Bun技术评估 - 08 Crypto

概述

本文是笔者的系列博文 《Bun技术评估》 中的第八篇。

本文主要探讨的内容,是研究在bun的体系中,如何处理密码学相关的操作和内容。在信息安全越来越重要的情况下,这些方面的内容和支持,对于一个应用开发系统,也是非常重要的。而且,我们讨论的是一个全功能的密码学框架,而不是某个特定的功能或者算法。

在这方面,nodejs中的crypto模块,其实已经做得非常好了,无论在功能的完善、安全性、算法的丰富性、易用性和性能方面,都是非常成熟的。笔者原来也以为,bun可以直接应用和继承nodejs crypto的完整的生态,但在实际应用中,却发现事情好像没有那么简单。不知道是什么原因的限制,虽然bun crypto在架构上确实应用了nodejs的实现,但在一些细节上,却略有差异,而且在某些情况下,这个差异还不小,影响到应用开发的程度。这就是笔者对于bun crypto的初步印象,我们会在后面结合一些实例,展开讨论。

本文主要探讨的内容,同样主要来自bun官方技术文档的相关篇幅。比较奇怪的是,笔者觉得bun对于密码学模块的规划,并没有nodejs那么重视和完善,它甚至没有专门的模块和文档板块,而是分散在功能和函数的层面。就是笔者可以感受到,关于密码学操作,主要分为两个大的方面,第一是 bun本身提供和实现了一些密码学方法;其次,bun可以通过crypto模块,来实现更多的密码学操作。

内置密码学函数

Bun在bun应用程序中,是一个全局的静态类。它本身就包括一些常见的密码学操作实现。

Hash

js 复制代码
Bun.hash("some data here", 1234);
// 15724820720172937558n

Bun.hash.wyhash("data", 1234); // equivalent to Bun.hash()
Bun.hash.crc32("data", 1234);
Bun.hash.adler32("data", 1234);
Bun.hash.cityHash32("data", 1234);
Bun.hash.cityHash64("data", 1234);
Bun.hash.xxHash32("data", 1234);
Bun.hash.xxHash64("data", 1234);
Bun.hash.xxHash3("data", 1234);
Bun.hash.murmur32v3("data", 1234);
Bun.hash.murmur32v2("data", 1234);
Bun.hash.murmur64v2("data", 1234);
Bun.hash.rapidhash("data", 1234);

这里的hash,确实是摘要算法,但不是严格密码学意义上的标准完整摘要。但它有个好处,就是效率和性能要高于标准的SHA等算法,特别适合于内部使用,因为在很多情况下,我们其实是不需要全功能的摘要方法的(比如完整性检查、临时签名等等)。

Bun.hash默认使用wyhash算法,当然从示例中,我们可以看到还有很多可选的实现。这类hash算法,都是可以处理一个字符串或者buffer,返回的结果,就是一个简单的32位或者64位的整数。一般执行时,还可以同时提供一个"种子"(整数),来混淆计算结果。如果把这个种子看成简单的密钥,那么这些hash算法就变成了简单的HMAC算法。

当然,标准的密码学hash算法,结果也是一个整数,但它们都是大整数,空间远超标准的32位或者64位整数。下面就是一个简单的示例:

js 复制代码
> require("crypto").createHash("SHA256").update("data").digest("hex")
'3a6eb0790f39ac87c94f3856b2dd2c5d110e6811602261a9a923d3bb23adc8b7'

> Bun.hash("data", 12345678).toString(16);
'551d3d5d03460bfa'

CryptoHasher

标准密码学摘要算法,在bun中,被称为CryptoHasher(密码学摘要)。它几乎完整的支持你所能见到的主流摘要算法你包括MD5、各种SHA、blake、shake等等,它的使用方式也和nodejs crypto类似,下面是一个简单的示例:

js 复制代码
> new Bun.CryptoHasher("sha256").update("data").digest("hex")
'3a6eb0790f39ac87c94f3856b2dd2c5d110e6811602261a9a923d3bb23adc8b7'

HMAC的实现:

js 复制代码
const hasher = new Bun.CryptoHasher("sha256", "secret-key");
hasher.update("hello world");
console.log(hasher.digest("hex"));
// => "095d5a21fe6d0646db223fdf3de6436bb8dfb2fab0b51677ecf6441fcf5f2a67"

bun的cryptoHasher有一个有趣的特性,就是它支持实例复制,这样就不需要为了多个摘要计算,创建多个实例了(nodejs是这么操作的)。

js 复制代码
const hasher = new Bun.CryptoHasher("sha256", "secret-key");
hasher.update("hello world");

const copy = hasher.copy();
copy.update("!");
console.log(copy.digest("hex"));
// => "3840176c3d8923f59ac402b7550404b28ab11cb0ef1fa199130a5c37864b5497"

