1

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 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

2 Comments

good morning in the begin thank's for your help i tried to use your code but unfortunately it failed to decrypt a file encrypted by java a point to underline, for safety measure I put arbitrary privatekey and Salt. the salt used in java code is 48 characters the privatekey in java code is 9 character I don't know if it will influence your code Thank you again for your help
kais: My code should work for any length inputs (that fit in memory), and on re-testing with your code modified for 9-char pw and 48-char salt it works fine. It might fail if pw or salt contains any non-ASCII character(s), depending on your platform(s) and environment(s). If you find a failure case with data that you can post (i.e. that need not be kept secret) add to your Q (with the ciphertext in a form that doesn't get corrupted, like hex or base64) and I can look at it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.