我正在尝试使用 NodeJS 解密遗留数据库,但没有成功。该代码最初是用 Java 编写的。
已编辑
用于加密数据的Java源代码。
import java.util.*;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.SecretKey;
import javax.crypto.spec.DESKeySpec;
import java.util.Base64;
public class Main {
public static void main(String[] args) {
String encrypted = "";
String secretCredentials = "abcdefghijklmnop";
String plainText = "Text to encrypt!";
String encoding = "UTF8";
String strategy = "DES";
try {
byte[] secretCredentialsBytes = secretCredentials.getBytes(encoding);
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(strategy);
SecretKey secretKey = secretKeyFactory.generateSecret(new DESKeySpec(secretCredentialsBytes));
Cipher cipher = Cipher.getInstance(strategy);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] plainTextBytes = plainText.getBytes();
byte[] outputBytes = cipher.doFinal(plainTextBytes);
byte[] encryptedTextBytes = Base64.getEncoder().encode(outputBytes);
encryptedText = new String(encryptedTextBytes);
System.out.println(encryptedText); //=> AIGBXYEWXz5w2Z3Fjqe5YiQbyR5eVbPW
} catch (Exception e) {
e.printStackTrace();
}
}
}
解密数据的NodeJS源码:
const {
createDecipheriv
} = await import('crypto');
const encrypted = 'AIGBXYEWXz5w2Z3Fjqe5YiQbyR5eVbPW';
const encryptedAsBuffer = Buffer.from(encrypted, 'base64');
const credentials = 'abcdefghijklmnop';
let credentialsAsBuffer = Buffer.from(credentials, 'utf-8');
const strategy = 'des-ede-cbc';
const iv = credentialsAsBuffer.subarray(credentialsAsBuffer.length - (credentialsAsBuffer.length - 8));
const decipher = createDecipheriv(strategy, credentialsAsBuffer, iv);
let decrypted = decipher.update(encryptedAsBuffer, 'base64', 'utf-8');
decrypted += decipher.final('utf-8');
console.log(decrypted);
但是返回错误...
node:internal/crypto/cipher:193
const ret = this[kHandle].final();
^
Error: error:1C800064:Provider routines::bad decrypt
at Decipheriv.final (node:internal/crypto/cipher:193:29)
at file:///... {
library: 'Provider routines',
reason: 'bad decrypt',
code: 'ERR_OSSL_BAD_DECRYPT'
}
Node.js v20.11.0
我知道“DES”算法遇到了漏洞,但它是一个遗留系统。
Java代码只指定了算法,没有指定模式,也没有指定填充。在这种情况下,将使用依赖于提供商的默认值。
对于 SunJCE 提供程序,这意味着 ECB 用作模式,PKCS#7 用作填充。反过来,欧洲央行意味着根本不涉及 IV。请注意,指定模式和填充更加透明,例如:
...
Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
...
您使用的密钥材料 (
abcdefghijklmnop
) 大小为 128 位(16 字节)。 DES仅应用64位密钥(其中仅使用56位,其余8位是奇偶校验位)。 Java 实现仅使用密钥材料的前 8 个字节,其余部分将被忽略。因此,Java 代码相当于:
...
secretCredentialsBytes = Arrays.copyOf(secretCredentialsBytes, 8);
...
此外,应在Java代码中指定
String
/byte[]
转换的编码,否则将使用与平台相关的默认编码。关于您的测试数据,这很可能是适合您环境的 UTF-8。等效,但更透明:
...
byte[] secretCredentialsBytes = secretCredentials.getBytes(StandardCharsets.UTF_8);
...
String encryptedText = new String(encryptedTextBytes, StandardCharsets.UTF_8);
...
NodeJS 端口的主要问题似乎是您的 NodeJS 环境不支持已弃用的 DES。然而,与 DES 相比,根据您的测试,支持三重 DES。
TripleDES 是 DES 的三次连续执行(作为加密/解密/加密)以提高安全性(但性能相应下降),使用至少两个不同的 DES 密钥 (2TDEA),或者更安全地使用三个不同的 DES 密钥( 3TDEA),s。 键控选项了解更多详细信息。
如果使用相同 DES 密钥而不是不同的 DES 密钥,则三重 DES 会简化为 DES。这样,可以用 Triple DES 来模仿 DES。对于 2TDEA 变体,8 字节 DES 密钥必须与其自身连接成 16 字节密钥(DES 密钥 | DES 密钥):
const encrypted = 'AIGBXYEWXz5w2Z3Fjqe5YiQbyR5eVbPW';
const stringKey = 'abcdefghijklmnop';
let keyAsBuffer = Buffer.from(stringKey, 'utf-8').subarray(0,8);
keyAsBuffer = Buffer.concat([keyAsBuffer, keyAsBuffer]); // DES key | DES key
const strategy = 'des-ede-ecb'; // Triple DES in 2TDEA variant, ECB mode
const decipher = crypto.createDecipheriv(strategy, keyAsBuffer, null);
let decrypted = decipher.update(encrypted, 'base64', 'utf-8');
decrypted += decipher.final('utf-8');
console.log(decrypted); // Text to encrypt!
请注意,在指定算法时也可以使用
des-ede
,因为这是 des-ede-ecb
的别名,尽管模式的显式指定更加透明(注意:对于 DES,des
是 des-cbc
的别名)
)。有关说明符的概述,请参见例如openssl-enc,秒。 支持的密码。des-ede3-ecb
(或 des-ede3
)指定。在这种情况下,密钥必须连接到 24 字节密钥(DES 密钥 | DES 密钥 | DES 密钥)。
安全:
正如您自己已经提到的:DES 已经过时(大约 20 年前已弃用)并且不安全。 3TDEA 变体中的三重 DES 更安全(现在也已弃用),但性能相对较差。当前对称加密的标准是 AES。
欧洲央行也缺乏安全感。具有 IV 的模式(例如 CBC)更安全,经过身份验证的加密(例如 GCM)更安全。
另一个漏洞是仅使用字符集编码从字符串(由于其熵通常较低)直接派生密钥。如果应用密码,则应使用密钥导出函数(例如 Argon2 或至少 PBKDF2)与随机盐结合来导出密钥。