SubtleCrypto.deriveKey()

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

SubtleCrypto 接口的 deriveKey() 方法用于从主密钥派生密钥。

它以基本密钥、使用的派生算法和派生密钥所需的属性为参数。返回一个 Promise,会兑现一个表示新密钥的 CryptoKey 对象。

值得注意的是,你可以使用的三种密钥派生算法有截然不同的特性,而适用于截然不同的情况。参见支持的算法以获取详细信息。

语法

js
deriveKey(algorithm, baseKey, derivedKeyAlgorithm, extractable, keyUsages)

参数

algorithm

一个对象,用于指定使用的派生算法

baseKey

一个 CryptoKey,表示派生算法的输入。如果算法(algorithm)为 ECDH,则该对象为 ECDH 的私钥。否则,它为派生函数的初始密钥材料(key material):例如,对于 PBKDF2,它可能是一个密码(使用 SubtleCrypto.importKey() 导入为一个 CryptoKey 对象)。

derivedKeyAlgorithm

一个用于派生密钥算法的对象。

extractable

一个布尔值,表示是否可以使用 SubtleCrypto.exportKey()SubtleCrypto.wrapKey() 来导出密钥。

keyUsages

一个数组,表示派生出来的密钥的用途。注意,密钥的用法必须是 derivedKeyAlgorithm 设置的算法所允许的。数组元素可能的值有:

返回值

一个 Promise,会兑现一个 CryptoKey

异常

当发生一下几种异常时,promise 会被拒绝:

InvalidAccessError DOMException

若主密钥与要求的派生算法所使用的密钥类型不匹配,或密钥的 keyUsages 的值中不包含 deriveKey,则会抛出此异常。

NotSupported DOMException

若尝试使用未知或不适用于派生的算法,或用于派生密钥的算法(algorithm)没有定义密钥长度(key length),则会抛出此异常。

SyntaxError DOMException

keyUsages 是空的,而解包装密钥的类型是 secretprivate,则抛出此异常。

支持的算法

deriveKey() 支持的三种算法各有特点而适用于不同的场合。

ECDH

ECDH(椭圆曲线迪菲—赫尔曼密钥交换,Elliptic Curve Diffie-Hellman)是一种密钥协商算法。它使每个人都能拥有用于生成共享密钥的 ECDH 公钥/私钥对:即,密钥仅在两人之间共享(而不包括其他人)。然后他们可以使用这个共享密钥作为对称密钥来保护他们的通信,或可以使用密钥来作为派生同类密钥(例如,使用 HKDF 算法)的输入。

ECDH 的规范定于 RFC 6090

HKDF

HKDF 是一种密钥派生函数。它被用于从一些熵值相对较高的输入(如 ECDH 密钥协商操作的输出)派生密钥材料。

它并用于从熵值相对较低的输入(例如密码)派生密钥。对于此种用途,请使用 PBKDF2。

HKDF 的规范定于 RFC 5869

PBKDF2

PBKDF2 也是一种密钥派生函数。它被用于从一些熵值相对较低的输入(例如密码)派生密钥材料。它通过将例如 HMAC 等函数以及加盐(salt)操作等一起应用到输入密码上,并多次重复此过程来派生密钥材料。这个过程重复的次数越多,密钥推导计算的成本就越高:这使得攻击者难以使用字典攻击这类暴力破解的方法来找出密钥。

PBKDF2 的规范定于 RFC 2898

示例

备注: 你可以在 GitHub 上尝试可用的示例

ECDH

在此示例中,Alice 和 Bob 分别生成了一个 ECDH 密钥对,然后相互交换公钥。并使用 deriveKey() 来派生一个可用于加密消息的共享 AES 密钥。在 GitHub 上查看完整代码。

js
/*
派生 AES 密钥,需要提供:
- 自己的 ECDH 私钥
- 对方的 ECDH 公钥
*/
function deriveSecretKey(privateKey, publicKey) {
  return window.crypto.subtle.deriveKey(
    {
      name: "ECDH",
      public: publicKey,
    },
    privateKey,
    {
      name: "AES-GCM",
      length: 256,
    },
    false,
    ["encrypt", "decrypt"],
  );
}

async function agreeSharedSecretKey() {
  // 生成两个 ECDH 密钥对:一个是 Alice 的,一个是 Bob 的
  // 在正常的使用情况下,他们会单独生成密钥对,并安全地交换公钥。
  let alicesKeyPair = await window.crypto.subtle.generateKey(
    {
      name: "ECDH",
      namedCurve: "P-384",
    },
    false,
    ["deriveKey"],
  );

  let bobsKeyPair = await window.crypto.subtle.generateKey(
    {
      name: "ECDH",
      namedCurve: "P-384",
    },
    false,
    ["deriveKey"],
  );

  // 然后 Alice 使用她的私钥和 Bob 的公钥生成密钥(secret key)。
  let alicesSecretKey = await deriveSecretKey(
    alicesKeyPair.privateKey,
    bobsKeyPair.publicKey,
  );

  // Bob 使用他的私钥和 Alice 的公钥来生成相同的密钥。
  let bobsSecretKey = await deriveSecretKey(
    bobsKeyPair.privateKey,
    alicesKeyPair.publicKey,
  );

  // Alice 可以使用她的密钥拷贝来加密发送给 Bob 的消息。
  let encryptButton = document.querySelector(".ecdh .encrypt-button");
  encryptButton.addEventListener("click", () => {
    encrypt(alicesSecretKey);
  });

  // Bob 可以使用他的拷贝来解密消息。
  let decryptButton = document.querySelector(".ecdh .decrypt-button");
  decryptButton.addEventListener("click", () => {
    decrypt(bobsSecretKey);
  });
}

PBKDF2

在此示例中,我们要求用户提供密码,然后使用 PBKDF2 派生 AES 密钥,并使用 AES 密钥来加密消息。 在 GitHub 上查看完整代码。

js
/*
获取用于作为 deriveKey 方法的输入的密钥材料。
密钥材料是用户提供的密码。
*/
function getKeyMaterial() {
  const password = window.prompt("请输入你的密码");
  const enc = new TextEncoder();
  return window.crypto.subtle.importKey(
    "raw",
    enc.encode(password),
    "PBKDF2",
    false,
    ["deriveBits", "deriveKey"],
  );
}

async function encrypt(plaintext, salt, iv) {
  const keyMaterial = await getKeyMaterial();
  const key = await window.crypto.subtle.deriveKey(
    {
      name: "PBKDF2",
      salt,
      iterations: 100000,
      hash: "SHA-256",
    },
    keyMaterial,
    { name: "AES-GCM", length: 256 },
    true,
    ["encrypt", "decrypt"],
  );

  return window.crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, plaintext);
}

规范

Specification
Web Cryptography API
# SubtleCrypto-method-deriveKey

浏览器兼容性

BCD tables only load in the browser

参见