尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Deno Web Crypto 源码深挖:纯 Rust 实现 WebCrypto 背后的 3 个设计亮点

发布时间:2026/9/8 23:19:52

资讯中心
01
ARTICLE

Deno Web Crypto 源码深挖:纯 Rust 实现 WebCrypto 背后的 3 个设计亮点

Deno Web Crypto 源码深挖:纯 Rust 实现 WebCrypto 背后的 3 个设计亮点
Deno Web Crypto 源码深挖纯 Rust 实现 WebCrypto 背后的 3 个设计亮点【免费下载链接】denoA modern runtime for JavaScript and TypeScript.项目地址: https://gitcode.com/GitHub_Trending/de/deno浏览器里敲下crypto.subtle.digest(SHA-256, data)这一串字节究竟落在哪一行 Rust这篇拆解 Deno Web Crypto 的纯 Rust 实现把每个 subtle 方法从 JS 全局到算法内核的调用链摊开讲。读完你能独立追踪任意一次 WebCrypto 调用落到ext/crypto/下对应的 Rust 文件与函数。这个模块在解决什么问题一个 WebCrypto API 要覆盖哈希、对称/非对称加解密、密钥派生与后量子算法几十种算法各自的边界条件都要精确到字节。Deno 把这件事压进单一 crate定位写得很直白ext/crypto/README.md 开篇只有一句This crate implements the Web Cryptography API.并在 ext/crypto/Cargo.toml 里用依赖声明圈出能力边界。类别关键 crate见 ext/crypto/Cargo.toml覆盖的算法面哈希sha1/sha2/sha3/tiny-keccakSHA-1、SHA-2、SHA3 族tiny-keccak开k12/kmac供 KangarooTwelve 系 XOF非对称rsa/p256/p384/p521/ecdsa/signature/spki/x25519-dalekRSA 签名加密、椭圆曲线签名与 ECDH、X25519/X448 交换对称aes/aes-gcm/aes-kw/cbc/ctr/ocb3/hmac分组密码各工作模式与消息认证码派生 / 后量子aws-lc-rs/argon2/fips203/fips205HKDF、PBKDF2、argon2fips203管 ML-KEMfips205管 ML-DSA这张表本身就是一张路线图你想知道某个算法由哪段 Rust 承担先看它属于哪一类再进对应模块。能力面与测试一一对应tests/unit/webcrypto_test.ts 和 tests/unit/webcrypto_mldsa_test.ts 分别守住传统算法与后量子算法的边界。一次调用如何跑通从 globalThis.crypto 到 Rust 侧一次subtle.digest要穿越两个世界JS 侧得先把扩展脚本拉起来挂到全局Rust 侧得把类和参数注册进运行时。JS 侧最小动作是加载脚本并绑定全局Deno 本体由 runtime/js/98_global_scope_shared.js 在启动时执行const crypto core.loadExtScript(ext:deno_crypto/00_crypto.js); Object.defineProperty(globalThis, crypto, { value: crypto.crypto, writable: false });脚本导出的crypto是个 getter第一次读取才铸造单例。真正把类、参数、状态接进运行时的是 ext/crypto/lib.rs 里的扩展声明它同时交代了本 crate 的接线方式deno_core::extension!(deno_crypto, deps [ deno_webidl, deno_web ], ops [ crypto::op_crypto_random_uuid_batch, op_crypto_is_seeded ], objects [ crypto::Crypto, subtle_crypto::SubtleCrypto, crypto_key::CryptoKey ], lazy_loaded_js [ 00_crypto.js ], options { maybe_seed: Optionu64, }, state |state, options| { if let Some(seed) options.maybe_seed { state.put(StdRng::seed_from_u64(seed)); } }, );deps只列两个 cratedeno_webidl提供 WebIDL 类型转换deno_web提供 console/inspect 支撑这正是 README Dependencies 一节的全部。objects是主体。Crypto、SubtleCrypto、CryptoKey三个类以 cppgc 包裹对象注册cppgc 是 V8 的增量垃圾收集器让 Rust 结构体活在 JS 堆里被自动回收于是subtle的每个方法都直接落在这些类的方法体上算法逻辑再由 ext/crypto/subtle_crypto.rs 分派到各纯 Rust 模块。ops只剩两个是历史沉淀op_crypto_is_seeded查一次状态、op_crypto_random_uuid_batch补一批 UUID二者都被 ext/crypto/lib.rs 顶部注释点名为铸造单例时选一次路径与补给普通路径的批量缓存。绝大多数 WebCrypto 入口因此不再走 op而是走objects里类的方法这是把每个 op 配一个 JS 函数的旧结构拆薄后的结果。设计亮点一密钥为什么不再路过 JS 密钥字节该放哪决定了每次加密要付多少搬运费。把它类比成保险箱旧方案是每次操作都把整把钥匙从 JS 打包寄到 Rust、用完再寄回新方案是钥匙一直锁在 Rust 手里的保险箱JS 只攥着一张取号单。ext/crypto/key_store.rs 的CryptoKeyHandle就是这张取号单它是个 V8 GC 对象pub struct CryptoKeyHandle { data: RawKeyData }文档注释把演进讲得很直白早期密钥存在 ext/crypto/00_crypto.js 的 JSWeakMapKEY_STORE里每次操作都要序列化跨过 JS/Rust 边界现在字节驻留在 Rust 的 cppgc 对象中JS 只持有 handle 并把它传给 op。因为 handle 本身是 GC 对象CryptoKey一旦回收密钥字节随同释放不需要FinalizationRegistry之类的额外簿记。ext/crypto/lib.rs 的KeyData注释补了一句时间线Previously the key bytes were serialized and passed from JavaScript on every operation.真正装字节的是 ext/crypto/shared.rs 的RawKeyData枚举五个变体各带一种用途标签Secret/Private/Public带用途标签的字节如 HMAC 密钥、PKCS8 私钥、SPKI 公钥Raw原样存储、不带标签的字节典型是 Ed25519/X25519/X448/ML-KEM 的公钥SeededPrivate { seed, private_key }FIPS 203/204 算法ML-KEM 解封装、ML-DSA 签名的复合素材private_key是展开后的字节、seed是派生用的短种子。这里有个容易被忽略的细节seed可为None即从展开后的裸私钥字节导入时不带种子此时导出raw-seed/jwk/pkcs8格式会被正确拒绝——标签不只是分类还参与后续导出的合法性判断。密钥材料的驻留点因此从 JS 弱引用表搬进了 Rust 的 GC 对象每次操作只传一个 handle。设计亮点二同一个 randomUUID两条路径为什么同一个方法会写出两套实现答案是随机数要不要可复现。种子机制可以类比成给随机数发生器上一个固定发条上同一发条每次转出同样的序列便于测试不上发条则从操作系统熵源取随机。发条从哪来就是 ext/crypto/lib.rs 里那个Optionu64——options.maybe_seed有值时state闭包把StdRng::seed_from_u64(seed)放进OpStateOpState是运行时为每个 isolate 维护的可变状态袋随机源从此变成确定性StdRng没有种子则落到线程级熵源。Rust 侧提供两个开关。ext/crypto/lib.rs 的op_crypto_is_seeded只是state.try_borrow::StdRng().is_some()op_crypto_random_uuid_batch则一次填 128 条完整 UUID 串。JS 侧在 ext/crypto/00_crypto.js 的getCryptoSingleton里铸造单例时记下usesSeededRng op_crypto_is_seeded()之后的randomUUID据此二选一function randomUUID() { if (this ! cryptoSingleton || usesSeededRng) { return FunctionPrototypeCall(cppgcRandomUUID, this); } if (uuidBatch UUID_BATCH_SIZE) { uuidBatchData op_crypto_random_uuid_batch(); uuidBatch 0; } const start uuidBatch * UUID_STRING_BYTES; return StringPrototypeSlice(uuidBatchData, start, start UUID_STRING_BYTES); }走了种子或调用者不是单例时直接进原生cppgcRandomUUID严格保留 RNG 的调用顺序保证可复现普通路径按 128 条一批取回命中缓存后只在 JS 里按 36 字节切片省掉逐条跨边界。批量路径背后是 ext/crypto/lib.rs 的fast_uuid_v4_bytes先就地改 16 字节的版本位与变体位bytes[6] (bytes[6] 0x0f) | 0x40再用HEX_CHARS查表拼出 36 字节串绕过格式化开销同名测试test_fast_uuid_v4_correctness拿uuidcrate 对拍确保这条手写的快路径与标准库逐字节一致。种子的注入点因此在 Rust 状态、JS 行为分派、批量与确定性两条取号路径之间形成了一条完整的传导链。兼容性工程被测试钉死的错误边界怎么保证抛出的恰好是 WPT 期望的那个错误而不是差不多的报错WebCrypto 的契约精确到 DOMException 名称ext/crypto/lib.rs 的CryptoError用#[class(...)]把每个变体绑到具体异常类。三个案例最能说明这种被测试钉死的工程。先解析、后抛错。一个未知的算法名如果在参数转换阶段就抛TypeError就偏离了规范。ext/crypto/digest.rs 的做法是把未登记的名字原样保留成DigestAlgorithm::Unknown(name)推迟到run()再抛规范要求的NotSupportedError——因为 WPT 的 digest 子测试硬编码了这个错误名参数形状对了、错误名错了同样会挂。错误名到 DOMException 的映射。同一张CryptoError表里UnsupportedDigestAlgorithm绑DOMExceptionNotSupportedError、消息带算法名DecryptionError绑DOMExceptionOperationErrorAEAD 认证标签失败的措辞ArrayBufferViewLengthExceeded绑DOMExceptionQuotaExceededError把 65536 字节熵上限写进消息HKDFLengthTooLarge同样落到OperationError。每一个名称都不是随意取的而是 WPT 逐条对出来的。XOF 参数的回绕陷阱。无限长度算法cSHAKE、TurboSHAKE、KangarooTwelve的参数字典里domainSeparation只接受[0x01, 0x7F]。ext/crypto/digest.rs 的read_optional_u8会先按完整u32读入遇到u u8::MAX直接报TypeError目的是不让0x101截断回绕成0x01而溜过调用方的范围检查。这种先读全值再拒越界的写法是算法从 JS 迁到 Rust 时被逐条保留下来的边界防护。参数校验、错误命名、范围检查三处都指向同一个工程原则正确性不是靠实现自觉而是靠外部测试把边界钉死在代码里。演进痕迹README 与现码不一致的三处README 记录的是较早的接线形态与当前代码有三处偏差。引用 README 时请先对照代码下表把差异摊开位置README 的说法代码现状初始化入口提供deno_crypto::init(Optionu64)主 worker 走deno_crypto::deno_crypto::args(options.seed)runtime/worker.rsweb worker 走init(options.seed)runtime/web_worker.rs快照路径走lazy_init()runtime/snapshot_info.rs独立 ops没有 standalone ops仍注册op_crypto_random_uuid_batch与op_crypto_is_seeded两个ext/crypto/lib.rs全局挂载用Object.defineProperty挂crypto/CryptoKeyDeno 本体改由 runtime/js/98_global_scope_shared.js 以loadExtScriptpropGetterOnly/propNonEnumerable完成README 里的Object.defineProperty示例更接近嵌入方视角展示外部运行时如何挂全局Deno 本体的运行时绑定已内化到启动脚本种子语义本身未变有 seed 时OpState放入确定性StdRng否则走系统熵源。源码导航推荐阅读顺序从哪里下钻决定了你能不能顺着一条链走到头。建议按调用链的先后走四步先看 ext/crypto/lib.rs它一次性给出扩展声明deps/ops/objects/options与CryptoError错误模型是整块代码的目录页再看 ext/crypto/00_crypto.js理解单例如何惰性铸造、randomUUID如何在批量与确定性两条路径间切换、Function.length为何要手动对齐 WebIDL然后按目标操作进对应的 ext/crypto/subtle_crypto.rs 与subtle_*.rs模块看一次sign/deriveBits在纯 Rust 里分派到哪最后用 tests/unit/webcrypto_test.ts 与 tests/unit/webcrypto_mldsa_test.ts 反向校验行为边界尤其后量子算法的SeededPrivate导出拒绝路径。顺着 lib.rs 的目录页、00_crypto.js 的分派口、subtle_*.rs 的算法内核、tests 的行为断言走一圈任意一次 WebCrypto 调用都能被定位到它真正执行的那行 Rust。【免费下载链接】denoA modern runtime for JavaScript and TypeScript.项目地址: https://gitcode.com/GitHub_Trending/de/deno创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。