Nagendra Varma
Nagendra Varma

Reputation: 2305

C# Rfc2898DeriveBytes to node

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

Answers (2)

Nagendra Varma
Nagendra Varma

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

Topaco
Topaco

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

Related Questions