nodejs实现chacha20加解密

430 阅读3分钟

如题,主要是做一个开发笔记。

设定和算法

本示例基于Nodejs和标准crypto模块。相关使用的函数和算法配置信息如下:

const { createCipheriv, createDecipheriv, randomBytes} = require("crypto");

// algo config
let ALGO = {
    Name        : "chacha20-poly1305",
    tagLength   : 16,
    ivLength    : 12
}

简单的说明一下:

  • 算法为chacha20-poly1305,是一个对称加密算法
  • 流式加密,密钥长度256,可以替换RC4
  • 带消息验证码(poly1305)安全程度更高
  • 和AES相比,软件计算性能更高
  • Nodejs内置支持(基于crypto模块)
  • iv的长度为12字节,tag为16,这是标准确定的
  • iv应当使用随机生成的信息

加密过程和函数

加密的相关代码

const encrypt = (data, key)=>{
    let 
    iv    = randomBytes(ALGO.ivLength),
    aData = randomBytes(ALGO.tagLength);

    // construct the cipher and set AAD
    const cipher = createCipheriv(ALGO.Name , key, iv, { authTagLength: ALGO.tagLength })
        .setAAD(aData);
        
    let ctext = Buffer.concat([
        cipher.update(data, 'utf-8'),
        cipher.final(),
        cipher.getAuthTag(),
        iv,aData]).toString("base64");

    return ctext;
}

简单说明:

  • aad数据理论上可以使用任意附加明文数据,这里简化使用一个定长的随机信息
  • chacha20使用256位的密钥
  • 使用算法名词、key、iv、add创建加密实例
  • 加密的结果,由加密密文、tag、iv和aad构成
  • 原始信息为一个多个部分组成的buffer,最终编码为base64

解密过程和函数

解密的相关代码和函数如下:


const decrypt = (edata, key)=>{
    let barray = Buffer.from(edata,"base64");

    let adata2 = barray.subarray(-ALGO.tagLength);
    let iv2    = barray.subarray(-(ALGO.ivLength+ALGO.tagLength), -ALGO.tagLength);
    let tag2   = barray.subarray(-(ALGO.ivLength+2*ALGO.tagLength),-(ALGO.ivLength+ALGO.tagLength));
    barray = barray.subarray(0,-(ALGO.ivLength+2*ALGO.tagLength));
    
    try {
        // create decipher
        let decipher = createDecipheriv(ALGO.Name, key, iv2, { authTagLength: ALGO.tagLength })
            .setAuthTag(tag2)
            .setAAD(adata2);
    
        let otext = decipher.update(barray).toString("utf-8");
        decipher.final();
        
        return otext; 
    } catch (err) {
        console.error(err.message);
    }
    return null;
}

简单说明:

  • 参数为密文和密钥
  • 密文先转换为字节数组,然后按照加密的设置和规则,拆分为iv、tag、adata、密文等组成部分
  • 基于iv、tag和adata创建解密实例
  • 使用解密实例进行解密
  • 解密结果输出为utf8字符串
  • 为了调用和使用方便,对相关操作和数据进行了简单的自定义封装

测试

相关测试代码如下:

const test = ()=>{

    // data to encrypto
    let ptext = "China中国";
    let ekey  = Buffer.alloc(32, 0x01); // 256bit key

    let eContent = encrypt(ptext, ekey);

    console.log("oText:", Buffer.from(ptext).byteLength, ptext);
    console.log("Encrypt:", Buffer.from(eContent,"base64").length, eContent);
    
    let otext = decrypt(eContent, ekey);
    
    console.log("Decrypt:", otext === ptext, otext);

}; test();

-- 运行参考结果
oText: 11 China中国
Encrypt: 55 0zz8Rcss7GCd0AxTRfQwe5CqdGHHh6cGko7BH9fpS/BbLDSD/7DeeCpfeTXeUfKACisP8mkchw==
Decrypt: true China中国

这里的要点如下:

  • 简单起见,加密内容是标准utf-8字符串
  • 密钥为一个固定的256位密钥,真正使用环境中,应当是一个外部加载或者协商的密钥
  • 加密和解密当然应当使用相同的密钥,实际环境中,应当有更安全的交换或者传输机制
  • 加密结果为标准Base64字符串
  • 由于有iv(12),tag(16),add(16),所以,除了加密信息之外,有固定长度44字节的附加信息。

完整代码

完整的可执行代码如下:

小结

本文简单记录了使用nodejs crypto模块实现的chacha20-poly1305算法的数据加解密操作过程。可以看到,整个流程和设置,基本上和AES加解密没有很大的差异,在nodejs中的实现和操作也非常简单方便,只需要注意有一些设置的要求。