背景:需要在node服务中调用其他平台的接口,对方的平台使用了des3-cbc加密校验,设置了32位的字符串key,和8位的字符串secret。对方平台使用的php语言,也有java平台的项目跟该平台对接,所以有php和java的实现方法可以参考。
之前没有用node写过类似加密的功能,所以一开始在网上找了一个相对比较完整的例子想来跑下效果。
例子中这么一段(好多文章都会这么设置)
function base64(text) {
let res = Buffer.from(text, 'base64')
return res;
};
把key和secret转换成二进制流,编码格式是base64,这是一开始就跳进去的第一个坑,到最后才跳出来。
Buffer有两个常用方法,一个是Buffer.from,一个是Buffer.alloc,两个都可以设置编码格式,区别是后者可以设置长度。
如果不设置编码格式,默认utf-8。
运行demo的时候报了一个错:Invalid IV length,
搜查国内外的帖子查找原因,说是需要一个8位的二进制流。大多数的实现是crypto.randomBytes(8),生成一个随机的8位二进制流,或者为空,没有指定8位字符串的。
也找到过一些文章不设置buffer编码格式,但是去掉Buffer.from(text, 'base64')中的base64时会报Invalid Key length错误,继续网上找错误原因,说key的二进制流是有长度限制的,支持24位。
后来把base64这个编码设置又恢复了回去,心想先解决iv长度问题再来看key的长度问题。
把iv设置成一个随机的8位二进制流,不指定字符串的时候加密除的结果不对。
测试过程中我是用一个在线的加密网站,配置好加密模式、填充、key和iv,先用简单的字符串来测试,测试成功以后再把真实请求参数填充进去,来跟其他平台(php)的加密结果来做对比。(在线加密地址:http://tool.chacuo.net/crypt3des)
然后研究了一个Buffer的用法,输出二进制流。
let buff1 = Buffer.from('12345678', 'base64');
let buff2 = Buffer.from('01234567890123456789012345678901', 'base64');
console.log(buff1, buff1.length)
console.log(buff2, buff2.length)
输出:
6
24
iv的二进制流不符合长度,8位的才行。
继续参考各种帖子,以及查看php、java的加密写法,更改在线加密网站的各种配置项。
然后想到一个有效的办法,放弃设置的key和iv字符串,找到符合长度条件的二进制流,也是这一步才找到了从第一个demo中的base64编码格式的坑里爬出来的梯子。参考网上另一个帖子,保持二进制参数一样,结果做对比,发现一样的!
这说明加密的方法没有问题,只是参数不对,再返过来研究二进制流的转化。
let buff1 = Buffer.from('12345678', 'base64');
let buff2 = Buffer.from('12345678')
let buff3 = Buffer.from('01234567890123456789012345678901', 'base64');
let buff4 = Buffer.from('01234567890123456789012345678901');
console.log(buff1, buff1.length)
console.log(buff2, buff2.length)
console.log(buff3, buff3.length)
console.log(buff4, buff4.length)
6
8
24
32
通过去掉编码设置的对比中发现,两种加密的内容和长度都不一样,这才去掉base64编码设置去掉。
对于32位字符串转化成24位二进制的解决办法是通过Buffer.alloc(24),先设置二进制长度,这样转化后的二进制只会取前24位。长度符合了,结果会不会一样呢,报着试一试的态度运行一下,发现一样的!
就这样,完成上岸。
还有一个点,就是设置填充,默认自动填充为true,对应的pkcs5padding;如果设置为false,cipher.setAutoPadding(false),对应的no padding。
填充为no padding的时候,对加密的数据有长度限制(报错信息:data not multiple of block length),经测试为8的倍数长度的字符串可加密(有文章说key的长度倍数)。
完整代码如下:
const crypto = require('crypto');
function buf(text) {
return Buffer.from(text)
};
function buf24(text) {
let buf = Buffer.alloc(24);
buf.write(text, 0)
return buf;
};
function encode(text, key, secret) {
key = buf24(key);
secret = buf(secret);
const cipher = crypto.createCipheriv('des-ede3-cbc', key, secret);
// cipher.setAutoPadding(false);
const encrypted = cipher.update(text, 'utf8', 'base64');
return encrypted + cipher.final('base64');
};
function decode(encryptedBase64, key, secret) {
key = buf24(key);
secret = buf(secret);
const decipher = crypto.createDecipheriv('des-ede3-cbc', key, secret);
let decrypted = decipher.update(encryptedBase64, 'base64', 'utf8');
decrypted += decipher.final('utf8');
return JSON.parse(decrypted);
};
module.exports = {
encode,
decode
}
php部分代码:
/**
* mcrypt 加密方法
* */
function encrypt($input){
$size = mcrypt_get_block_size(MCRYPT_3DES,MCRYPT_MODE_CBC);
$input = $this->pkcs5_pad($input, $size);
$key = str_pad($this->key,24,'0');
$td = mcrypt_module_open(MCRYPT_3DES, '', MCRYPT_MODE_CBC, '');
if( $this->iv == '' ) {
$iv = @mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
} else {
$iv = $this->iv;
}
@mcrypt_generic_init($td, $key, $iv);
$data = mcrypt_generic($td, $input);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
$data = base64_encode($data);
return $data;
}
/**
* mcrypt 解密方法
* */
function decrypt($encrypted){
$encrypted = base64_decode($encrypted);
$key = str_pad($this->key,24,'0');
$td = mcrypt_module_open(MCRYPT_3DES,'',MCRYPT_MODE_CBC,'');
if( $this->iv == '' ) {
$iv = @mcrypt_create_iv (mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
} else {
$iv = $this->iv;
}
@mcrypt_generic_init($td, $key, $iv);
$decrypted = mdecrypt_generic($td, $encrypted);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
$y=$this->pkcs5_unpad($decrypted);
return $y;
}
java部分代码:
/**
* 加密用到的IV值,有提供,比如12345678
* @return
*/
public static byte[] getIVBytes() {
return iv.getBytes();
}
//加密方式和加密模式定义
private static final String MCRYPT_TRIPLEDES = "DESede";
private static final String TRANSFORMATION = "DESede/CBC/PKCS5Padding";
/**
* 解密函数
* @param data 加密字符串
* @return 解密后的字符串
*/
public static String decrypt(String data) {
if (data == null)
return null;
String result = null;
try {
DESedeKeySpec spec = new DESedeKeySpec(getSecretKey());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(MCRYPT_TRIPLEDES);
SecretKey sec = keyFactory.generateSecret(spec);
IvParameterSpec IvParameters = new IvParameterSpec(getIVBytes());
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, sec, IvParameters);
result = new String(cipher.doFinal(Base64.decode(data)), "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 加密函数
* @param data 加密前的字符串
* @return 加密后的字符串
*/
public static String encrypt(String data) {
if (data == null)
return null;
String result = null;
try {
DESedeKeySpec spec = new DESedeKeySpec(getSecretKey());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(MCRYPT_TRIPLEDES);
SecretKey sec = keyFactory.generateSecret(spec);
IvParameterSpec IvParameters = new IvParameterSpec(getIVBytes());
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, sec, IvParameters);
result = Base64.encode(cipher.doFinal(data.getBytes("UTF-8")));
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
参考文章:
http://www.manongjc.com/detail/19-ebjchzdgyzgpxwl.html
https://stackoverflow.com/questions/46353150/tripledes-cbc-nodejs-implementation-throuble
https://blog.csdn.net/ErErFei/article/details/73558226
https://yijiebuyi.com/blog/13e2ae33082ac12ba4946b033be04bb5.html