ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Deno ext/crypto 深度解析:Web Crypto API 的 Rust 实现、cppgc 对象化与可播种随机数

Deno ext/crypto 深度解析:Web Crypto API 的 Rust 实现、cppgc 对象化与可播种随机数 Deno ext/crypto 深度解析Web Crypto API 的 Rust 实现、cppgc 对象化与可播种随机数【免费下载链接】denoA modern runtime for JavaScript and TypeScript.项目地址: https://gitcode.com/GitHub_Trending/de/denoext/crypto是 Deno 中实现 W3C Web Cryptography API 的扩展crate 名deno_crypto。本文以该目录的 README 为核心骨架结合 lib.rs、00_crypto.js 与各算法模块源码讲清楚三件事这个扩展暴露的Crypto/SubtleCrypto/CryptoKey接口如何在 Rust 侧落地为 cppgc 包裹类Optionu64初始化 seed 如何改变随机数路径以及密钥材料为什么存放在 Rust 侧的垃圾回收对象里而不是 JavaScriptWeakMap中。读完本文你可以独立定位任何 WebCrypto 调用的底层实现位置并理解 Deno 如何在保持 WPT 兼容的前提下把算法逻辑从 JS 迁移到纯 Rust。一、这个 crate 是什么实现 Web Cryptography APIREADME 开篇即给出定位This crate implements the Web Cryptography API并指向 W3C 规范WebCryptoAPI 工作草案。Cargo.toml 的元信息与之对应[package] name deno_crypto description Web Cryptography API implementation for Deno算法能力面由依赖声明直接体现见 Cargo.toml哈希sha1、sha2、sha3、tiny-keccak后者启用k12/kmacfeature用于 KangarooTwelve 系列 XOF 算法非对称rsa、p256启用ecdh/ecdsa、p384、p521、curve25519-dalek、x25519-dalek、ecdsa、signature、spki、const-oid对称aes、aes-gcm、aes-kw、cbc、ctr、ocb3、hmac密钥派生aws-lc-rsHKDF/PBKDF2、argon2后量子fips203ML-KEM-512/768/1024即 ML-KEM 封装、fips205ML-DSA 签名。这与 tests/unit/webcrypto_test.ts、tests/unit/webcrypto_mldsa_test.ts 等测试覆盖的算法面一一对应。二、README 给出的用法JS 侧挂载全局与 Rust 侧注册扩展README 的 Usage Example 给出了嵌入该扩展的标准姿势值得完整保留。从 JavaScript 侧加载扩展脚本并把CryptoKey、crypto、Crypto、SubtleCrypto挂到全局作用域import { core } from ext:core/mod.js; const crypto core.loadExtScript(ext:deno_crypto/00_crypto.js); Object.defineProperty(globalThis, CryptoKey, { value: crypto.CryptoKey, enumerable: false, configurable: true, writable: true, }); Object.defineProperty(globalThis, crypto, { value: crypto.crypto, enumerable: false, configurable: true, writable: false, // 全局 crypto 只读 }); Object.defineProperty(globalThis, Crypto, { value: crypto.Crypto, enumerable: false, configurable: true, writable: true, }); Object.defineProperty(globalThis, SubtleCrypto, { value: crypto.SubtleCrypto, enumerable: false, configurable: true, writable: true, });Rust 侧则需要在运行时构造时提供deno_crypto::deno_crypto::init(Optionu64)其中Optionu64是可选的初始化种子。当前仓库中该调用的真实落点是 runtime/snapshot_info.rsdeno_crypto::deno_crypto::init(None)与 runtime/web_worker.rsdeno_crypto::deno_crypto::init(options.seed)JS 入口则由 runtime/js/98_global_scope_shared.js 通过core.loadExtScript(ext:deno_crypto/00_crypto.js)加载。扩展声明ops、objects 与 options 三要素README Surface 一节的关键论断是所有 WebCrypto 入口都实现为objects [...]注册的 cppgc 包裹类上的方法没有独立 opssubtle_*.rs中的按算法 helper 是纯 Rust 函数直接从那些方法体调用。对照 lib.rs 的扩展宏即可验证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 [deno_webidl, deno_web]即 README Dependencies 一节所列的两个依赖 crate分别提供 WebIDL 类型检查工具与 console/inspect 支持仅剩 2 个 standalone ops。op_crypto_is_seeded判断OpState里是否放入了StdRng即是否提供了 seedop_crypto_random_uuid_batch一次性批量产出 UUID 字符串。这是历史上大批op_crypto_*全部下沉到 cppgc 方法后的残留options.maybe_seed就是 README 说的Optionu64种子。state闭包中若有 seed 则把StdRng::seed_from_u64(seed)存入OpState从而得到可复现的伪随机数流主要用于测试/快照场景没有 seed 时随机数来自操作系统熵源。runtime/worker.rs 中WorkerOptions携带pub seed: Optionu64并在扩展参数中以deno_crypto::deno_crypto::args(options.seed)透传。三、seed 如何改变运行时行为JS 侧的两条 UUID 路径有了 seed 概念00_crypto.js 顶部注释解释了运行时策略普通路径的randomUUID采用批量 UUID 缓存而seeded runtimes走原生方法以保留精确的 RNG 调用顺序const UUID_BATCH_SIZE 128; let uuidBatchData; let uuidBatch UUID_BATCH_SIZE; function randomUUID() { if (this ! cryptoSingleton || usesSeededRng) { return FunctionPrototypeCall(cppgcRandomUUID, this); } if (uuidBatch UUID_BATCH_SIZE) { uuidBatchData op_crypto_random_uuid_batch(); // 一次取回 128 条 uuidBatch 0; } const start uuidBatch * UUID_STRING_BYTES; return StringPrototypeSlice(uuidBatchData, start, start UUID_STRING_BYTES); }批量路径背后是 Rust 侧的fast_uuid_v4_byteslib.rs在 16 字节上就地设置 UUIDv4 的版本位与变体位bytes[6] (bytes[6] 0x0f) | 0x40等再用查表HEX_CHARS直接拼出 36 字节字符串避免格式化开销同文件内还附带了与uuidcrate 对拍的正确性测试test_fast_uuid_v4_correctness。而getCryptoSingleton()在铸造Crypto单例时会调用op_crypto_is_seeded()记下usesSeededRng标志——这正是 seed 从 RustOpState反哺 JS 行为分派的完整闭环。四、Surface 架构cppgc 包裹类取代每个 op 一个 JS 函数README 的 Surface 是理解本 crate 当前形态的钥匙。对照 00_crypto.js 头部注释JS 层已被刻意做薄every WebCrypto algorithm body (SubtleCrypto.{digest,encrypt,decrypt,sign,verify,deriveBits,deriveKey,importKey,exportKey,wrapKey,unwrapKey,generateKey,getPublicKey,encapsulateKey,encapsulateBits,decapsulateKey,decapsulateBits,supports}, and theCrypto.{getRandomValues,randomUUID,subtle}members) is implemented natively on the cppgc-wrapped Rust classes inext/crypto/{crypto,subtle_crypto,crypto_key}.rsJS 侧只保留四类簿记工作privateCustomInspect装饰为三个原型挂Deno.privateCustomInspect符号使Deno.inspect输出符合 WebIDL 形状如CryptoKey只显示type/extractable/algorithm/usages惰性铸造单例cppgc 堆在快照构建期未附着到 V8 isolate因此Crypto/SubtleCrypto单例必须延迟到运行时第一次读取globalThis.crypto/crypto.subtle时经由Crypto.create(getSubtleSingleton())与SubtleCrypto.create()分配structured-clone 复活回调core.registerCloneableResource(CryptoKey, (data) CryptoKey.fromCloneData(data))使CryptoKey可跨 Worker 结构化克隆tests/unit/structured_clone_test.ts 有对应断言Function.length修正例如deriveBits用一个三参转发器保证规范的length 2makeAsyncForwarder则把所有SubtleCrypto方法包成 async使同步错误也走 Promise rejection并对齐 WebIDL 操作必需参数个数如unwrapKey为 7、deriveKey为 5。按算法拆分后每个操作族有独立模块subtle_digest.rs、subtle_encrypt.rs、subtle_decrypt.rs、subtle_sign.rs、subtle_verify.rs、subtle_derive_bits.rs、subtle_derive_key.rs、subtle_import_key.rs、subtle_export_key.rs、subtle_generate_key.rs、subtle_wrap_key.rs、subtle_get_public_key.rs、subtle_encapsulate.rs、subtle_encapsulate_key.rs以及非对称原子模块 ed25519.rs、x25519.rs、x448.rs、mldsa.rs、mlkem.rs、slhdsa.rs。错误模型CryptoError与 DOMException 类映射WebCrypto 要求错误以特定 DOMException 名称抛出NotSupportedError、OperationError、QuotaExceededError…。lib.rs 中的CryptoError枚举用#[class(...)]属性精确绑定每一类的 JS 异常类例如UnsupportedDigestAlgorithm(String)→DOMExceptionNotSupportedError消息Algorithm {0} is not supportedDecryptionError→DOMExceptionOperationError消息decryption error - integrity check failedAEAD 认证标签失败ArrayBufferViewLengthExceeded(usize)→DOMExceptionQuotaExceededError上限 65536 字节熵HKDFLengthTooLarge→DOMExceptionOperationError。digest.rs 中的注释进一步展示了这种映射如何被 WPT 测试逐条钉死未知算法名不能在 converter 层直接抛TypeError而是保留为DigestAlgorithm::Unknown(name)推迟到run()抛出规范要求的NotSupportedError因为 WPTdigest.https.any.html的若干子测试硬编码了错误名称。五、密钥材料放在哪里RawKeyData与CryptoKeyHandleREADME 说密钥由subtle_*纯 Rust helper 直接从方法体调用这里有一个关键的性能设计值得展开。lib.rs 中KeyData的注释写得很直白Owned key material handed to the sign/verify/derive ops after being looked up from the Rust-sideKeyStoreby handle.Previously the key bytes were serialized and passed from JavaScript on every operation.而 key_store.rs 的CryptoKeyHandle是 V8 垃圾回收对象unsafe impl GarbageCollected持有真正的密钥字节RawKeyData。其文档注释概括了演进方向历史实现中密钥放在00_crypto.js的 JSWeakMapKEY_STORE里、每次加密操作都要序列化跨 JS/Rust 边界现在密钥字节生活在 Rust 的 cppgc 对象内JS 只持有 handleCryptoKey被回收时密钥随之自动释放无需FinalizationRegistry。密钥素材的类型由 shared.rs 的RawKeyData枚举表达Secret/Private/Public带用途标签的字节如 HMAC 密钥、PKCS8 私钥、SPKI 公钥Raw原样存储的字节Ed25519/X25519/X448/ML-KEM 公钥等不携带 secret/private/public 标签SeededPrivate { seed, private_key }FIPS 203/204 算法ML-KEM 解封装密钥、ML-DSA 签名密钥的复合素材——private_key是展开后的密钥字节seed是用于派生的短种子seed为None时从展开私钥字节导入导出raw-seed/jwk/pkcs8格式会被正确拒绝node_interop.rs 中有对应报错路径。序列化/导出格式则定义在 lib.rs 顶部#[serde(rename_all lowercase)] pub enum KeyFormat { Raw, Pkcs8, Spki } #[serde(rename_all lowercase)] pub enum KeyType { Secret, Private, Public }六、算法分派内幕sign/verify/derive 的同步内核README 说subtle_*::run是纯 Rust、被 cppgc 方法体直接调用。lib.rs 中保留的同步分派函数是理解一次sign()调用在 Rust 里走了多远的样本。signsign_key_sync该函数在spawn_blocking内由 subtle_sign.rs 的run调用按Algorithm变体分派完整算法枚举见 key.rs 的AlgorithmRSASSA-PKCS1-v1_5、RSA-PSS、RSA-OAEP、ECDSA、ECDH、AES-CTR、AES-CBC、AES-GCM、AES-KW、HMAC、PBKDF2、HKDFRSASSA-PKCS1-v1_5RsaPrivateKey::from_pkcs1_der解出私钥后按hash变体SHA-1/256/384/512构造rsa::pkcs1v15::SigningKeyD签名缺失hash时报CryptoError::MissingArgumentHash即 JS 侧的TypeError: Missing argument hashRSA-PSS额外要求salt_length缺失则MissingArgumentSaltLength先用OsRng随机盐再Pss::new_with_salt::D(salt_len)签名ECDSA按named_curveP-256/P-384/P-521见CryptoNamedCurve从 PKCS#8 解码私钥先计算 prehash 再sign_prehash产出 rawr||s签名其中 P-521 分支对短于 33 字节的哈希做左补零满足bits2field的最小长度要求——这是一个容易被忽视的曲线相关细节HMACSHA3 变体走hmac::HmacSha3_*tiny-keccak路径其余走aws_lc_rs::hmac::sign注意 key.rs 中FromCryptoHash for HmacAlgorithm对 SHA3 的转换被标记为unreachable!SHA3 is only supported for digest, not HMACJS 层保证 SHA3 不会到达该路径。verifyverify_key_sync结构对称但多一条路径KeyType::Private的 ECDSA 验证会先从 PKCS#8 私钥推导VerifyingKeyP256SigningKey::from(secret_key).verifying_key()即允许用私钥对象验证的 WebCrypto 行为验证失败统一返回false而非抛错符合规范。deriveBitsderive_bits_sync同一文件内还收纳了派生内核salt为Some对应 PBKDF2/HKDF、None对应 ECDHPBKDF2assert!(length.is_multiple_of(8))aws_lc_rs::pbkdf2::derive按PBKDF2_HMAC_SHA{1,256,384,512}派生length/8字节HKDFSalt::new(alg, salt).extract(secret)得 PRK 后expand(info_slice, HkdfOutput(length))HkdfOutput是 key.rs 中为hkdf::KeyType定义的新类型包装超限报错映射为HKDFLengthTooLargeOperationErrorECDH对 P-256/P-384/P-521 用elliptic_curve::ecdh::diffie_hellman返回共享点的 raw x 坐标公共方既可以是 SPKI 点from_encoded_point常量时间Option判空也可以是 PKCS#8 私钥自动.public_key()。七、digest 特写从 AlgorithmIdentifier 到 XOF 参数校验digest.rs 展示了converter 推迟错误模式的完整形态是 WPT 兼容工程的缩影算法名解析AlgorithmIdentifier允许字符串或{ name }字典缺name成员按 WebIDL 语义报TypeErrorWPTdigest({}, ...)空对象子测试钉死了这一点而未登记的算法名被保留为DigestAlgorithm::Unknown由run()抛NotSupportedError算法注册表SHA-1、SHA-256/384/512、SHA3-256/384/512之外还登记了七个 XOF 算法——cSHAKE128/256、TurboSHAKE128/256、KT128、KT256、KangarooTwelve匹配不区分大小写canonical_digest_nameXOF 参数字典SubtleDigestXof枚举携带outputLength及可选functionName/customizationcSHAKE/KT或domainSeparationTurboSHAKE。run_xof校验outputLength为 8 的倍数、TurboSHAKE/KangarooTwelve 的outputLength非零、domainSeparation落在[0x01, 0x7F]违规统一报InvalidXofParametersOperationError。注意read_optional_u8会先按 u32 读完整值再拒绝0xFF防止0x101回绕成0x01绕过范围检查——这类边界处理正是从 JS 迁移到 Rust 时逐条保留的BufferSource 转换自定义WebIdlConverter for BufferSource把ArrayBuffer/ArrayBufferView物化为Vecu8保证跨.await安全并显式拒绝SharedArrayBuffer及其视图与 WebIDLBufferSource无[AllowShared]的契约一致。KangarooTwelve-128/256 的实现在该文件底部KT128 直接调tiny_keccak::KangarooTwelveKT256 则用内部手写的TurboShakeNode136sponge状态 25×u64、rate 136 字节按 8192 字节分块做树形归约是对tiny-keccak仅带k12feature 的补充。八、从 README 到当前仓库接口演进的三处差异README 是较早时期的文档与源码现状存在三处值得注意的差异引用时需注意init(Optionu64)已演进为args(seed)。当前 runtime/worker.rs 以deno_crypto::deno_crypto::args(options.seed)构造扩展参数options.maybe_seed对应 README 的Optionu64worker 路径用init(options.seed)runtime/web_worker.rs快照路径用lazy_init()runtime/snapshot.rs。种子语义未变有 seed 时OpState放入确定性StdRng无独立 ops 已基本成立但留有两个例外。当前扩展仍注册op_crypto_random_uuid_batch与op_crypto_is_seeded两个 ops见第二节扩展宏前者服务 JS 批量 UUID 快路径后者向 JS 暴露 seed 状态JS 挂载代码已内化。README 的Object.defineProperty(globalThis, ...)示例演示的是嵌入方视角Deno 本体的运行时全局绑定改由 runtime/js/98_global_scope_shared.js 完成loadExtScript后挂全局扩展脚本导出的是Crypto、gettercrypto、CryptoKey、SubtleCrypto及两个 Node.jsKeyObject互用函数cryptoKeyExportNodeKeyMaterial/importCryptoKeySync。九、小结回到 README 的 Surface 结论现在的表述可以更具体deno_crypto把 WebCrypto 的每个入口做成deno_core::extension!中objects列表里的 cppgc 包裹类方法crypto.rs 的Crypto、subtle_crypto.rs 的SubtleCrypto、crypto_key.rs 的CryptoKey算法逻辑分散在同目录的subtle_*.rs/ 原子算法模块中以纯 Rust 分派密钥字节驻留在 V8 GC 管理的CryptoKeyHandle中避免每次操作序列化跨边界Optionu64种子通过扩展options注入OpState驱动 JS 在批量与确定性两条 UUID 路径间切换。对于要阅读或调试 Deno 加密路径的开发者建议的入口顺序是lib.rs扩展声明与错误模型→ 00_crypto.js单例铸造与 WPT 形状修正→ 目标操作的subtle_*.rs再配合 tests/unit/webcrypto_test.ts 与 tests/unit/webcrypto_mldsa_test.ts 验证行为边界。【免费下载链接】denoA modern runtime for JavaScript and TypeScript.项目地址: https://gitcode.com/GitHub_Trending/de/deno创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表