nodejs平台中实现DESede/CBC/PKCS5Padding加密算法爬坑之旅

背景:需要在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

你可能感兴趣的:(nodejs平台中实现DESede/CBC/PKCS5Padding加密算法爬坑之旅)