Issue trying to decrypt a java encrypted file using NodeJS

Viewed 123

Please help me on solving below problem/issue:

I have a document which is encrypted using the algorithm AES/CTR/NOPADDING. The encryption is made by a project developped using Java.

We recently launched a migration project aiming to migrate all our microservice from Java to NodeJS. The current microservice responsible for the encryption can't be migrated right now.

My task actually is to migrate one of these microservice from Java to NodeJS, this microsevice should be able to decrypt these document. I'm facing an issue when trying to decrypt the document, result is still an encrypted document instead of an Excel file...

I'm sharing with you:

  1. The Java code for encryption
  2. The node code for decryption

I have no knowledge of Java or cryptography so any help in that direction will be highly appreciated.

Java code:

  / **
 * Encryption utilities.
 *
 */
public class EncryptionUtil {
    /**
     * Salt.
     */
    private static final String SALT = "dummydummydummy1";

    static {
        // Initialize Bouncy Castle provider
        Security.insertProviderAt(new BouncyCastleProvider(), 1);
    }

private String privateKey = "kais"
    /**
     * Initialize a Cipher.
     *
     * @param privateKey Private key
     * @param mode       Mode (encrypt or decrypt)
     * @return Cipher
     * @throws Exception e
     */
    private static Cipher getCipher(String privateKey, int mode) throws Exception {
        PBEKeySpec keySpec = new PBEKeySpec(privateKey.toCharArray(), SALT.getBytes(), 2000, 256);
        SecretKeyFactory skf = SecretKeyFactory.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC");
        SecretKey desKey = skf.generateSecret(keySpec);
        Cipher cipher = Cipher.getInstance("AES/CTR/NOPADDING");
        cipher.init(mode, desKey);
        return cipher;
    }
}

Node code:

const algorithm = 'aes-256-ctr';
const SALT = "dummydummydummy1";
const iv = crypto.randomBytes(16);

const secretKey = 'kais'


// input file
const r = fs.createReadStream(appRoot + `/1c6dd5c1-02ff-49c9-813b-a554fd94344e`);


// decrypt content
const decrypt = crypto.createDecipheriv(algorithm, SALT, iv);

// write file
const w = fs.createWriteStream(appRoot + `/Jiratraited.xlsx`);

// start pipe
r.pipe(decrypt).pipe(w);
1 Answers

Your Java code is using a PBE (Password-Based Encryption) scheme; this derives the key, and (for this scheme) also IV, from the password and salt via a KDF (Key Derivation Function) and specifically PBKDF (Password-Based KDF). You need to match this on the JS side.

This scheme uses the PKCS12 PBKDF, which nodejs 'crypto' doesn't expose although the underlying OpenSSL does implement it; neither does crypto-js AFAICS. There may be some other library, but I didn't want to spend time searching so I just wrote one (which took longer than I expected because code I have in my library and have been using for yoinks turned out to have a bug). Anyway:

$ cat 72351593.js
const crypto = require('crypto'), fs = require('fs');

function pkcs12kdf (pw,salt,hash,iter,id,len) {
  let v = ["md5","sha1","sha224","sha256"].indexOf(hash)>=0? 64: 128;
  salt = Buffer.from(salt,'utf8'); pw = Buffer.from(pw+"\0",'utf16le').swap16();
  let D = Buffer.alloc(v,id), P = Buffer.alloc(0), S = Buffer.alloc(0);
  let ss = Math.ceil(salt.length/v)*v, pp = Math.ceil(pw.length/v)*v;
  while( S.length < ss ) S = Buffer.concat([S,salt]); S = S.subarray(0,ss);
  while( P.length < pp ) P = Buffer.concat([P,pw]);   P = P.subarray(0,pp);
  let I = Buffer.concat([S,P]), result = Buffer.alloc(0);
  while( true ){
    let x = Buffer.concat([D,I]);
    for( let i = 1; i <= iter; i++ ){
      h = crypto.createHash(hash); h.update(x); x = h.digest();
    }
    result = Buffer.concat([result,x]);
    if( result.length >= len ) return result.subarray(0,len);
    let B = Buffer.alloc(0);
    while( B.length < v ) B = Buffer.concat([B,x]); B = B.subarray(0,v);
    for( let i = 0; i < I.length; i += v ){ // add B+1 to I at i
      let c = 1<<8;
      for( let j = v-1; j>=0; j-- ) I[i+j] = (c = I[i+j]+B[j]+(c>>8)) & 0xFF;
    }
  }
}

// note fixed pw and salt is totally insecure, but matches Q
let key = pkcs12kdf("kais","dummydummydummy1","sha256",2000,1,32);
let iv  = pkcs12kdf("kais","dummydummydummy1","sha256",2000,2,16);
let enc = Buffer.from(fs.readFileSync("72351593.dat"),'binary');
let d = crypto.createDecipheriv ('aes-256-ctr',key,iv);
let clr = Buffer.concat([d.update(enc),d.final()]);
console.log ("decrypted="+clr.toString('latin1'));

$ node 72351593.js
decrypted=test

If your real code is using fixed password and salt like this example, you (and whoever is using this) should be aware that doing so with a CTR-mode cipher for more than one data value is totally insecure; it is the canonical example of a 'two-time pad' frequently condemned in crypto.SX and security.SX. But security is offtopic for SO.

Related