Nodejs javascript实现PBEWithMD5AndTripleDES / CBC / PKCS5Padding

时间:2014-03-06 11:25:06

标签: javascript node.js cryptography tripledes

为了编写一个简单的nodejs app与用java编写的服务器交谈,我必须为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) {
    }
  }
}

我使用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'));

在将加密信息发送到服务器之后,我无法使用上面java代码示例中列出的decipher对象解密消息。我认为主要问题是md5部分。我无法弄清楚如何使用crypto nodejs模块实现该功能。有谁知道如何解决这个问题?或者是其他任何模块或库来实现这一目标吗?

编辑:我为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;

但我有几个问题/问题:

  • 我是否在javascript中使用了正确的算法?
  • salt计算是否与java实现匹配?
  • 如何确定java实现中使用了哪个keyLength
  • 如何在java实现中生成initialization vector?在使用node-forge的最后一个代码示例中,我必须在iv上提供cipher.start(iv)。在java代码中,我无法看到这是如何完成的。在我看来,iv在客户端和服务器上必须是相同的,或者这是不正确的?

2 个答案:

答案 0 :(得分:2)

我反向设计了com.sun.crypto.provider.PBES1Core#derivedveCipherKey();

中的密钥派生函数的DESede部分。

我们在Java服务器中使用Jasypt作为加密库,我们的node.js服务器可以使用它加密和解密。我希望它有所帮助(在ES2015中编写,在节点v4.0.0及更高版本中运行):

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

要解释一下发生了什么:

  • 加密的消息以十六进制格式返回,其他内容可能更适合您的实现。
  • _generateKey()是来自java源代码的直接副本。
  • 使用的密钥长度为32字节,假设前24个字节是TripleDES的密钥,最后8个是盐
  • 生成的消息以随机生成的盐为前缀,用于加密消息。
  • 根据JVM的安全设置,您可能实际上没有使用des-ede3(cbc似乎是固定设置)。你绝对应该仔细检查这是否适合你的设置。

这里可能需要清理一些代码,但至少应该让你从正确的方向开始。

答案 1 :(得分:0)

我5年前的最终解决方案是:

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