背景:需要在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)
输出:
<Buffer d7 6d f8 e7 ae fc> 6
<Buffer d3 5d b7 e3 9e bb f3 dd 35 db 7e 39 eb bf 3d d3 5d b7 e3 9e bb f3 dd 35> 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)
<Buffer d7 6d f8 e7 ae fc> 6
<Buffer 31 32 33 34 35 36 37 38> 8
<Buffer d3 5d b7 e3 9e bb f3 dd 35 db 7e 39 eb bf 3d d3 5d b7 e3 9e bb f3 dd 35> 24
<Buffer 30 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 37 38 39 30 31 32 33 34 35 36 37 38 39 30 31> 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
网友评论