Reputation: 2305
I'm rewriting an encryption algorithm (implemented in C#) using Node. The encryption uses a 32-byte key and a 16-byte IV and it uses Rfc2898DeriveBytes to generate the key and IV. I have used crypto.pbkdf2Sync
to generate a key of 48 bytes instead of 32. The first 32 bits would be the key and the remaining bits would serve as IV as shown below:
const crypto = require( "crypto");
const secret = 'e23d5bb0-2349-289a-d932-abc5a238a873';
const salt = '1bca26a8-49b8-6ad0-b65c-206a96107702';
const algorithm = 'aes-256-xts';
const keyAndIv = crypto.pbkdf2Sync(Buffer.from(secret, "binary"),
Buffer.from(salt, "binary"),
1024,
48,
'sha1');
const key = keyAndIv.slice(0, 32);
const iv = keyAndIv.slice(32, 48);
const cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update('test', 'utf8', 'hex');
encrypted += cipher.final('hex');
console.log(encrypted);
This is throwing the below error:
Error: Invalid key length
at Cipheriv.createCipherBase (internal/crypto/cipher.js:78:18)
at Cipheriv.createCipherWithIV (internal/crypto/cipher.js:122:20)
at new Cipheriv (internal/crypto/cipher.js:231:22)
at Object.createCipheriv (crypto.js:105:10)
Any thoughts would be really helful.
Upvotes: 2
Views: 293
Reputation: 2305
Below code works for most of the ciphers:
const crypto = require( "crypto");
const base64url = require('base64url');
const secret = 'secret';
const salt = 'add some salt';
const plainText = 'test';
const algorithm = 'aes-256-ctr';
const derivedBytes = crypto.pbkdf2Sync(Buffer.from(secret), Buffer.from(salt), 1024, 48, 'sha1');
const key = derivedBytes.slice(0, 32);
const iv = derivedBytes.slice(32, 48);
const cipher = crypto.createCipheriv(algorithm, key, iv);
const output = Buffer.concat([cipher.update(plainText, 'utf8'), cipher.final()]);
console.log(output.toString('hex'));
console.log(base64url(output));
Detailed blog: https://www.devinstincts.com/2019/05/15/rewrite-c-encryption-using-node-crypto/
Upvotes: 0
Reputation: 49131
The cause of the observed behaviour is the used mode, AES-256-XTS.
AES-XTS generates two keys from the passed key and is available as AES-256-XTS and AES-128-XTS. AES-256-XTS expects a 64-byte key and generates from this two 32-byte keys, AES-128-XTS expects a 32-byte key and generates two 16-byte keys.
Instead of padding ciphertext stealing is used. Therefore, plaintext and ciphertext have the same length. In addition, the plaintext must be at least one block (= 16 bytes) in size. More detailed information about AES-XTS can be found here and here.
The use of a 32-byte key in the C#-code speaks for the use of AES-128-XTS and not AES-256-XTS, for which a 64-byte key would be necessary. If the algorithm in the code is changed from AES-256-XTS to AES-128-XTS, the error will no longer occur.
E.g. for the posted input data the plain text
The quick brown fox jumped over the lazy dog
is encrypted with AES-128-XTS in the following ciphertext
b70d9f10ea6c1db513e141290059a73ab7c454e7d0a24fe482c9a6023a783303fe8bcc41bec1734d85af84ba
There's nothing wrong with the key and IV generation.
The NodeJS-method pbkdf2Sync
with digest = 'SHA1'
is the counterpart to the C#-method Rfc2898DeriveBytes
. Both implement PBKDF2 with HMACSHA1 and therefore return the same result for the same input data. E.g. for the posted input data:
secret: e23d5bb0-2349-289a-d932-abc5a238a873
salt: 1bca26a8-49b8-6ad0-b65c-206a96107702
iteration count: 1024
key size: 48
the following byte-sequence is generated:
BE00676F6A3D57EE66FF618FDE5BB15C0E1FC9ECDE5CE949BC784D14ACB7963B49FA9319394A69024A1F359BCC23C703
Upvotes: 1