SubtleCrypto: wrapKey() 方法

Baseline 已廣泛支援

此特性已相當成熟,可在許多裝置和瀏覽器版本上使用。自 ⁨2020 年 1 月⁩ 起,所有主流瀏覽器均已支援。

安全上下文: 此功能僅在安全上下文(HTTPS)中可用,且支援此功能的瀏覽器數量有限。

注意:此功能在 Web Workers 中可用。

SubtleCrypto 介面的 wrapKey() 方法用於“包裝”一個金鑰。這意味著它會以外部、可移植的格式匯出金鑰,然後加密匯出的金鑰。包裝金鑰有助於在不受信任的環境中保護它,例如在未受保護的資料儲存中或在不受保護的網路上傳輸時。

SubtleCrypto.exportKey() 一樣,您需要為金鑰指定一個 匯出格式。要匯出金鑰,其 CryptoKey.extractable 必須設定為 true

但由於 wrapKey() 還會加密要匯出的金鑰,因此您還需要傳入必須用於加密它的金鑰。這有時被稱為“包裝金鑰”。

wrapKey() 的逆操作是 SubtleCrypto.unwrapKey()wrapKey 由匯出 + 加密組成,而 unwrapKey 由匯入 + 解密組成。

語法

js
wrapKey(format, key, wrappingKey, wrapAlgo)

引數

格式(format)

一個描述金鑰將要匯出的資料格式的字串,之後會被加密。它可以是以下之一:

raw

原始格式。

pkcs8

PKCS #8格式。

spki

SubjectPublicKeyInfo格式。

jwk

JSON Web Key格式。

key

要包裝的 CryptoKey

wrappingkey

用於加密匯出金鑰的 CryptoKey。該金鑰必須具有 wrapKey 用途。

wrapAlgo

一個物件,指定用於加密匯出金鑰的 演算法以及任何必需的額外引數。

返回值

一個 Promise,該 Promise 會 fulfilled 為一個包含加密後的匯出金鑰的 ArrayBuffer

異常

當遇到以下任一異常時,Promise 將被拒絕:

InvalidAccessError DOMException

當包裝金鑰不是請求的包裝演算法的金鑰時丟擲。

NotSupported DOMException

嘗試使用未知或不適合加密或包裝的演算法時丟擲。

TypeError

當嘗試使用無效格式時丟擲。

支援的演算法

所有 可用於加密的演算法 也可用於金鑰包裝,前提是金鑰具有“wrapKey”用途。對於金鑰包裝,您還可以選擇 AES-KW

AES-KW

AES-KW 是一種使用 AES 密碼進行金鑰包裝的方法。

與 AES-GCM 等其他 AES 模式相比,使用 AES-KW 的一個優點是 AES-KW 不需要初始化向量。要使用 AES-KW,輸入必須是 64 位的倍數。

AES-KW 在 RFC 3394 中有規定。

示例

注意: 您可以在 GitHub 上 嘗試這些工作示例

Raw wrap

此示例包裝一個 AES 金鑰。它使用“raw”作為匯出格式,並使用 AES-KW(帶有一個密碼派生金鑰)來加密它。在 GitHub 上檢視完整程式碼。

js
let salt;

/*
Get some key material to use as input to the deriveKey method.
The key material is a password supplied by the user.
*/
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"],
  );
}

/*
Given some key material and some random salt
derive an AES-KW key using PBKDF2.
*/
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"],
  );
}

/*
Wrap the given key.
*/
async function wrapCryptoKey(keyToWrap) {
  // get the key encryption key
  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");
}

/*
Generate an encrypt/decrypt secret key,
then wrap it.
*/
window.crypto.subtle
  .generateKey(
    {
      name: "AES-GCM",
      length: 256,
    },
    true,
    ["encrypt", "decrypt"],
  )
  .then((secretKey) => wrapCryptoKey(secretKey))
  .then((wrappedKey) => console.log(wrappedKey));

PKCS #8 wrap

此示例包裝一個 RSA 私有簽名金鑰。它使用“pkcs8”作為匯出格式,並使用 AES-GCM(帶有一個密碼派生金鑰)來加密它。在 GitHub 上檢視完整程式碼。

js
let salt;
let iv;

/*
Get some key material to use as input to the deriveKey method.
The key material is a password supplied by the user.
*/
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"],
  );
}

/*
Given some key material and some random salt
derive an AES-GCM key using PBKDF2.
*/
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"],
  );
}

/*
Wrap the given key.
*/
async function wrapCryptoKey(keyToWrap) {
  // get the key encryption key
  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,
  });
}

/*
Generate a sign/verify key pair,
then wrap the private key.
*/
window.crypto.subtle
  .generateKey(
    {
      name: "RSA-PSS",
      // Consider using a 4096-bit key for systems that require long-term security
      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 wrap

此示例包裝一個 RSA 公有加密金鑰。它使用“spki”作為匯出格式,並使用 AES-CBC(帶有一個密碼派生金鑰)來加密它。在 GitHub 上檢視完整程式碼。

js
let salt;
let iv;

/*
Get some key material to use as input to the deriveKey method.
The key material is a password supplied by the user.
*/
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"],
  );
}

/*
Given some key material and some random salt
derive an AES-CBC key using PBKDF2.
*/
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"],
  );
}

/*
Wrap the given key.
*/
async function wrapCryptoKey(keyToWrap) {
  // get the key encryption key
  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,
  });
}

/*
Generate an encrypt/decrypt key pair,
then wrap it.
*/
window.crypto.subtle
  .generateKey(
    {
      name: "RSA-OAEP",
      // Consider using a 4096-bit key for systems that require long-term security
      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 wrap

此示例包裝一個 ECDSA 私有簽名金鑰。它使用“jwk”作為匯出格式,並使用 AES-GCM(帶有一個密碼派生金鑰)來加密它。在 GitHub 上檢視完整程式碼。

js
let salt;
let iv;

/*
Get some key material to use as input to the deriveKey method.
The key material is a password supplied by the user.
*/
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"],
  );
}

/*
Given some key material and some random salt
derive an AES-GCM key using PBKDF2.
*/
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"],
  );
}

/*
Wrap the given key.
*/
async function wrapCryptoKey(keyToWrap) {
  // get the key encryption key
  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,
  });
}

/*
Generate a sign/verify key pair,
then wrap the private key
*/
window.crypto.subtle
  .generateKey(
    {
      name: "ECDSA",
      namedCurve: "P-384",
    },
    true,
    ["sign", "verify"],
  )
  .then((keyPair) => wrapCryptoKey(keyPair.privateKey))
  .then((wrappedKey) => console.log(wrappedKey));

規範

規範
Web 加密級別 2
# SubtleCrypto-method-wrapKey

瀏覽器相容性

另見