SubtleCrypto:wrapKey() 方法
Baseline Widely available
This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.
SubtleCrypto
接口的 wrapKey()
方法用于“包装”(wrap)密钥。这一味着它以外部可移植的格式导出密钥,然后对其进行加密。包装密钥有助于在不受信任的环境中保护它,例如在未受保护的数据存储,或在未受保护的网络上进行传输。
与 SubtleCrypto.exportKey()
一样,你需要指定密钥的导出格式。要导出密钥,必须将 CryptoKey.extractable
设置为 true
。
但是,由于 wrapKey()
还会对要导出的密钥进行加密,因此还需要传入用于加密的密钥。这有时被称为“包装密钥”(wrapping key)。
wrapKey()
的逆运算是 SubtleCrypto.unwrapKey()
:wrapKey
由导出 + 加密组成,而 unwrapKey
由导入 + 解密组成。
语法
wrapKey(format, key, wrappingKey, wrapAlgo)
参数
format
-
描述密钥在加密之前所导出的数据格式的字符串。它可以是以下值之一:
key
-
将被包装的密钥。
wrappingkey
-
用于加密导出密钥的密钥。密钥的用途必须包括
wrapKey
。 wrapAlgo
-
指定用于加密导出密钥的算法的对象,以及任何所需的额外参数:
- 对于 RSA-OAEP,请传入
RsaOaepParams
对象。 - 对于 AES-CTR,请传入
AesCtrParams
对象。 - 对于 AES-CBC,请传入
AesCbcParams
对象。 - 对于 AES-GCM,请传入
AesGcmParams
对象。 - 对于 AES-KW,请传入字符串
"AES-KW"
,或{ "name": "AES-KW" }
形式的对象。
- 对于 RSA-OAEP,请传入
返回值
一个 Promise
,会兑现一个包含已加密的导出密钥的 ArrayBuffer
。
异常
当遇到以下异常时,promise 将会被拒绝:
InvalidAccessError
DOMException
-
当包装密钥不是要求的包装算法的密钥时触发。
NotSupported
DOMException
-
当尝试使用未知或不适用于加密/包装的算法时触发。
TypeError
-
当尝试使用无效格式时触发。
支持的算法
AES-KW
AES-KW 是一种使用 AES 密码来对密钥进行包装的方法。
使用 AES-KW 相比于其他 AES 模式(例如 AES-GCM)的一个有点是,AES-KW 不需要初始化向量。要使用 AES-KW,输入必须是 64 位的倍数。
AES-KW 规定于 RFC 3394 中。
示例
备注: 你可以在 GitHub 上尝试这个可用的示例。
Raw 包装
以下示例包装了一个 AES 密钥。它使用“raw”作为导出格式,并使用 AES-KW 和密码派生密钥对其进行加密。在 GitHub 中查看完整的代码。
let salt;
/*
获取用于作为 deriveKey 方法的输入的密钥材料。
密钥材料是用户提供的密码。
*/
function getKeyMaterial() {
const password = window.prompt("Enter your password");
const enc = new TextEncoder();
return window.crypto.subtle.importKey(
"raw",
enc.encode(password),
{ name: "PBKDF2" },
false,
["deriveBits", "deriveKey"],
);
}
/*
给定密钥材料和随机盐,使用 PBKDF2 派生一个 AES-KW 密钥。
*/
function getKey(keyMaterial, salt) {
return window.crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt,
iterations: 100000,
hash: "SHA-256",
},
keyMaterial,
{ name: "AES-KW", length: 256 },
true,
["wrapKey", "unwrapKey"],
);
}
/*
包装给定的密钥。
*/
async function wrapCryptoKey(keyToWrap) {
// 获取密钥加密密钥
const keyMaterial = await getKeyMaterial();
salt = window.crypto.getRandomValues(new Uint8Array(16));
const wrappingKey = await getKey(keyMaterial, salt);
return window.crypto.subtle.wrapKey("raw", keyToWrap, wrappingKey, "AES-KW");
}
/*
生成加密/解密密钥,然后包装它。
*/
window.crypto.subtle
.generateKey(
{
name: "AES-GCM",
length: 256,
},
true,
["encrypt", "decrypt"],
)
.then((secretKey) => wrapCryptoKey(secretKey))
.then((wrappedKey) => console.log(wrappedKey));
PKCS #8 包装
以下示例包装了一个 RSA 私有签名密钥。它使用“pkcs8”作为导出格式,并使用 AES-GCM 和密码派生密钥对其进行加密。在 GitHub 中查看完整的代码。
let salt;
let iv;
/*
获取用于作为 deriveKey 方法的输入的密钥材料。
密钥材料是用户提供的密码。
*/
function getKeyMaterial() {
const password = window.prompt("Enter your password");
const enc = new TextEncoder();
return window.crypto.subtle.importKey(
"raw",
enc.encode(password),
{ name: "PBKDF2" },
false,
["deriveBits", "deriveKey"],
);
}
/*
给定密钥材料和随机盐,使用 PBKDF2 派生一个 AES-GCM 密钥。
*/
function getKey(keyMaterial, salt) {
return window.crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt,
iterations: 100000,
hash: "SHA-256",
},
keyMaterial,
{ name: "AES-GCM", length: 256 },
true,
["wrapKey", "unwrapKey"],
);
}
/*
包装给定的密钥。
*/
async function wrapCryptoKey(keyToWrap) {
// 获取密钥加密密钥
const keyMaterial = await getKeyMaterial();
salt = window.crypto.getRandomValues(new Uint8Array(16));
const wrappingKey = await getKey(keyMaterial, salt);
iv = window.crypto.getRandomValues(new Uint8Array(12));
return window.crypto.subtle.wrapKey("pkcs8", keyToWrap, wrappingKey, {
name: "AES-GCM",
iv,
});
}
/*
生成签名/验证密钥对,然后包装其中的私钥。
*/
window.crypto.subtle
.generateKey(
{
name: "RSA-PSS",
// 考虑对需要长期安全性的系统使用 4096 位密钥
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256",
},
true,
["sign", "verify"],
)
.then((keyPair) => wrapCryptoKey(keyPair.privateKey))
.then((wrappedKey) => {
console.log(wrappedKey);
});
SubjectPublicKeyInfo 包装
以下示例包装了一个 RSA 公开加密密钥。它使用“spki”作为导出格式,并使用 AES-CBC 算法和密码派生密钥对其进行加密。在 GitHub 中查看完整的代码。
let salt;
let iv;
/*
获取用于作为 deriveKey 方法的输入的密钥材料。
密钥材料是用户提供的密码。
*/
function getKeyMaterial() {
const password = window.prompt("Enter your password");
const enc = new TextEncoder();
return window.crypto.subtle.importKey(
"raw",
enc.encode(password),
{ name: "PBKDF2" },
false,
["deriveBits", "deriveKey"],
);
}
/*
给定密钥材料和随机盐,使用 PBKDF2 派生一个 AES-CBC 密钥。
*/
function getKey(keyMaterial, salt) {
return window.crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt,
iterations: 100000,
hash: "SHA-256",
},
keyMaterial,
{ name: "AES-CBC", length: 256 },
true,
["wrapKey", "unwrapKey"],
);
}
/*
包装给定的密钥。
*/
async function wrapCryptoKey(keyToWrap) {
// 获取密钥加密密钥
const keyMaterial = await getKeyMaterial();
salt = window.crypto.getRandomValues(new Uint8Array(16));
const wrappingKey = await getKey(keyMaterial, salt);
iv = window.crypto.getRandomValues(new Uint8Array(16));
return window.crypto.subtle.wrapKey("spki", keyToWrap, wrappingKey, {
name: "AES-CBC",
iv,
});
}
/*
生成加密/解密密钥对,然后包装它。
*/
window.crypto.subtle
.generateKey(
{
name: "RSA-OAEP",
// 考虑对需要长期安全性的系统使用 4096 位密钥
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256",
},
true,
["encrypt", "decrypt"],
)
.then((keyPair) => wrapCryptoKey(keyPair.publicKey))
.then((wrappedKey) => console.log(wrappedKey));
JSON Web Key 包装
以下示例包装了一个 ECDSA 私有签名密钥。它使用“jwk”作为导出格式,并使用 AES-GCM 和密码派生密钥对其进行加密。在 GitHub 中查看完整的代码。
let salt;
let iv;
/*
获取用于作为 deriveKey 方法的输入的密钥材料。
密钥材料是用户提供的密码。
*/
function getKeyMaterial() {
const password = window.prompt("Enter your password");
const enc = new TextEncoder();
return window.crypto.subtle.importKey(
"raw",
enc.encode(password),
{ name: "PBKDF2" },
false,
["deriveBits", "deriveKey"],
);
}
/*
给定密钥材料和随机盐,使用 PBKDF2 派生一个 AES-GCM 密钥。
*/
function getKey(keyMaterial, salt) {
return window.crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt,
iterations: 100000,
hash: "SHA-256",
},
keyMaterial,
{ name: "AES-GCM", length: 256 },
true,
["wrapKey", "unwrapKey"],
);
}
/*
包装给定的密钥。
*/
async function wrapCryptoKey(keyToWrap) {
// 获取密钥加密密钥
const keyMaterial = await getKeyMaterial();
salt = window.crypto.getRandomValues(new Uint8Array(16));
const wrappingKey = await getKey(keyMaterial, salt);
iv = window.crypto.getRandomValues(new Uint8Array(12));
return window.crypto.subtle.wrapKey("jwk", keyToWrap, wrappingKey, {
name: "AES-GCM",
iv,
});
}
/*
生成签名/验证密钥对,然后包装其中的私钥。
*/
window.crypto.subtle
.generateKey(
{
name: "ECDSA",
namedCurve: "P-384",
},
true,
["sign", "verify"],
)
.then((keyPair) => wrapCryptoKey(keyPair.privateKey))
.then((wrappedKey) => console.log(wrappedKey));
规范
Specification |
---|
Web Cryptography API # SubtleCrypto-method-wrapKey |
浏览器兼容性
BCD tables only load in the browser