Reputation: 488
Code Sample is as follows:
var crypto = require('crypto');
var key = 'ExchangePasswordPasswordExchange';
var plaintext = '150.01';
var iv = new Buffer(crypto.randomBytes(16))
ivstring = iv.toString('hex');
var cipher = crypto.createCipheriv('aes-256-cbc', key, ivstring)
var decipher = crypto.createDecipheriv('aes-256-cbc', key,ivstring);
cipher.update(plaintext, 'utf8', 'base64');
var encryptedPassword = cipher.final('base64');
Getting error of invalid IV length.
Upvotes: 18
Views: 57008
Reputation: 21
In case of cypher aes-256-cbc
, required length for Key and IV is 32 Bytes and 16 Bytes.
You can calculate Key length by dividing 256
bits by 8
bits, equals 32
bytes.
Following GetUnsafeKeyIvSync(password)
uses exactly same behavior as previous crypto did in old days.
There is no salt, and single iteration with MD5 digest, so anyone can generate exactly same Key and Iv.
This is why deprecated.
However, you may still need to use this approach only if your encrypted data is stored and cannot be changed(or upgraded.).
Do NOT use this function for new project. This is provided only for who cannot upgrade previously encrypted data for other reason.
import * as crypto from 'node:crypto';
import { Buffer } from 'node:buffer';
export function GetUnsafeKeyIvSync(password) {
try {
const key1hash = crypto.createHash('MD5').update(password, 'utf8');
const key2hash = crypto.createHash('MD5').update(key1hash.copy().digest('binary') + password, 'binary');
const ivhash = crypto.createHash('MD5').update(key2hash.copy().digest('binary') + password, 'binary');
const Key = Buffer.from(key1hash.digest('hex') + key2hash.digest('hex'), 'hex');
const IV = Buffer.from(ivhash.digest('hex'), 'hex');
return { Key, IV };
}
catch (error) {
console.error(error);
}
}
export function DecryptSync(data, KeyIv) {
let decrypted;
try {
const decipher = crypto.createDecipheriv('aes-256-cbc', KeyIv.Key, KeyIv.IV);
decrypted = decipher.update(data, 'hex', 'utf8');
decrypted += decipher.final('utf8');
}
catch (error) {
console.error(error);
decrypted = '';
}
return decrypted;
}
export function EncryptSync(data, KeyIv) {
let encrypted;
try {
const cipher = crypto.createCipheriv('aes-256-cbc', KeyIv.Key, KeyIv.IV);
encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
}
catch (error) {
console.error(error);
encrypted = '';
}
return encrypted;
}
For testing,
export function Test() {
const password = 'my plain text password which is free from length requirment';
const data = 'this is data to be encrypted and decrypted';
// Use same logic to retrieve as legacy crypto did.
// It is unsafe because there is no salt and single iteration.
// Anyone can generate exactly same Key/Iv with the same password.
// We would only need to use this only if stored encrypted data must be decrypted from previous result.
// Do NOT use this for new project.
const KeyIv = GetUnsafeKeyIvSync(password);
// Key is in binary format, for human reading, converted to hex, but Hex string is not passed to Cypher.
// Length of Key is 32 bytes, for aes-256-cbc
console.log(`Key=${KeyIv.Key.toString('hex')}`);
// Key is in binary format , for human reading, converted to hex, but Hex string is not passed to Cypher.
// Length of IV is 16 bytes, for aes-256-cbc
console.log(`IV=${KeyIv.IV.toString('hex')}`);
const encrypted = EncryptSync(data, KeyIv);
console.log(`enc=${encrypted}`);
const decrypted = DecryptSync(encrypted, KeyIv);
console.log(`dec=${decrypted}`);
console.log(`Equals ${decrypted === data}`);
return decrypted === data;
}
Upvotes: 0
Reputation: 963
In Node.js 10 I had to use a 12 bytes string for it to work... const iv = crypto.pseudoRandomBytes(6).toString('hex');
. 16 bytes gave me an error. I had this problem when I was running Node.js 10 globally, and then uploading it to a Cloud Functions server with Node.js 8. Since Cloud Functions have Node.js 10 in beta, I just switched to that and now it works with the 12 bytes string. It didn't even work with a 16 bytes string on Node.js 8 on the Cloud Functions server...
Upvotes: 0
Reputation: 506
The above answer adds more overhead than needed, since you converted each byte to a hexidecimal representation that requires twice as many bytes all you need to do is generate half the number of bytes
var crypto = require('crypto');
var key = 'ExchangePasswordPasswordExchange';
var plaintext = '150.01';
var iv = new Buffer(crypto.randomBytes(8))
ivstring = iv.toString('hex');
var cipher = crypto.createCipheriv('aes-256-cbc', key, ivstring)
var decipher = crypto.createDecipheriv('aes-256-cbc', key,ivstring);
cipher.update(plaintext, 'utf8', 'base64');
var encryptedPassword = cipher.final('base64');
Upvotes: 14
Reputation: 3645
From https://github.com/nodejs/node/issues/6696#issuecomment-218575039 -
The default string encoding used by the crypto module changed in v6.0.0 from binary to utf8. So your binary string is being interpreted as utf8 and is most likely becoming larger than 16 bytes during that conversion process (rather than smaller than 16 bytes) due to invalid utf8 character bytes being added.
Modifying your code so that ivstring
is always 16 characters in length should solve your issue.
var ivstring = iv.toString('hex').slice(0, 16);
Upvotes: 30