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等攻擊手段來盜取密鑰。