Nodejs javascript implementation of PBEWithMD5AndTripleDES/CBC/PKCS5Padding

﹥>﹥吖頭↗ 提交于 2020-01-10 15:59:13

问题


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 {
  Cipher decipher;

  byte[] salt = {
      (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04,
      (byte) 0x0A, (byte) 0x0B, (byte) 0x0C, (byte) 0x0D
  };
  int iterationCount = 10;

  public Crypto(String pass) {
    try {
      KeySpec keySpec = new PBEKeySpec(pass.toCharArray(), salt, iterationCount);

      SecretKey key = SecretKeyFactory.getInstance(
          "PBEWithMD5AndTripleDES").generateSecret(keySpec);

      ecipher = Cipher.getInstance("PBEWithMD5AndTripleDES/CBC/PKCS5Padding");

      AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount);

      decipher.init(Cipher.DECRYPT_MODE, key, paramSpec);

    } catch (Exception ex) {
    }
  }
}

I use the crypto module of nodejs

var crypto = require('crypto'),
      pass = new Buffer(wek),
      salt = new Buffer([0x01, 0x02, 0x03, 0x04, 0x0A, 0x0B, 0x0C, 0x0D])
      password = 'mySecretPassword'
      key = crypto.pbkdf2(pass, salt, 10, 256)
      cipher, 
      encrypted;

cipher = crypto.createCipher('des-ede-cbc', key);
encrypted = cipher.update(new Buffer('the very secred information'));

After sending the encrypted information to the server, I can't decrypt the message with the decipher Object as listed in the java code sample above. I think the main problem is the md5 part. I can't figure out how to implement that with the crypto nodejs module. Has anyone an idea how to solve this problem? Or is ther any other module or library to achieve that?

EDIT: I tried another module for nodejs: node-forge

forge = require('node-forge')

var numIterations = 10,
      keyLength = 24,
      password = forge.util.createBuffer('mySecretPassword'),
      salt = new forge.util.ByteBuffer(new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x0A, 0x0B, 0x0C, 0x0D])),
      derivedKey = forge.pkcs5.pbkdf2(password, salt.getBytes(), numIterations, keyLength, forge.md.md5.create())
      iv = {}; // TODO... ???

var cipher = forge.des.createEncryptionCipher(derivedKey);
cipher.start(iv);
cipher.update('the very secred information');
cipher.finish();
var encrypted = cipher.output;

But I have several problems/questions:

  • Do I use the correct algorithm in javascript?
  • Is the salt calculation match with the java implementation?
  • How can I determine which keyLength is used in the java implementation?
  • How is the initialization vector generated in the java implementation? In the last code sample with node-forgeI have to provide the iv on cipher.start(iv). In the java code I can't see how this is done. In my opinion the iv must be the same on client and server or is this incorrect?

回答1:


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:

  • Encrypted messages are returned in hex format, something else might fit your implementation better.
  • The _generateKey() is a direct copy from the java source.
  • The keys used are 32 byte length and assume that the first 24 bytes are the keys for TripleDES and the last 8 are the salt
  • The generated message is prefixed with the random generated salt that used to encrypt the message.
  • Depending on the security settings of your JVM it might be possible that you are not actually using des-ede3 (cbc seems to be a fixed setting). You should definitely double check if this works with your setup.

Some code clean up might be necessary here, but it should at least get you started in the right direction.




回答2:


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');
  }
};


来源:https://stackoverflow.com/questions/22223043/nodejs-javascript-implementation-of-pbewithmd5andtripledes-cbc-pkcs5padding

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!