背景
项目需要与客户那边对接,其中有个数据需要用DES3加密,对方提供了java版本的加密算法代码
代码如下:
private static final String SENSITIVE_INFO_SECRET_KEY = "58cf16b25485a0116b85806bba9ca7e4";
private static final String KEY_ALGORITHM = "DESede";
private static String IV = "12345678";
private static final String CIPHER_ALGORITHM = "desede/CBC/PKCS5Padding";
public String encrypt(String params) {
try {
DESedeKeySpec spec = new DESedeKeySpec(SENSITIVE_INFO_SECRET_KEY.getBytes(StandardCharsets.UTF_8));
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, SecretKeyFactory.getInstance(KEY_ALGORITHM).generateSecret(spec), new IvParameterSpec(IV.getBytes()));
byte[] bOut = cipher.doFinal(params.getBytes(StandardCharsets.UTF_8));
return base64Utils.encodeToString(bOut);
} catch (Exception e) {
}
return "";
}
我们后台使用nodejs实现,直接在网上找到DES3算法实现的例子:
const crypto = require('crypto');
function base64(text) {
return Buffer.from(text, "base64");
};
function encode(text, secretKey) {
secretKey = base64(secretKey);
const cipher = crypto.createCipheriv('des-ede3-cbc', secretKey, Buffer.alloc(8));
const encrypted = cipher.update(text, 'utf8', 'base64');
return encrypted + cipher.final('base64');
};
但是这个实现中,IV参数使用的是默认值,我们需要改成客户设定的IV。
再参考官方文档中接口说明:
crypto.createCipheriv(algorithm, key, iv[, options])#
中英对照
版本历史
- algorithm ">
- key ">
| "> | "> | "> | "> | "> | "> - iv ">
| "> | "> | "> | "> | "> - options ">
- 返回: ">
使用给定的 algorithm、key 和初始化向量(iv)创建并返回 Cipher 对象。
options 参数控制流的行为,并且是可选的,除非使用 CCM 或 OCB 模式的加密(例如 'aes-128-ccm')。 在这种情况下,需要 authTagLength 选项并指定身份验证标签的长度(以字节为单位),请参阅 CCM 模式。 在 GCM 模式下,authTagLength 选项不是必需的,但可用于设置 getAuthTag() 将返回的身份验证标签的长度,默认为 16 字节。
algorithm 依赖于 OpenSSL,例如 'aes192' 等。 在 OpenSSL 的最新版本中,openssl list -cipher-algorithms(在 OpenSSL 的旧版本中为 openssl list-cipher-algorithms)将显示可用的加密算法。
key 是 algorithm 使用的原始密钥,iv 是初始化向量。 两个参数都必须是 'utf8' 编码的字符串、缓冲区、TypedArray 或 DataView。 key 可以是 secret 类型的 KeyObject。 如果加密不需要初始化向量,则 iv 可以是 null。
当为 key 或 iv 传入字符串时,请考虑到当使用字符串作为加密 API 输入时的注意事项。
初始化向量应该是不可预测的和独特的;理想情况下,它们将是加密随机的。 它们不必是机密的:IV 通常不加密就添加到密文消息中。 必须是不可预测的和独特的,但不必是机密的,这听起来可能有些矛盾。请记住,一定不能让攻击者提前预测到给定的 IV。
注意到: 当为 key 或 iv 传入字符串时,请考虑到当使用字符串作为加密 API 输入时的注意事项
因为客户提供的 iv 就是有效的UTF-8字符串,可以直接传入。
修改后的代码就是
const IV = '12345678';
const DES3_SECRET_KEY = '58cf16b25485a0116b85806bba9ca7e4';
const DES3_ALGORITHM = 'des-ede3-cbc';
function des3_encode(plaintext) {
try {
const secretKey = Buffer.from(DES3_SECRET_KEY, 'base64');
const cipher = crypto.createCipheriv('des-ede3-cbc', secretKey, IV);
const encrypted = cipher.update(plaintext, 'utf8', 'base64');
return encrypted + cipher.final('base64');
} catch (err) {
logger.log('crypto data failed:', err);
return null;
}
}
现在来计算一下加密结果:
java中对字符串加密后:ag1zFkiXRZo=
nodejs中对字符串加密后:kSDNTtAccvw=
竟然不一致。
其实在修改iv的过程中,我们也注意到key也是可以直接传入字符串的,改为直接用string
const cipher = crypto.createCipheriv('des-ede3-cbc', DES3_SECRET_KEY, IV);
加密一下竟然报错了,说是key的长度不对:
Error: Invalid key length
at new Cipheriv (crypto.js:219:16)
at Object.createCipheriv (crypto.js:619:10)
明明跟Java一样的key...
再来重新对比java代码和nodejs代码,可以看出在Ciper初始化之后的处理基本是一致的,都是对utf-8格式的明文加密,再用base64格式输出。
再看下Java中Ciper初始化过程:
DESedeKeySpec spec = new DESedeKeySpec(SENSITIVE_INFO_SECRET_KEY.getBytes(StandardCharsets.UTF_8));
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, SecretKeyFactory.getInstance(KEY_ALGORITHM).generateSecret(spec), new IvParameterSpec(IV.getBytes()));
注意到Java中初始化Ciper时,Key是用的DESedeKeySpec来构造的,点击new DESedeKeySpec 处的跳链进入源码查看构造过程:
public class DESedeKeySpec implements KeySpec {
public static final int DES_EDE_KEY_LEN = 24;
private byte[] key;
public DESedeKeySpec(byte[] key) throws InvalidKeyException {
this(key, 0);
}
public DESedeKeySpec(byte[] key, int offset) throws InvalidKeyException {
if (key.length - offset < 24) {
throw new InvalidKeyException("Wrong key size");
} else {
this.key = new byte[24];
System.arraycopy(key, offset, this.key, 0, 24);
}
}
public byte[] getKey() {
return (byte[])this.key.clone();
}
可以看到key的长度超过24位时,会截取前24位,并不是直接使用传入的值。
实际传入的key长度为32位。
尝试将nodejs中的key直接修改为前24位,重新加密计算后:
ag1zFkiXRZo=
与JAVA计算结果一致。



