将Java的PBEWithMD5AndDES转换为带有加密的JavaScript



我有以下代码在java中解密,我希望在nodejs中实现,但我发现了许多tuto解决我的问题,但他们使用盐,当我试图删除盐系统时不起作用。

My old functions from java

public void readLastLogin(File lastLogin) {
try {
final Cipher ciph = this.openCipher(Cipher.DECRYPT_MODE);
final DataInputStream dis = new DataInputStream(new CipherInputStream(new FileInputStream(lastLogin), ciph));
String user = dis.readUTF();
String token = dis.readUTF();
dis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public void saveLastLogin(File lastLogin, String user, String token) {
try {
final Cipher ciph = this.openCipher(Cipher.ENCRYPT_MODE);
final DataOutputStream dos = new DataOutputStream(new CipherOutputStream(new FileOutputStream(lastLogin), ciph));
dos.writeUTF(user);
dos.writeUTF(token);
dos.close();
} catch (final Exception e) {
e.printStackTrace();
}
}
private Cipher openCipher(final int mode) throws Exception {
final Random rnd = new Random(43287234L);
final byte[] data = new byte[8];
rnd.nextBytes(data);
final PBEParameterSpec spec = new PBEParameterSpec(data, 5);
final SecretKey key = SecretKeyFactory
.getInstance("PBEWithMD5AndDES")
.generateSecret(new PBEKeySpec("mysecretpassword".toCharArray()));
final Cipher ret = Cipher.getInstance("PBEWithMD5AndDES");
ret.init(mode, key, spec);
return ret;
}

我试了一下,但不工作

KDF(password, iterations) {
var key = new Buffer(password,'utf-8');
var i;
for (i = 0; i < iterations; i+=1) {
key = crypto.createHash("md5").update(key).digest();
}
return key;
}
getKeyIV(password, iterations) {
var key = this.KDF(password, iterations);
var keybuf = new Buffer(key,'binary').slice(0,8);
var ivbuf = new Buffer(key,'binary').slice(8,16);
return [ keybuf, ivbuf ];
}
decrypt(message) {
var kiv = this.getKeyIV('password' ,5);
var decipher = crypto.createDecipheriv('des-ede', kiv[0], kiv[1]);
var result = decipher.update(message, 'hex', 'utf-8');
return result + decipher.final('utf-8');
}

编辑:Java代码运行时生成的salt是十六进制编码的:0x0C9D4AE41E8315FC

final byte[] data = new byte[8];
data[0] = 12;
data[1] = -99;
data[2] = 74;
data[3] = -28;
data[4] = 30;
data[5] = -125;
data[6] = 21;
data[7] = -4;

代码中有三个问题:

  • Java代码使用writeUTF()。这是基于修改后的UTF-8,加上两个额外的字节来包含数据大小。一个用于修改UTF-8的NodeJS库是mutf-8
  • 必须使用des-cbc(DES在CBC模式下)而不是des-ede(ECB模式下的3DES/2TDEA)。如果DES在使用的NodeJS版本中不可用,它可以在CBC模式下用3DES/2TDEA模拟,des-ede-cbc,其中8字节的密钥必须与自身连接到16字节的密钥,将3DES减少到DES。这种解决方法的缺点是性能损失。Java代码应用一个静态的8字节盐,因为Random()是用一个静态种子实例化的。此盐可以在运行时确定为(十六进制编码):0x0C9D4AE41E8315FC

pbewithmd5anddes-jsPBEWithMD5AndDES到NodeJS的端口,可以使用KDF(),getKeyIV()decrypt()方法,但必须考虑到上述几点进行调整。此外,必须实现readUTF()(writeUTF()的对应版本)的功能。一个可能的解决方案是:

var crypto = require('crypto');
const { MUtf8Decoder } = require("mutf-8");
var pbewithmd5anddes = {
KDF: function(password,salt,iterations) {
var pwd = Buffer.from(password,'utf-8');
var key = Buffer.concat([pwd, salt]);
var i;
for (i = 0; i < iterations; i+=1) {
key = crypto.createHash('md5').update(key).digest();
}
return key;
},
getKeyIV: function(password,salt,iterations) {
var key = this.KDF(password,salt,iterations);
var keybuf = Buffer.from(key,'binary').subarray(0,8);
var ivbuf = Buffer.from(key,'binary').subarray(8,16);
return [ keybuf, ivbuf ];
},
decrypt: function(payload,password,salt,iterations) {
var encryptedBuffer = Buffer.from(payload,'base64');
var kiv = this.getKeyIV(password,salt,iterations);
//var decipher = crypto.createDecipheriv('des-cbc', kiv[0], kiv[1]);                              // Fix 1: If supported, apply DES-CBC with key K
var decipher = crypto.createDecipheriv('des-ede-cbc', Buffer.concat([kiv[0], kiv[0]]), kiv[1]);   // otherwise DES-EDE-CBC with key K|K  
var decryptedBuf = Buffer.concat([decipher.update(encryptedBuffer), decipher.final()])
var decrypted = this.readUTF(decryptedBuf)                                                        // Fix 2: apply writeUTF counterpart
return decrypted;
},
readUTF: function(decryptedBuf) {
var decoder = new MUtf8Decoder()
var decryptedData = []
var i = 0;
while (i < decryptedBuf.length){
var lengthObj = decryptedBuf.readInt16BE(i);
var bytesObj = decryptedBuf.subarray(i+2, i+2+lengthObj);
var strObj = decoder.decode(bytesObj)
decryptedData.push(strObj)
i += 2 + lengthObj;
}
return decryptedData;
}
};
测试:

在Java端,执行以下命令:

saveLastLogin(file, "This is the first plaintext with special characters like §, $ and €.", "This is the second plaintext with special characters like §, $ and €.");

写入file的原始密文是Base64编码的:

Ow8bdeNM0QpNFQaoDe7dhG3k9nWz/UZ6v3+wQVgrD5QvWR/4+sA+YvqtnQBsy35nQkwhwGRBv1h1eOa587NaFtnJUWVHsRsncLWZ05+dD2rYVpcZRA8s6P2iANK6yLr+GO/+UpZpSe0fA4fFqEK1nm3U7NXdyddfuOlZ3h/RiyxK5819LieUne4F8/TpMzT0RIWkxqagbVw=

这可以在NodeJS端解密:

var payload = 'Ow8bdeNM0QpNFQaoDe7dhG3k9nWz/UZ6v3+wQVgrD5QvWR/4+sA+YvqtnQBsy35nQkwhwGRBv1h1eOa587NaFtnJUWVHsRsncLWZ05+dD2rYVpcZRA8s6P2iANK6yLr+GO/+UpZpSe0fA4fFqEK1nm3U7NXdyddfuOlZ3h/RiyxK5819LieUne4F8/TpMzT0RIWkxqagbVw=';
var password = 'mysecretpassword';
var salt = Buffer.from('0C9D4AE41E8315FC', 'hex');
var iterations = 5;
var decrypted = pbewithmd5anddes.decrypt(payload,password,salt,iterations);
console.log(decrypted);

输出:

[
'This is the first plaintext with special characters like §, $ and €.',
'This is the second plaintext with special characters like §, $ and €.'
]

请注意,代码中包含严重漏洞:

  • PBEWithMD5AndDES使用基于破损MD5摘要(PBKDF1)的密钥派生,并使用已弃用的DES(近20年前正式撤销)作为加密算法。
  • 使用静态盐。
  • 5的迭代计数通常太小。
  • Random()(不像SecureRandom())不是加密强的。还请注意,使用像Random()这样的PRNG进行确定性密钥派生并不是一个好主意,因为实现可能会发生变化,即使使用相同的种子也会导致不同的结果。

最新更新