In order to write an simple nodejs app talking to an server written in java I have to implement the following functionality for nodejs.
public class Crypto {
My final solution 5 years ago was:
var forge = require('node-forge');
var crypto = require('crypto');
var base64Coder = require('./utils/tac-base64coder');
var ByteBuffer = forge.util.ByteBuffer;
var DES_EDE_KEY_LEN = 24;
var DES_BLOCK_SIZE = 8;
var SALT_BYTES = [0x45, 0xC4, 0x31, 0x72, 0x8A, 0x62, 0xB3, 0x9A];
var KEY_BUFFER_LENGTH = 24;
var IV_BUFFER_LENGTH = 8;
module.exports = {
/**
* Generates the derived key. The 16 bytes of the first digest and the 1st 8 bytes of the 2nd digest
* form the triple DES key, and the last 8 bytes of the 2nd digest form the IV.
*
* @method _deriveCipherKey
* @param {String} key The key phrase
* @param {Int8Array} salt The salt
* @param {Number} iCount The iteration count
* @returns {Buffer}
* @private
*/
_deriveCipherKey: function _deriveCipherKey (key, salt, iCount) {
var md;
var passwdBytes = new Buffer(key);
var i;
var toBeHashed;
var result = new Buffer(DES_EDE_KEY_LEN + DES_BLOCK_SIZE);
result.fill(0);
// if the 2 salt halves are the same, invert one of them
for (i = 0; i < 4; i++) {
if (salt[i] !== salt[i + 4]) {
break;
}
}
if (i === 4) { // same, invert 1st half
for (i = 0; i < 2; i++) {
var tmp = salt[i];
salt[i] = salt[3 - i];
salt[3 - 1] = tmp;
}
}
for (i = 0; i < 2; i++) {
toBeHashed = new Buffer(salt.length / 2);
toBeHashed.fill(0);
salt.copy(toBeHashed, 0, i * (salt.length / 2));
for (var j = 0; j < iCount; j++) {
md = crypto.createHash('md5');
md.update(toBeHashed);
md.update(passwdBytes);
toBeHashed = md.digest();
}
toBeHashed.copy(result, i * 16);
}
return result;
},
/**
* Encrypts the given string with the given key
*
* @method encrypt
* @param {String} encryptionKey The encryption key
* @param {String} toEncrypt The string to encrypt
* @returns {String}
*/
encrypt: function encrypt (encryptionKey, toEncrypt) {
var encodedStr = forge.util.encodeUtf8(toEncrypt);
var salt = new Buffer(SALT_BYTES);
var key = new Buffer(KEY_BUFFER_LENGTH);
var iv = new Buffer(IV_BUFFER_LENGTH);
var key2 = new ByteBuffer();
var iv2 = new ByteBuffer();
var derivedKey = this._deriveCipherKey(encryptionKey, salt, 12);
var cipher;
var i = 0;
derivedKey.copy(key, 0, 0, 24);
derivedKey.copy(iv, 0, 24);
for (; i < KEY_BUFFER_LENGTH; i++) {
key2.putByte(key[i]);
}
for (i = 0; i < IV_BUFFER_LENGTH; i++) {
iv2.putByte(iv[i]);
}
cipher = forge.des.createEncryptionCipher(key2);
cipher.start(iv2);
cipher.update(forge.util.createBuffer(encodedStr));
cipher.finish();
return base64Coder.encode(cipher.output.getBytes().toString());
},
/**
* Decrypts the given base64 string with the given key
*
* @method decrypt
* @param {String} encryptionKey The decryption key
* @param {String} toDecrypt The encrypted base64 string
* @returns {String}
*/
decrypt: function decrypt (encryptionKey, toDecrypt) {
var decr = forge.util.decode64(toDecrypt);
var salt = new Buffer(SALT_BYTES);
var key = new Buffer(KEY_BUFFER_LENGTH);
var iv = new Buffer(IV_BUFFER_LENGTH);
var derivedKey = this._deriveCipherKey(encryptionKey, salt, 12);
var key2 = new forge.util.ByteBuffer();
var iv2 = new forge.util.ByteBuffer();
var i = 0;
var cipher;
derivedKey.copy(key, 0, 0, 24);
derivedKey.copy(iv, 0, 24);
for (; i < KEY_BUFFER_LENGTH; i++) {
key2.putByte(key[i]);
}
for (i = 0; i < IV_BUFFER_LENGTH; i++) {
iv2.putByte(iv[i]);
}
cipher = forge.des.createDecryptionCipher(key2);
cipher.start(iv2);
cipher.update(forge.util.createBuffer(decr));
cipher.finish();
return cipher.output.getBytes().toString('utf8');
}
};
I reverse engineered the DESede part of the key derivation function found at com.sun.crypto.provider.PBES1Core#deriveCipherKey();
We use Jasypt as encryption library in a Java server and our node.js server is able to encrypt and decrypt with this. I hope it helps (Written in ES2015, runs in node v4.0.0 and up):
'use strict';
var crypto = require('crypto');
class Encryption {
constructor() {
this.privateKey = new Buffer('<your password>', 'utf-8');
}
encrypt(message) {
var salt = crypto.randomBytes(8);
var key = this._generateKey(this.privateKey, salt);
var cipher = crypto.createCipheriv('des-ede3-cbc', this._subBuf(key, 0, 24), this._subBuf(key, 24));
var result = cipher.update(message, 'utf-8', 'hex');
return salt.toString('hex') + result + cipher.final('hex');
}
decrypt(message) {
var salt = new Buffer(message.substr(0, 16), 'hex');
var key = this._generateKey(this.privateKey, salt);
message = message.substr(16);
var decipher = crypto.createDecipheriv('des-ede3-cbc', this._subBuf(key, 0, 24), this._subBuf(key, 24));
var result = decipher.update(message, 'hex', 'utf-8');
return result + decipher.final('utf-8');
}
_generateKey(password, salt) {
if (!(password instanceof Buffer)) {
throw new Error('Password needs to be a buffer');
}
if (!(salt instanceof Buffer) || salt.length != 8) {
throw new Error('Salt needs to be an 8 byte buffer');
}
var iterations;
for(iterations = 0; iterations < 4 && salt[iterations] == salt[iterations + 4]; ++iterations) {}
if(iterations == 4) {
for(iterations = 0; iterations < 2; ++iterations) {
var tmp = salt[iterations];
salt[iterations] = salt[3 - iterations];
salt[2] = tmp; // Seems like an error that we have to live with now
}
}
var result = new Buffer(32);
for(iterations = 0; iterations < 2; ++iterations) {
var intermediate = new Buffer(salt.length / 2);
for (let i = 0; i < salt.length / 2; i++) {
intermediate[i] = salt[iterations * (salt.length / 2) + i];
}
for(let i = 0; i < 1000; ++i) {
var hash = crypto.createHash('md5');
hash.update(intermediate);
hash.update(password);
intermediate = hash.digest();
}
for (let i = 0; i<intermediate.length; i++) {
result[i + (iterations * 16)] = intermediate[i];
}
}
return result;
}
_subBuf(buffer, start, length) {
if (!length) {
length = buffer.length - start;
}
var result = new Buffer(length, 'hex');
for (let i = 0; i < length; i++) {
result[i] = buffer[i + start]
}
return result;
}
}
To explain a little what's going on:
Some code clean up might be necessary here, but it should at least get you started in the right direction.