Web Cryptography API描述了一套密码学工具,规范了JavaScript如何以安全和符合惯例的方式实现加密。
这些工具包括生成、使用和应用加密密钥对,加密和解密消息,以及可靠地生成随机数。

20.12.1 生成随机数

在需要生成随机值时,很多人会使用Math.random()。
这个方法在浏览器中是以伪随机数生成器(PRNG, PseudoRandom Number Generator)方式实现的。
所谓“伪”指的是生成值的过程不是真的随机。PRNG生成的值只是模拟了随机的特性。浏览器的PRNG并未使用真正的随机源,只是对一个内部状态应用了固定的算法。
每次调用Math.random(),这个内部状态都会被一个算法修改,而结果会被转换为一个新的随机值。
由于算法本身是固定的,其输入只是之前的状态,因此随机数顺序也是确定的。
很明显,如果攻击者知道PRNG的内部状态,就可以预测后续生成的伪随机值。如果开发者无意中使用PRNG生成了私有密钥用于加密,则攻击者就可以利用PRNG的这个特性算出私有密钥。
伪随机数生成器主要用于快速计算出看起来随机的值。不过并不适合用于加密计算。为解决这个问题,密码学安全伪随机数生成器(CSPRNG,Cryptographically Secure PseudoRandom Number Generator)额外增加了一个熵作为输入。
Web Cryptography API引入了CSPRNG,这个CSPRNG可通过crypto.getRandomValues()在全局Crypto对象上访问。
与Math.random()返回一个介于0和1之间的浮点数不同,getRandomValues()会把随机值写入作为参数传给它的定型数组。
定型数组的类不重要,因为底层缓冲区会被随机的二进制位填充。

20.12.2 使用SubtleCrypto对象

Web Cryptography API重头特性都暴露在了SubtleCrypto对象上,可以通过window.crypto. subtle访问
这个对象包含一组方法,用于执行常见的密码学功能,如加密、散列、签名和生成密钥。
因为所有密码学操作都在原始二进制数据上执行,所以SubtleCrypto的每个方法都要用到ArrayBuffer和ArrayBufferView类型。
由于字符串是密码学操作的重要应用场景,因此TextEncoder和TextDecoder是经常与SubtleCrypto一起使用的类,用于实现二进制数据与字符串之间的相互转换。

1.生成密码学摘要

计算数据的密码学摘要是非常常用的密码学操作。
这个规范支持4种摘要算法:SHA-1和3种SHA-2。
SubtleCrypto.digest()方法用于生成消息摘要。
要使用的散列算法通过字符串”SHA-1”、”SHA-256”、”SHA-384”或”SHA-512”指定。
通常,在使用时,二进制的消息摘要会转换为十六进制字符串格式。通过将二进制数据按8位进行分割,然后再调用toString(16)就可以把任何数组缓冲区转换为十六进制字符串。
软件公司通常会公开自己软件二进制安装包的摘要,以便用户验证自己下载到的确实是该公司发布的版本(而不是被恶意软件篡改过的版本)。

2.CryptoKey与算法

如果没了密钥,那密码学也就没什么意义了。
SubtleCrypto对象使用CryptoKey类的实例来生成密钥。
CryptoKey类支持多种加密算法,允许控制密钥抽取和使用。
注:CryptoKey支持很多算法,但其中只有部分算法能够用于SubtleCrypto的方法。要了解哪个方法支持什么算法,可以参考W3C网站上Web Cryptography API规范的“Algorithm Overview”。

3.生成CryptoKey

使用SubtleCrypto.generateKey()方法可生成随机CryptoKey,这个方法返回一个期约,解决为一个或多个CryptoKey实例。
使用时需要给这个方法传入一个指定目标算法的参数对象、一个表示密钥是否可以从CryptoKey对象中提取出来的布尔值,以及一个表示这个密钥可以与哪个SubtleCrypto方法一起使用的字符串数组(keyUsages)。

4.导出和导入密钥

如果密钥是可提取的,那么就可以在CryptoKey对象内部暴露密钥原始的二进制内容。
使用exportKey()方法并指定目标格式(”raw”、”pkcs8”、”spki”或”jwk”)就可以取得密钥。
这个方法返回一个期约,解决后的ArrayBuffer中包含密钥。
与exportKey()相反的操作要使用importKey()方法实现。
importKey()方法的签名实际上是generateKey()和exportKey()的组合。

5.从主密钥派生密钥

使用SubtleCrypto对象可通过可配置的属性从已有密钥获得新密钥。
SubtleCrypto支持一个deriveKey()方法和一个deriveBits()方法,前者返回一个解决为CryptoKey的期约,后者返回一个解决为ArrayBuffer的期约。
deriveBits()方法接收一个算法参数对象、主密钥和输出的位长作为参数。
deriveKey()方法是类似的,只不过返回的是CryptoKey的实例而不是ArrayBuffer。

6.使用非对称密钥签名和验证消息

通过SubtleCrypto对象可以使用公钥算法用私钥生成签名,或者用公钥验证签名。
这两种操作分别通过SubtleCrypto.sign()和SubtleCrypto.verify()方法完成。

7.使用对称密钥加密和解密

SubtleCrypto对象支持使用公钥和对称算法加密和解密消息。
这两种操作分别通过SubtleCrypto. encrypt()和SubtleCrypto.decrypt()方法完成。

8.包装和解包密钥

SubtleCrypto对象支持包装和解包密钥,以便在非信任渠道传输。
这两种操作分别通过Subtle-Crypto.wrapKey()和SubtleCrypto.unwrapKey()方法完成。