web cryptography API
论文原链接:https://www.theseus.fi/bitstream/handle/10024/92960/Web_Cryptography_API_Luoma-aho.pdf
背景
- 随着数据安全的需求提高,另外js的流行,出现越来越多的前端富应用。用户对客户端加密/前端(浏览器端)加密的需求越来越强。
- 在已经有https(ssl/tsl)传输的基础上,前端的加解密是否必要?--本文探讨的重点
- js的加解密有很多弊端,比如:
- 缺少密码学安全的随机数生成器(CSPRNG)
- 缺少安全的私钥存储机制
- 不可预估的计算性能瓶颈
- 一些流行的js加解密库:
Stanford JavaScript Crypto Library(sjcl)
CryptoJS
Forge
Web Cryptography API
crypto知识简单介绍
- 公钥加解密(非对称加解密)
- 对称加解密
- 数字签名,提供身份验证和信息完整验证(未被篡改)
签名的key和加解密的key不能共用,签名具有法律效益。Web Cryptography API也对此做了限制。你不能生成同时用来加密和签名的key - 信息摘要(Message Digest),摘要算法是单向的(你不能恢复出原文),如MD5,SHA-1,SHA-256,SHA-512。MD5和sha-1有碰撞风险,不建议继续使用。主要用途:
- 信息完整验证(未被篡改)
- 创建唯一的标识符
基本使用
- window.crypto.subtle就是SubtleCrypto接口
- 主流最新浏览器基本都已经支持该接口,并且一致,除了safari,需要注意
// fix safari crypto namespace
if (window.crypto && !window.crypto.subtle && window.crypto.webkitSubtle) {
window.crypto.subtle = window.crypto.webkitSubtle;
}
/**
* Detect Web Cryptography API
* @return {Boolean} true, if success
*/
function isWebCryptoAPISupported() {
return 'crypto' in window && 'subtle' in window.crypto;
}
- getRandomValues 同步方法,获取随机数,因为性能要求,这是一个伪随机数生成器(PRNG)。浏览器也通过添加系统级别的种子来提高熵(不确定性的量度),来满足密码学使用要求。
var size = 10;
var array = new Uint8Array(size);
window.crypto.getRandomValues(array);
// print values to console
for (var i=0; i!==array.length; ++i) {
console.log(array[i]);
}
-
SubtleCrypto接口
- encrypt,decrypt(加解密方法),算法支持:
- RSA-OAEP
- AES-CTR
- AES-CBC
- AES-GCM
- AES-CFB
- Sign,verify(签名验签发方法),算法支持:
- RSASSA-PKCS1-v1.5
- RSA-PSS
- ECDSA
- AES-CMAC
- HMAC
- Digest(摘要方法),算法支持:
- SHA-1, SHA-256, SHA-384, SHA-512
- GenerateKey
生成对称/非对称的密钥(CryptoKey对象) - DeriveKey 和 deriveBits
从原密钥或是从伪随机函数生成的密码/口令中生成出一个密钥 - WrapKey 和 unwrapKey
用来保护在不安全信道或不可信环境存储的密钥的方法 - ImportKey 和 exportKey
导入密钥,转换再导出不同格式
上面的方法都会返回Promise
- encrypt,decrypt(加解密方法),算法支持:
var message = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.';
var cryptoKeyPair; // for storing the CryptoKey object
var ciphertext; // for storing the encryption result
var plaintext; // for storing the decryption result
var algorithm = {
name: "RSA-OAEP",
modulusLength: 2048, // 1024, 2048, 4096
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {
name: "SHA-256" // SHA-1, SHA-256, SHA-384, SHA-512
}
};
// Generate keys
window.crypto.subtle.generateKey(algorithm,
false, // non-exportable
["encrypt", "decrypt"] // usage
)
.then(function (result) {
// Store keys
cryptoKeyPair = result;
})
.then(function () {
// Encrypt
var data = utils.convertTextToUint8Array(message);
return window.crypto.subtle.encrypt(algorithm, cryptoKeyPair.publicKey, data);
})
.then(function (result) {
// Store ciphertext
ciphertext = new Uint8Array(result);
// console.log('ciphertext', ciphertext);
})
.then(function () {
// Output
console.log('Encrypted data:');
console.log(utils.convertUint8ArrayToHexView(ciphertext, 16, ''));
})
.then(function () {
// Decrypt
return window.crypto.subtle.decrypt(
algorithm, cryptoKeyPair.privateKey, ciphertext
);
})
.then(function (result) {
// Store plaintext
plaintext = new Uint8Array(result);
})
.then(function () {
// Output
console.log('Decrypted data:');
console.log(utils.convertUint8ArrayToText(plaintext));
})
- 导出jwk(JSON Web Key)格式
var algorithm = {
name: "RSA-OAEP",
modulusLength: 2048, // 1024, 2048, 4096
publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
hash: {
name: "SHA-256" // "SHA-1", "SHA-256", "SHA-384", "SHA -512"
}
}
window.crypto.subtle.generateKey(algorithm,
false, // non-exportable
["encrypt", "decrypt"] // usage
)
.then(function (cryptoKey) {
return window.crypto.subtle.exportKey(
'jwk', // export format
cryptoKey.publicKey
);
})
.then(function (exportedKey) {
console.log('Exported key:');
console.log(JSON.stringify(exportedKey));
});
-
CryptoKey对象可以存储在indexdb里(支持结构化克隆算法),不适合存储在localStorage里(只能存储简单对象)
-
SubtleCrypto,浏览器中的支持情况如下图
开发者需要熟知浏览器环境的安全常识:
- HTTP Strict Transport Security standard (HSTS)
规则让浏览器知道网站只能通过http安全连接来访问
- Content Security Policy (CSP)
规则通知浏览器应该从何处加载资源
- HTTP Public Key Pinning (HPKP)
它允许web主机运营商指导用户代理在一段时间内强制使用特定的加密身份,从而有效地减轻某些中间人攻击
- Subresource integrity (SRI)
它允许用户代理在不进行意外操作的情况下验证获取的资源是否已被传递。
开发者需要了解如下警告:
-
标注了'encrypt'使用属性的CryptoKey不能用来解密,反之亦然,标注了'decrypt'的不能用来加密。因为通常用于执行数据的数字签名。
而在使用API做数字签名时,必须使用一对带签名和验签功能的钥匙。 -
非对称加密算法不是用来加密长消息的。通常消息的长度要小于密钥长度(比如一个2048位的RSA-OAEP算法可以加密最长1704位(或213字节)的消息,因为为了安全原因,还会加上一些字节填充)
-
密钥存储在浏览器中时,攻击者可能利用系统漏洞,采用xss等攻击手段来盗取密钥。