-1

I referred nodejs encryption mentioned in https://stackoverflow.com/a/65072352/4910936 and i could encrypt and while decrypting getting error ""Error: Unsupported state or unable to authenticate data"

var crypto = require('crypto');

console.log('AES GCMC 256 String encryption with PBKDF2 derived key');

var plaintext = 'The quick brown fox jumps over the lazy dog';
console.log('plaintext:  ', plaintext);

const cryptoConfig = {
    cipherAlgorithm: 'aes-256-gcm',
    masterKey: 'somekey',
    iterations: 65535,
    keyLength: 32,
    saltLength: 16,
    ivLength: 12,
    tagLength: 16,
    digest: 'sha512'
}

var ciphertext = encrypt(plaintext);
console.log('ciphertext: ', ciphertext);
decrypt(ciphertext)
function encrypt(content) {
    const salt = crypto.randomBytes(cryptoConfig.saltLength);
    console.log("salt : ", salt)
    const iv = crypto.randomBytes(cryptoConfig.ivLength);
    console.log("iv : ", iv)
    const key = crypto.pbkdf2Sync(cryptoConfig.masterKey, salt, cryptoConfig.iterations,
    cryptoConfig.keyLength, cryptoConfig.digest);
    const cipher = crypto.createCipheriv(cryptoConfig.cipherAlgorithm, key, iv);
    const encrypted = Buffer.concat([cipher.update(content, 'utf8'), cipher.final()]);
    const tag = cipher.getAuthTag();
    console.log("tag : ", tag)
    // ### put the auth tag at the end of encrypted
    //const encdata = Buffer.concat([salt, iv, tag, encrypted]).toString('base64');
    const encdata = Buffer.concat([salt, iv, encrypted, tag]).toString('base64');
    
    return encdata;
}
    
function decrypt(encdata){
    ///decrypt
    // base64 decoding
    const bData = Buffer.from(encdata, 'base64');

    // convert data to buffers
    const salt1 = bData.slice(0, 16);
    const iv1 = bData.slice(16, 32);
    const tag1 = bData.slice(32, 48);
    const text1 = bData.slice(48);

    // derive key using; 32 byte key length
    // const key = crypto.pbkdf2Sync(cryptoConfig.masterkey, salt , 2145, 32, 'sha512');
    const key1 = crypto.pbkdf2Sync(cryptoConfig.masterKey, salt1, cryptoConfig.iterations,
    cryptoConfig.keyLength, cryptoConfig.digest)

    // AES 256 GCM Mode
    const decipher = crypto.createDecipheriv('aes-256-gcm', key1, iv1);
    decipher.setAuthTag(tag1);

    // encrypt the given text
    const decrypted = decipher.update(text1, 'binary', 'utf8') + decipher.final('utf8');

    console.log(decrypted)
}

From error it looks like I messed up while separating IV, salt from the encrypted data and thus not matching with what used while encrypting.

Pat
  • 535
  • 1
  • 16
  • 41
  • 2
    You do `Buffer.concat([salt, iv, encrypted, tag])` where iv (aka nonce) is 12 bytes but (after de-base64) you split as salt,iv,tag,encrypted with iv as 16 and the last two swapped. Why don't you use the same `config.` fields on decrypt you did on encrypt? – dave_thompson_085 Jul 15 '21 at 07:19
  • @dave_thompson_085 same encryption&decryption is shared across cross platforms. Encrypted in Nodejs, decrypted in java and vice versa, so cannot use config. Thanks for finding that encrypted and tag swapped. I fixed it in encryption version and followed Micahel answer below and wrok. – Pat Jul 15 '21 at 13:53
  • 1
    Your code can be easily fixed with: `const iv1 = bData.slice(16, 28); const text1 = bData.slice(28, bData.length - 16); const tag1 = bData.slice(bData.length - 16);` The advantage of the original code is that concatenation is done on byte level, so no separators are needed and Base64 encoding is required only once. – Topaco Jul 15 '21 at 17:48
  • @user9014097 Yes i see that now using multiple Base64 encoding and removed separators and used what you suggested. Thanks – Pat Jul 15 '21 at 21:57