console.log(hasher.digest("hex"));
// => "095d5a21fe6d0646db223fdf3de6436bb8dfb2fab0b51677ecf6441fcf5f2a67"

new Bun.CryptoHasher("sha256","secret-key").update("hello world!").digest("hex")
'3840176c3d8923f59ac402b7550404b28ab11cb0ef1fa199130a5c37864b5497'

这个示例中,要注意copy的时机,copy.update(),已经是在hello world的基础之上,又新增加了内容,而非重新摘要。

Password

Bun提供了一个基于argon2和bcrypt算法的密码编码和验证的算法。可以用于密码编码,存储和验证。

js 复制代码
const password = "super-secure-pa$$word";

const hash = await Bun.password.hash(password);
// => $argon2id$v=19$m=65536,t=2,p=1$tFq+9AVr1bfPxQdh6E8DQRhEXg/M/SqYCNu6gVdRRNs$GzJ8PuBi+K+BVojzPfS5mjnC8OpLGtv8KJqF99eP6a4

const isMatch = await Bun.password.verify(password, hash);
// => true

// 算法可选bcrypt
await Bun.password.hash(pwd, {  algorithm: "bcrypt" });
 

这个密码操作实现的想法很好(有salt生成和算法参数设置),但在实际应用环境中,例如标准的Web登录验证的过程,可能会遇到浏览器没有内置argon2算法支持的问题。笔者现在尚未找到和理解很好的应用这个模式的场景。

Crypto模块

前一个章节的内容,是Bun类直接内置的快速密码学操作函数,并不是完整的密码学套件。而这方面的内容,是通过另外一个crypto模块提供的,但是要注意的是,虽然它也叫crypto,但并不是直接继承和使用的nodejs的crypto,笔者感觉是基本上是重新用zig实现了一遍,当然它会尽力提供和nodejs兼容的接口和使用方式。所以,从开发者来看,这两者的开发使用是差异不大的。

按照密码学操作的框架,这里我们简单的检查一下,在bun中是如何操作和实现的,这里的内容较多,笔者只是例举一个框架和要点:

  • 引用

bun使用crypto或者node:crypto来引用模块提供的方法。

js 复制代码
const { 
    createHmac, createCipheriv, createDecipheriv, randomBytes , getCurves,
} = require("node:crypto");
  • 编码和转换

bun支持标准的nodejs buffer和TextEncoder/Decoder等编码转换方式,这一点基本继承了nodejs相关的功能和特性。

  • 随机信息

bun crypto提供的随机函数包括randomBytes,randomFill,randomint,randomUUID等等。可以用于生成各种随机信息。

  • HASH/HMAC

除了再前面提到过的Bun类型提供的hash方法之外,也支持crypto相关的hash和hmac处理,使用方法和nodejs无异。

  • 密钥衍生

bun crypto提供了scrypto,pbkdf2、hkdf等标准密钥衍生算法。这些算法基本上是比较常用和标准的。

  • 对称加密解密

bun实现了标准的createCipherIV和createDecipher等加密器创建方法和使用方法。支持大部分常用的aes算法和参数(其实根本用不了那么多)。

  • 非对称加密

bun crypte支持非对称加密的generateKeyPair,createPrivateKey,createPublickKey等方法,可以用于生成和处理各种密钥。密钥对象还支持各种格式的导入导出操作。

基于生成的密钥,提供了privateEncrypt/privateDecrypt,publicEncrypt/publicDecrypt等私钥公钥加解密方法。

  • 签名和验证

bun中可以使用nodejs系统中标准的sign和verify方法。它们使用了类似的公钥和私钥对象。

  • 密钥协商

bun提供了标准ECDH密钥协商机制和方式。

  • OTP

bun没有内置OTP的实现,要使用OTP,需要开发者自行实现。

  • 其他

bun提供了类似的timeSafeEqual函数。

  • 建议的算法集

如果使用bun进行开发,笔者建议的密码学算法和参数组合是:

BASE64URL-WYHASH-SHA256-AES-256-GCM-SECP384R1-ECDH-PBKDF2

当然笔者理想中的算法和参数组合是:

BASE63URL-WYHASH-SHA256-CHACHA20-POLY1305-SECP384R1-ECDH-PBKDF2

性能

从笔者的评估经验来看,虽然表面上,bun继承实现了nodejs的crypto模块,但可能是由于内部的执行机制还是略有差异的,体现在性能测试上,结果差别还是比较大的。

下面是一个简单的测试过程,包括了hmac和aes加解密的操作,为了方便简单,甚至它们可以共享一套相同的代码:

js 复制代码
const { 
    createHmac, createCipheriv, createDecipheriv, randomBytes 
} = require("crypto");

