
2025-04-10 日报 Day152

今日的鸡汤
一寸光阴一寸金,寸金难买寸光阴,时间买不到也租不来,惜时如金就是增进财富储量,因时制宜就是掌控发展变量,分秒必争就是创造价值增量。
今日学习内容
1、JS 红皮书 P663-674 第二十章:JavaScript API
今日笔记
1、 Web Cryptography API: Web Cryptography API 描述了一套密码学工具,规范了 JavaScript 如何以安全和符合惯例的方式实现加密。这些工具包括生成、使用和应用加密密钥对,加密和解密消息,以及可靠地生成随机数。
2、生成随机数: 在需要生成随机值时,很多人会使用 Math.random()。这个方法在浏览器中是以伪随机数生成器(PRNG,PseudoRandom Number Generator)方式实现的。所谓“伪”指的是生成值的过程不是真的随机。PRNG 生成的值只是模拟了随机的特性。
由于算法本身是固定的,其输入只是之前的状态,因此随机数顺序也是确定的。xorshift128+使用 128 位内部状态,而算法的设计让任何初始状态在重复自身之前都会产生 2128–1 个伪随机值。这种循环被称为置换循环(permutation cycle),而这个循环的长度被称为一个周期(period)。很明显,如果攻击者知道 PRNG 的内部状态,就可以预测后续生成的伪随机值。如果开发者无意中使用 PRNG 生成了私有密钥用于加密,则攻击者就可以利用 PRNG 的这个特性算出私有密钥。
伪随机数生成器主要用于快速计算出看起来随机的值。不过并不适合用于加密计算。为解决这个问题,密码学安全伪随机数生成器(CSPRNG,Cryptographically Secure PseudoRandom Number Generator)额外增加了一个熵作为输入,例如测试硬件时间或其他无法预计行为的系统特性。这样一来,计算速度明显比常规 PRNG 慢很多,但 CSPRNG 生成的值就很难预测,可以用于加密了。
Web Cryptography API 引入了 CSPRNG,这个 CSPRNG 可以通过 crypto.getRandomValues()在全局 Crypto 对象上访问。
1 | const array = new Uint8Array(1); |
要使用 CSPRNG 重新实现 Math.random(),可以通过生成一个随机的 32 位数值,然后用它去除最大的可能值 0xFFFFFFFF。这样就会得到一个介于 0 和 1 之间的值
1 | function randomFloat() { |
3、使用 SubtleCrypto 对象: Web Cryptography API 重头特性都暴露在了 SubtleCrypto 对象上,可以通过 window.crypto.subtle 访问:
console.log(crypto.subtle); // SubtleCrypto {}
这个对象包含一组方法,用于执行常见的密码学功能,如加密、散列、签名和生成密钥。
- 生成密码学摘要: 计算数据的密码学摘要是非常常用的密码学操作。这个规范支持 4 种摘要算法:SHA-1 和 3 种 SHA-2。
SHA-1(Secure Hash Algorithm 1):架构类似 MD5 的散列函数。接收任意大小的输入,生成 160 位消息散列。由于容易受到碰撞攻击,这个算法已经不再安全。
SHA-2(Secure Hash Algorithm 2):构建于相同耐碰撞单向压缩函数之上的一套散列函数。规范支持其中 3 种:SHA-256、SHA-384 和 SHA-512。生成的消息摘要可以是 256 位(SHA-256)、384 位(SHA-384)或 512 位(SHA-512)。这个算法被认为是安全的,广泛应用于很多领域和协议,包括 TLS、PGP 和加密货币(如比特币)。
SubtleCrypto.digest()方法用于生成消息摘要。要使用的散列算法通过字符串”SHA-1”、”SHA-256”、”SHA-384”或”SHA-512”指定。下面的代码展示了一个使用 SHA-256 为字符串”foo”生成消息摘要的例子:
1 | (async function () { |
通常,在使用时,二进制的消息摘要会转换为十六进制字符串格式。通过将二进制数据按 8 位进行分割,然后再调用 toString(16)就可以把任何数组缓冲区转换为十六进制字符串:
1 | (async function () { |
软件公司通常会公开自己软件二进制安装包的摘要,以便用户验证自己下载到的确实是该公司发布的版本(而不是被恶意软件篡改过的版本)。下面的例子演示了下载 Firefox v67.0,通过 SHA-512 计算其散列,再下载其 SHA-512 二进制验证摘要,最后检查两个十六进制字符串匹配:
1 | (async function () { |
- CryptoKey与算法: 如果没了密钥,那密码学也就没什么意义了。SubtleCrypto 对象使用 CryptoKey 类的实例来生成密钥。CryptoKey 类支持多种加密算法,允许控制密钥抽取和使用。
CryptoKey 类支持以下算法,按各自的父密码系统归类。
RSA(Rivest-Shamir-Adleman):公钥密码系统,使用两个大素数获得一对公钥和私钥,可用于签名/验证或加密/解密消息。RSA 的陷门函数被称为分解难题(factoring problem)。
RSASSA-PKCS1-v1_5:RSA 的一个应用,用于使用私钥给消息签名,允许使用公钥验证签名。
SSA(Signature Schemes with Appendix),表示算法支持签名生成和验证操作。
PKCS1(Public-Key Cryptography Standards #1),表示算法展示出的 RSA 密钥必需的数学特性。
RSASSA-PKCS1-v1_5 是确定性的,意味着同样的消息和密钥每次都会生成相同的签名。
RSA-PSS:RSA 的另一个应用,用于签名和验证消息。
PSS(Probabilistic Signature Scheme),表示生成签名时会加盐以得到随机签名。
与 RSASSA-PKCS1-v1_5 不同,同样的消息和密钥每次都会生成不同的签名。
与 RSASSA-PKCS1-v1_5 不同,RSA-PSS 有可能约简到 RSA 分解难题的难度。
通常,虽然 RSASSA-PKCS1-v1_5 仍被认为是安全的,但 RSA-PSS 应该用于代替RSASSA-PKCS1-v1_5。
RSA-OAEP:RSA 的一个应用,用于使用公钥加密消息,用私钥来解密。
OAEP(Optimal Asymmetric Encryption Padding),表示算法利用了 Feistel 网络在加密前处理未加密的消息。
OAEP 主要将确定性 RSA 加密模式转换为概率性加密模式。
ECC(Elliptic-Curve Cryptography):公钥密码系统,使用一个素数和一个椭圆曲线获得一对公钥和私钥,可用于签名/验证消息。ECC 的陷门函数被称为椭圆曲线离散对数问题(elliptic curve discrete logarithm problem)。ECC 被认为优于 RSA。虽然 RSA 和 ECC 在密码学意义上都很强,但 ECC 密钥比 RSA 密钥短,而且 ECC 密码学操作比 RSA 操作快。
ECDSA(Elliptic Curve Digital Signature Algorithm):ECC 的一个应用,用于签名和验证消息。这个算法是数字签名算法(DSA,Digital Signature Algorithm)的一个椭圆曲线风格的变体。
ECDH(Elliptic Curve Diffie-Hellman):ECC 的密钥生成和密钥协商应用,允许两方通过公开通信渠道建立共享的机密。这个算法是 Diffie-Hellman 密钥交换(DH,Diffie-Hellman key exchange)协议的一个椭圆曲线风格的变体。
AES(Advanced Encryption Standard):对称密钥密码系统,使用派生自置换组合网络的分组密码加密和解密数据。AES 在不同模式下使用,不同模式算法的特性也不同。
AES-CTR:AES 的计数器模式(counter mode)。这个模式使用递增计数器生成其密钥流,其行为类似密文流。使用时必须为其提供一个随机数,用作初始化向量。AES-CTR 加密/解密可以并行。
AES-CBC:AES 的密码分组链模式(cipher block chaining mode)。在加密纯文本的每个分组之前,先使用之前密文分组求 XOR,也就是名字中的“链”。使用一个初始化向量作为第一个分组的 XOR 输入。
AES-GCM:AES 的伽罗瓦/计数器模式(Galois/Counter mode)。这个模式使用计数器和初始化向量生成一个值,这个值会与每个分组的纯文本计算 XOR。与 CBC 不同,这个模式的 XOR 输入不依赖之前分组密文。因此 GCM 模式可以并行。由于其卓越的性能,AES-GCM 在很多网络安全协议中得到了应用。
AES-KW:AES 的密钥包装模式(key wrapping mode)。这个算法将加密密钥包装为一个可移植且加密的格式,可以在不信任的渠道中传输。传输之后,接收方可以解包密钥。与其他 AES 模式不同,AES-KW 不需要初始化向量。
HMAC(Hash-Based Message Authentication Code):用于生成消息认证码的算法,用于验证通过不可信网络接收的消息没有被修改过。两方使用散列函数和共享私钥来签名和验证消息。
KDF(Key Derivation Functions):可以使用散列函数从主密钥获得一个或多个密钥的算法。KDF能够生成不同长度的密钥,也能把密钥转换为不同格式。
HKDF(HMAC-Based Key Derivation Function):密钥推导函数,与高熵输入(如已有密钥)一起使用。
PBKDF2(Password-Based Key Derivation Function 2):密钥推导函数,与低熵输入(如密钥字符串)一起使用。 - 生成CryptoKey: 使用 SubtleCrypto.generateKey()方法可以生成随机 CryptoKey,这个方法返回一个期约,解决为一个或多个 CryptoKey 实例。使用时需要给这个方法传入一个指定目标算法的参数对象、一个表示密钥是否可以从 CryptoKey 对象中提取出来的布尔值,以及一个表示这个密钥可以与哪个SubtleCrypto 方法一起使用的字符串数组(keyUsages)。
-生成CryptoKey: 使用 SubtleCrypto.generateKey()方法可以生成随机 CryptoKey,这个方法返回一个期约,解决为一个或多个 CryptoKey 实例。使用时需要给这个方法传入一个指定目标算法的参数对象、一个表示密钥是否可以从 CryptoKey 对象中提取出来的布尔值,以及一个表示这个密钥可以与哪个SubtleCrypto 方法一起使用的字符串数组(keyUsages)。
由于不同的密码系统需要不同的输入来生成密钥,上述参数对象为每种密码系统都规定了必需的输入:
RSA 密码系统使用 RsaHashedKeyGenParams 对象;
ECC 密码系统使用 EcKeyGenParams 对象;
HMAC 密码系统使用 HmacKeyGenParams 对象;
AES 密码系统使用 AesKeyGenParams 对象。
keyUsages 对象用于说明密钥可以与哪个算法一起使用。至少要包含下列中的一个字符串:
encrypt
decrypt
sign
verify
deriveKey
deriveBits
wrapKey
unwrapKey
keyUsages 对象用于说明密钥可以与哪个算法一起使用。至少要包含下列中的一个字符串:
encrypt
decrypt
sign
verify
deriveKey
deriveBits
wrapKey
unwrapKey
假设要生成一个满足如下条件的对称密钥:
支持 AES-CTR 算法;
密钥长度 128 位;
不能从 CryptoKey 对象中提取;
可以跟 encrypt()和 decrypt()方法一起使用。
那么可以参考如下代码:
(async function() {
const params = {
name: ‘AES-CTR’,
length: 128
};
const keyUsages = [‘encrypt’, ‘decrypt’];
const key = await crypto.subtle.generateKey(params, false, keyUsages);
console.log(key);
// CryptoKey {type: “secret”, extractable: true, algorithm: {…}, usages: Array(2)}
})(); - 导出和导入密钥: 如果密钥是可提取的,那么就可以在 CryptoKey 对象内部暴露密钥原始的二进制内容。使用exportKey()方法并指定目标格式(”raw”、”pkcs8”、”spki”或”jwk”)就可以取得密钥。这个方法返回一个期约,解决后的 ArrayBuffer 中包含密钥:
(async function() {
const params = {
name: ‘AES-CTR’,
length: 128
};
const keyUsages = [‘encrypt’, ‘decrypt’];
const key = await crypto.subtle.generateKey(params, true, keyUsages);
const rawKey = await crypto.subtle.exportKey(‘raw’, key);
console.log(new Uint8Array(rawKey));
// Uint8Array[93, 122, 66, 135, 144, 182, 119, 196, 234, 73, 84, 7, 139, 43, 238,
// 110]
})();
exportKey()相反的操作要使用 importKey()方法实现。importKey()方法的签名实际上是generateKey()和 exportKey()的组合。下面的方法会生成密钥、导出密钥,然后再导入密钥: - 从主密钥派生密钥: 使用 SubtleCrypto 对象可以通过可配置的属性从已有密钥获得新密钥。
- 使用非对称密钥签名和验证消息: 通过 SubtleCrypto 对象可以使用公钥算法用私钥生成签名,或者用公钥验证签名。这两种操作分别通过 SubtleCrypto.sign()和 SubtleCrypto.verify()方法完成。
- 使用对称密钥加密和解密: SubtleCrypto 对象支持使用公钥和对称算法加密和解密消息。这两种操作分别通过 SubtleCrypto.encrypt()和 SubtleCrypto.decrypt()方法完成。加密消息需要传入参数对象以指定算法和必要的值、加密密钥和要加密的数据。
- 包装和解包密钥: SubtleCrypto 对象支持包装和解包密钥,以便在非信任渠道传输。这两种操作分别通过 SubtleCrypto.wrapKey()和 SubtleCrypto.unwrapKey()方法完成。
4、小结: 除了定义新标签,HTML5 还定义了一些 JavaScript API。这些 API 可以为开发者提供更便捷的 Web接口,暴露堪比桌面应用的能力。本章主要介绍了以下 API。
Atomics API 用于保护代码在多线程内存访问模式下不发生资源争用。
postMessage() API 支持从不同源跨文档发送消息,同时保证安全和遵循同源策略。
Encoding API 用于实现字符串与缓冲区之间的无缝转换(越来越常见的操作)。
File API 提供了发送、接收和读取大型二进制对象的可靠工具。
媒体元素