SubtleCrypto：wrapKey() 方法
=========================

**安全上下文:** 此项功能仅在一些支持的浏览器的安全上下文（HTTPS）中可用。

`SubtleCrypto` 接口的 **`wrapKey()`** 方法用于“包装”（wrap）密钥。这一味着它以外部可移植的格式导出密钥，然后对其进行加密。包装密钥有助于在不受信任的环境中保护它，例如在未受保护的数据存储，或在未受保护的网络上进行传输。

与 `SubtleCrypto.exportKey()` 一样，你需要指定密钥的导出格式。要导出密钥，必须将 `CryptoKey.extractable` (en-US) 设置为 `true`。

但是，由于 `wrapKey()` 还会对要导出的密钥进行加密，因此还需要传入用于加密的密钥。这有时被称为“包装密钥”（wrapping key）。

`wrapKey()` 的逆运算是 `SubtleCrypto.unwrapKey()`：`wrapKey` 由导出 + 加密组成，而 `unwrapKey` 由导入 + 解密组成。

语法
--


```js
wrapKey(format, key, wrappingKey, wrapAlgo)
```

### 参数

`format`

描述密钥在加密之前所导出的数据格式的字符串。它可以是以下值之一：

`raw`

Raw 格式。

`pkcs8`

PKCS #8 格式。

`spki`

SubjectPublicKeyInfo 格式。

`jwk`

JSON Web Key 格式。


`key`

将被包装的密钥。

`wrappingkey`

用于加密导出密钥的密钥。密钥的用途必须包括 `wrapKey`。

`wrapAlgo`

指定用于加密导出密钥的算法的对象，以及任何所需的额外参数：

* 对于 RSA-OAEP，请传入 `RsaOaepParams` (en-US) 对象。
* 对于 AES-CTR，请传入 `AesCtrParams` (en-US) 对象。
* 对于 AES-CBC，请传入 `AesCbcParams` (en-US) 对象。
* 对于 AES-GCM，请传入 `AesGcmParams` (en-US) 对象。
* 对于 AES-KW，请传入字符串 `"AES-KW"`，或 `{ "name": "AES-KW" }` 形式的对象。

### 返回值

一个 `Promise`，会兑现一个包含已加密的导出密钥的 `ArrayBuffer`。

### 异常

当遇到以下异常时，promise 将会被拒绝：

`InvalidAccessError` `DOMException`

当包装密钥不是要求的包装算法的密钥时触发。

`NotSupported` `DOMException`

当尝试使用未知或不适用于加密/包装的算法时触发。

`TypeError`

当尝试使用无效格式时触发。

支持的算法
-----

所有可用于加密的算法，只要设置了“wrapKey”用途，也都可以用于密钥包装。对于密钥包装，你还可以使用 AES-KW。

### 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 中查看完整的代码。


```js
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 中查看完整的代码。


```js
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 中查看完整的代码。


```js
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 中查看完整的代码。


```js
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 with JavaScript enabled. Enable JavaScript to view data.

参见
--

* `SubtleCrypto.exportKey()`
* PKCS #8 格式。
* SubjectPublicKeyInfo 格式。
* JSON Web Key 格式。
* AES-KW 规范。