1 Answers1

1

This is a sample program I wrote for my cross-platform-crypto blog - this should help you in separating and shipping salt, iv and gcm tag.

A live running version can be found here: https://replit.com/@javacrypto/CpcNodeJsCryptoAesGcm256Pbkdf2StringEncryption#index.js/

var crypto = require('crypto');

console.log('AES GCM 256 String encryption with PBKDF2 derived key');

var plaintext = 'The quick brown fox jumps over the lazy dog';
console.log('plaintext: ', plaintext);
var password = "secret password";

console.log('\n* * * Encryption * * *');

var ciphertextBase64 = aesGcmPbkdf2EncryptToBase64(password, plaintext);
console.log('ciphertext (Base64): ' + ciphertextBase64);
console.log('output is (Base64) salt : (Base64) nonce : (Base64) ciphertext : (Base64) gcmTag');

console.log('\n* * * Decryption * * *');
var ciphertextDecryptionBase64 = ciphertextBase64;
console.log('ciphertext (Base64): ', ciphertextDecryptionBase64);
console.log('input is (Base64) salt : (Base64) nonce : (Base64) ciphertext : (Base64) gcmTag');
var decryptedtext = aesGcmPbkdf2DecryptFromBase64(password, ciphertextBase64);
console.log('plaintext: ', decryptedtext);

function aesGcmPbkdf2EncryptToBase64(password, data) {
  var PBKDF2_ITERATIONS = 15000;
  var salt = generateSalt32Byte();
  var key = crypto.pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, 32, 'sha256');
  var nonce = generateRandomNonce();
  const cipher = crypto.createCipheriv('aes-256-gcm', key, nonce);
  let encryptedBase64 = '';
  cipher.setEncoding('base64');
  cipher.on('data', (chunk) => encryptedBase64 += chunk);
  cipher.on('end', () => {
  // do nothing console.log(encryptedBase64);
  // Prints: some clear text data
  });
  cipher.write(data);
  cipher.end();
  var saltBase64 = base64Encoding(salt);
  var nonceBase64 = base64Encoding(nonce);
  var gcmTagBase64 = base64Encoding(cipher.getAuthTag());
  return saltBase64 + ':' + nonceBase64 + ':' + encryptedBase64 + ':' + gcmTagBase64;
}

function aesGcmPbkdf2DecryptFromBase64(password, data) {
  var PBKDF2_ITERATIONS = 15000;
  var dataSplit = data.split(":");
  var salt = base64Decoding(dataSplit[0]);
  var gcmTag = base64Decoding(dataSplit[3]);
  var key = crypto.pbkdf2Sync(password, salt, PBKDF2_ITERATIONS, 32, 'sha256');
  var nonce = base64Decoding(dataSplit[1]);
  var ciphertext = dataSplit[2];
  const decipher = crypto.createDecipheriv('aes-256-gcm', key, nonce);
  decipher.setAuthTag(gcmTag);
  let decrypted = '';
  decipher.on('readable', () => {
    while (null !== (chunk = decipher.read())) {
      decrypted += chunk.toString('utf8');
    }
  });
  decipher.on('end', () => {
  // do nothing console.log(decrypted);
  });
  decipher.write(ciphertext, 'base64');
  decipher.end();
  return decrypted;
}

function generateSalt32Byte() {
  return crypto.randomBytes(32);
}

function generateRandomNonce() {
  return crypto.randomBytes(12);
}

function base64Encoding(input) {
  return input.toString('base64');
}

function base64Decoding(input) {
  return Buffer.from(input, 'base64')
}
Michael Fehr
  • 5,827
  • 2
  • 19
  • 40