3

为了编写一个简单的 nodejs 应用程序与用 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 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'));

将加密信息发送到服务器后,我无法使用decipher上面 java 代码示例中列出的 Object 解密消息。我认为主要问题是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实现匹配?
  • 如何确定keyLengthjava 实现中使用了哪个?
  • initialization vectorjava实现中如何生成?在最后一个代码示例中,node-forge我必须提供ivon cipher.start(iv). 在 java 代码中,我看不到这是如何完成的。在我看来iv,客户端和服务器上的必须相同还是不正确?
4

2 回答 2

2

我对 com.sun.crypto.provider.PBES1Core#deriveCipherKey() 中的密钥派生函数的 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 似乎是一个固定设置)。您绝对应该仔细检查这是否适用于您的设置。

一些代码清理在这里可能是必要的,但它至少应该让你朝着正确的方向开始。

于 2016-01-27T23:28:41.940 回答
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');
  }
};
于 2019-11-22T11:15:16.070 回答