PBEWithMD5AndTripleDES/CBC/PKCS5Padding的Nodejs javascript实现

为了编写一个简单的 nodejs 应用程序与用 java 编写的服务器通信,我必须为 nodejs 实现以下功能.

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) {
    }
  }
}

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

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

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?

我为 nodejs 尝试了另一个模块:node-forge

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:

  • 我是否在 javascript 中使用了正确的算法?
  • salt计算是否与java实现匹配?
  • 如何确定在 java 实现中使用了哪个 keyLength?
  • 初始化向量在java实现中是如何产生的?在 node-forge 的最后一个代码示例中,我必须在 cipher.start(iv) 上提供 iv.在 java 代码中,我看不到这是如何完成的.我认为客户端和服务器上的 iv 必须相同还是不正确?
  • 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?

推荐答案

我对 com.sun.crypto.provider.PBES1Core#deriveCipherKey() 处的密钥派生函数的 DESede 部分进行了逆向工程;

I reverse engineered the DESede part of the key derivation function found at com.sun.crypto.provider.PBES1Core#deriveCipherKey();

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

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

解释一下发生了什么:

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

这里可能需要清理一些代码,但它至少应该让您朝着正确的方向开始.

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

相关文章