const start = async()=>{
    let key = randomBytes(32);
    let t,c,d, tag, text, i = 100000;

    console.time("CRYPTO");
    
    while(i--) {
        text = Math.random().toString(36).slice(2,10);
        text = createHmac("SHA256",key).update(text).digest();

        // encrypt 
        c = createCipheriv("aes-256-gcm",key,key.subarray(-8));
        t = Buffer.concat([c.update(text), c.final()]);
        tag = c.getAuthTag();

        // decrypt
        d = createDecipheriv("aes-256-gcm",key,key.subarray(-8));
        d.setAuthTag(tag);

        text = d.update(t).toString();
    }

    console.timeEnd("CRYPTO");

}; start();

// nodejs执行
node c2.js
CRYPTO: 1.825s

// bun 执行
bun c2.js 
[869.76ms] CRYPTO

虽然测试体现了bun相对于nodejs而言在密码学操作方面的优势,但也应该看到,两者的密码学计算和操作的性能都非常高,所以如果业务系统对密码学操作的性能要求不是非常看重,这并不是一个很大的问题。

问题和不足

在笔者的评估和应用过程中,发现了一些问题和不足:

  • 椭圆曲线的种类和数量
js 复制代码
bun repl 
> require("crypto").getCurves();
[ 'secp224r1', 'prime256v1', 'secp384r1', 'secp521r1' ]

node 
> require("crypto").getCurves();
[
  'Oakley-EC2N-3',
  'Oakley-EC2N-4',
  'SM2',
  'brainpoolP160r1',
  'brainpoolP160t1',
  'brainpoolP192r1',
  'brainpoolP192t1',
  'brainpoolP224r1',
  'brainpoolP224t1',
  'brainpoolP256r1',
  'brainpoolP256t1',
  'brainpoolP320r1',
  'brainpoolP320t1',
  'brainpoolP384r1',
  'brainpoolP384t1',
  'brainpoolP512r1',
  'brainpoolP512t1',
  'c2pnb163v1',
  'c2pnb163v2',
  'c2pnb163v3',
  'c2pnb176v1',
  'c2pnb208w1',
  'c2pnb272w1',
  ...

在现阶段,bun只支持区区4种椭圆曲线。虽然大多数ECC可能开发者都不会用到,这四种其实也是够用的,但有两个可能缺失的选择,包括SM2(国密算法)和secp256k1(区块链标准曲线)也是挺遗憾的。希望bun社区能够快速的弥补修复这个问题。

  • 不支持chacha20-poly1305

这其实也是一个不小的问题。因为其实chacha20-poly1305也是一个应用非常广泛的数据流加密算法。这个问题笔者也不是特别理解。现在的弥补方案是引入其js实现。

  • 其他不支持的算法

bun不支持的其他算法包括AES-ECB,DES/3DES,RC4等,但这些都是由于安全性的原因弃用的算法,这不是一个很大的问题。

  • ECC密钥处理的兼容性

笔者在使用bun进行ECC加密的过程中,遇到了一个这个问题。在nodejs环境中,可以基于一个保存的私钥,恢复密钥对象。但相同的操作,在bun中是不能完成的。但后面在导入过程中,先计算私钥对应的公钥,补足后,也是可以恢复的,就是不如在nodejs环境中方便而已。这是一个兼容性的问题,但可以解决。

小结

本文评估和探讨了bun对于密码学操作的支持和功能,包括了bun内置的密码学方法包括hash/hmac,和通过crypto模块实现的密码学操作。简单例举了其配置和使用方式,支持的主要算法和不支持的部分。并简单的对比bun crypto和nodejs实现之间的性能差异。

相关推荐
啪叽3 分钟前
探索鲜为人知的浏览器API:document.currentScript的实用案例
前端·javascript·dom
我是谁谁5 分钟前
Vue3 组合式 API 核心宏详解:defineProps、defineEmits、defineExpose
javascript·vue.js
多客潇潇和多客逸逸是好朋友6 分钟前
如何在代码里面更改数据库密码?
后端
该用户为高级用户9 分钟前
从零构建高性能Web服务器:基于Nginx与Lua的实践指南
后端
iOS开发上架哦22 分钟前
开发者视角的网络调试流程进化:抓包工具实践指南与Sniffmaster使用笔记
后端
陈随易29 分钟前
VSCode v1.101发布,MCP极大增强关联万物,基于VSCode的操作系统雏形已初见端倪
前端·后端·程序员
tonydf30 分钟前
浅尝一下微软的AutoGen框架
人工智能·后端
然我35 分钟前
从 Callback 地狱到 Promise:手撕 JavaScript 异步编程核心
前端·javascript·html
hello早上好37 分钟前
Spring Bean生命周期
java·后端
sg_knight41 分钟前
Rollup vs Webpack 深度对比:前端构建工具终极指南
前端·javascript·webpack·node.js·vue·rollup·vite