飞鱼

恭喜你发现了一个菜鸡博主。

Node.js 对称加密算法 chacha20-poly1305、aes-256-gcm 的简单实现

飞鱼 2022年04月24日 08:34:13
// Node.js v16.14.2

const crypto = require('crypto')

// 查看支持的算法列表
let cryptoList = crypto.getCiphers()
cryptoList = cryptoList.filter(item => item.indexOf('chacha20') !== -1)
console.log(cryptoList)
// [ 'chacha20', 'chacha20-poly1305' ]

/*
设置一个随机函数
当然你也可以用
crypto.randomBytes(16).toString('hex')
来获取一个长度为 32 的 16 进制随机密码
*/
function getRandom(len) {
  let rnd = ''
  let L = Math.ceil(len / 6) + 1
  while (rnd.length < len) {
    let arr = crypto.webcrypto.getRandomValues(new Uint32Array(L))
    arr.forEach(item => rnd += item.toString(36))
  }
  return rnd.slice(0, len)
}

let key = getRandom(32)
let nonce = getRandom(12)
let aad = getRandom(16)
let iv = getRandom(16)

// 测试 chacha20-poly1305 加密
let 原始文本 = `我爱北京天安门`
let 加密文本 =
  crypto
    .createCipheriv('chacha20-poly1305', key, nonce, {authTagLength: 16})
    .setAAD(aad)
    .update(原始文本, 'utf8', 'hex')

let 解密文本 =
  crypto
    .createCipheriv('chacha20-poly1305', key, nonce, {authTagLength: 16})
    .setAAD(aad)
    .update(加密文本, 'hex', 'utf8')

console.log({加密文本, 解密文本})
/*
{
  '加密文本': '7811d6289d30d3d0f52da2f0dbe633c6acba88b533',
  '解密文本': '我爱北京天安门'
}
*/

// 测试 aes-256-gcm 加密
原始文本 = '天安门上太阳升'
加密文本 =
  crypto
    .createCipheriv('aes-256-gcm', key, iv)
    .update(原始文本, 'utf8', 'hex')
解密文本 =
  crypto
    .createDecipheriv('aes-256-gcm', key, iv)
    .update(加密文本, 'hex', 'utf8')

console.log({加密文本, 解密文本})
/*
{
  '加密文本': '3c061e07f7aae06bf7819c7c9f1df13d1980a405ba',
  '解密文本': '天安门上太阳升'
}
*/

写在最后:

为什么随机函数 getRandom() 里面 len 要除以 6?请看下面的代码:

for (let i = 0; i < 100; i++) {
  let rnd = ''
  let L = 10
  let arr = crypto.webcrypto.getRandomValues(new Uint32Array(L))
  arr.forEach(item => rnd += item.toString(36))
  console.log({len: rnd.length, L, times: rnd.length / L})
}
/*
{ len: 66, L: 10, times: 6.6 }
{ len: 64, L: 10, times: 6.4 }
{ len: 66, L: 10, times: 6.6 }
{ len: 63, L: 10, times: 6.3 }
{ len: 65, L: 10, times: 6.5 }
{ len: 64, L: 10, times: 6.4 }
{ len: 64, L: 10, times: 6.4 }
{ len: 58, L: 10, times: 5.8 }
{ len: 66, L: 10, times: 6.6 }
{ len: 64, L: 10, times: 6.4 }
{ len: 67, L: 10, times: 6.7 }
{ len: 66, L: 10, times: 6.6 }
....
*/

len 和 L 之间的倍率基本上在 5.5~7 之间波动。那么,如果想快速得到一个长度相对合理的随机值,L = len / 6 并向上取整,是比较合理的。
但是,仍然会出现 rnd.length < len 的情况,所以 L 在这个基础上再 +1 。这样,基本上 while 在第一次循环完成后就终止了。

© 2020 飞鱼的博客