Spec-Zone.ru › Web APIs

SubtleCrypto: метод deriveKey()

Базовая Широко доступная *

Эта функция хорошо зарекомендовала себя и работает на многих устройствах и версиях браузеров. Она доступна в браузерах с июля 2015 года.

* Некоторые части этой функции могут иметь разный уровень поддержки.

  • Узнать больше
  • Полная совместимость
  • Отправить отзыв

Безопасный контекст: Эта функция доступна только в безопасных контекстах (HTTPS), в некоторых или во всех поддерживающих браузерах.

Примечание: Эта функция доступна в Web Workers.

Метод deriveKey() интерфейса SubtleCrypto может быть использован для вывода секретного ключа из мастер-ключа.

Он принимает в качестве аргументов исходный материал ключа, алгоритм вывода и желаемые свойства выводимого ключа. Он возвращает Promise, который будет выполнен с объектом CryptoKey, представляющим новый ключ.

Стоит отметить, что поддерживаемые алгоритмы вывода ключей имеют довольно разные характеристики и подходят для разных ситуаций. Подробности см. в разделе Поддерживаемые алгоритмы.

Синтаксис

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

Параметры

algorithm

Объект, определяющий алгоритм вывода для использования.

  • Для использования ECDH, передайте объект EcdhKeyDeriveParams, указав строку ECDH в свойстве name.
  • Для использования HKDF, передайте объект HkdfParams.
  • Для использования PBKDF2, передайте объект Pbkdf2Params.
  • Для использования X25519, передайте объект EcdhKeyDeriveParams, указав строку X25519 в свойстве name.
baseKey

CryptoKey, представляющий входные данные для алгоритма вывода. Если algorithm — ECDH или X25519, то это будет частный ключ ECDH или X25519. В противном случае это будет исходный материал ключа для функции вывода: например, для PBKDF2 это может быть пароль, импортированный как CryptoKey с помощью SubtleCrypto.importKey().

derivedKeyAlgorithm

Объект, определяющий алгоритм, который будет использоваться для выведенного ключа:

  • Для HMAC передайте объект HmacKeyGenParams.
  • Для AES-CTR, AES-CBC, AES-GCM или AES-KW, передайте объект AesKeyGenParams.
  • Для HKDF, передайте объект HkdfParams.
  • Для PBKDF2, передайте объект Pbkdf2Params.
extractable

Булево значение, указывающее, будет ли возможен экспорт ключа с помощью SubtleCrypto.exportKey() или SubtleCrypto.wrapKey().

keyUsages

Массив Array, указывающий, что можно сделать с выведенным ключом. Обратите внимание, что разрешенные действия по использованию ключа должны быть разрешены алгоритмом, установленным в derivedKeyAlgorithm. Возможные значения массива:

  • encrypt: Ключ может использоваться для шифрования сообщений.
  • decrypt: Ключ может использоваться для дешифрования сообщений.
  • sign: Ключ может использоваться для подписи сообщений.
  • verify: Ключ может использоваться для проверки подписей.
  • deriveKey: Ключ может использоваться для вывода нового ключа.
  • deriveBits: Ключ может использоваться для вывода битов.
  • wrapKey: Ключ может использоваться для заворачивания ключа.
  • unwrapKey: Ключ может использоваться для разворачивания ключа.

Возвращаемое значение

A Promise, который выполняется с CryptoKey.

Исключения

Обещание отклоняется, когда возникает одно из следующих исключений:

InvalidAccessError DOMException

Возникает, когда мастер-ключ не является ключом для запрошенного алгоритма вывода или если значение keyUsages этого ключа не содержит deriveKey.

NotSupported DOMException

Возникает при попытке использовать алгоритм, который неизвестен или не подходит для вывода, или если алгоритм, запрошенный для выведенного ключа, не определяет длину ключа.

SyntaxError DOMException

Возникает, когда keyUsages пусто, но разворачиваемый ключ имеет тип secret или private.

Поддерживаемые алгоритмы

Алгоритмы, поддерживаемые deriveKey() имеют довольно разные характеристики и подходят для разных ситуаций.

Алгоритмы вывода ключей

HKDF

HKDF — это функция вывода ключей. Она разработана для вывода материала ключа из некоторого входного значения с высокой энтропией, например, из результата операции согласования ключей ECDH.

Она не предназначена для вывода ключей из входных данных с относительно низкой энтропией, таких как пароли. Для этого используйте PBKDF2.

HKDF специфицирована в RFC 5869.

PBKDF2

PBKDF2 также является функцией вывода ключей. Она предназначена для вывода материала ключа из входных данных с относительно низкой энтропией, таких как пароль. Она выводит материал ключа, применяя функцию, такую как HMAC, к входному паролю вместе с некоторым солью, и повторяет этот процесс много раз. Чем больше раз повторяется процесс, тем более вычислительно затратным является вывод ключа: это затрудняет атаку с подбором пароля злоумышленнику.

PBKDF2 специфицирована в RFC 2898.

Алгоритмы согласования ключей

ECDH

ECDH (Эллиптическая кривая Диффи-Хеллмана) — это алгоритм согласования ключей. Он позволяет двум участникам, у каждого из которых есть пара открытый/закрытый ключей ECDH, сгенерировать общую секретную информацию: то есть секретную информацию, которую они — и никто другой — делят. Затем они могут использовать эту общую секретную информацию в качестве симметричного ключа для защиты своего общения или могут использовать секрет в качестве входных данных для вывода такого ключа (например, с использованием алгоритма HKDF).

ECDH специфицирована в RFC 6090.

X25519

X25519 — это алгоритм согласования ключей, похожий на ECDH, но построенный на основе эллиптической кривой Curve25519, которая является частью семейства алгоритмов цифровой подписи кривых Эдвардса (EdDSA), определённых в RFC 8032.

Алгоритмы Curve25519 широко используются в криптографии и считаются одними из самых эффективных/быстрых доступных. По сравнению с алгоритмами обмена ключами кривых NIST (Национальный институт стандартов и технологий), используемыми с ECDH, Curve25519 проще в реализации, а их негосударственный источник означает, что решения, лежащие в основе его проектных решений, прозрачны и открыты.

X25519 специфицирована в RFC 7748.

Примеры

Примечание: Вы можете попробовать работающие примеры на GitHub.

ECDH: вывод общего секретного ключа

В этом примере Алиса и Боб каждый генерируют пару ключей ECDH, затем обмениваются открытыми ключами. Затем они используют deriveKey() для вывода общего ключа AES, который они могли бы использовать для шифрования сообщений. См. полный код на GitHub.

/*
Derive an AES key, given:
- our ECDH private key
- their ECDH public key
*/
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() {
  // Generate 2 ECDH key pairs: one for Alice and one for Bob
  // In more normal usage, they would generate their key pairs
  // separately and exchange public keys securely
  let aliceKeyPair = await window.crypto.subtle.generateKey(
    {
      name: "ECDH",
      namedCurve: "P-384",
    },
    false,
    ["deriveKey"],
  );

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

  // Alice then generates a secret key using her private key and Bob's public key.
  let aliceSecretKey = await deriveSecretKey(
    aliceKeyPair.privateKey,
    bobKeyPair.publicKey,
  );

  // Bob generates the same secret key using his private key and Alice's public key.
  let bobSecretKey = await deriveSecretKey(
    bobKeyPair.privateKey,
    aliceKeyPair.publicKey,
  );

  // Alice can then use her copy of the secret key to encrypt a message to Bob.
  let encryptButton = document.querySelector(".ecdh .encrypt-button");
  encryptButton.addEventListener("click", () => {
    encrypt(aliceSecretKey);
  });

  // Bob can use his copy to decrypt the message.
  let decryptButton = document.querySelector(".ecdh .decrypt-button");
  decryptButton.addEventListener("click", () => {
    decrypt(bobSecretKey);
  });
}

X25519: вывод общего секретного ключа

В этом примере Алиса и Боб каждый генерируют пару ключей X25519, затем обмениваются открытыми ключами. Затем каждый из них использует deriveKey() для вывода общего ключа AES из своего закрытого ключа и открытого ключа другого. Они могут использовать этот общий ключ для шифрования и дешифрования обменивающихся сообщений.

HTML

Сначала мы определяем HTML <input>, который вы будете использовать для ввода открытого текста сообщения, которое отправит "Алиса", и кнопку, которую вы можете нажать для запуска процесса шифрования.

<label for="message">Plaintext message from Alice (Enter):</label>
<input
  type="text"
  id="message"
  name="message"
  size="50"
  value="The lion roars near dawn" />
<input id="encrypt-button" type="button" value="Encrypt" />

За этим следуют еще два элемента для отображения шифротекста после того, как Алиса зашифровала открытый текст с помощью своей копии секретного ключа, и для отображения текста после того, как Боб расшифровал его со своей копией секретного ключа.

<div id="results">
  <label for="encrypted">Encrypted (Alice)</label>
  <input
    type="text"
    id="encrypted"
    name="encrypted"
    size="30"
    value=""
    readonly />

  <label for="results">Decrypted (Bob)</label>
  <input
    type="text"
    id="decrypted"
    name="decrypted"
    size="50"
    value=""
    readonly />
</div>

JavaScript

Код ниже показывает, как мы используем deriveKey(). Мы передаём открытый ключ X25519 удалённой стороны, закрытый ключ X25519 локальной стороны и указываем, что полученный ключ должен быть ключом AES-GCM. Мы также устанавливаем полученный ключ как неэкстрагируемый и подходящий для шифрования и дешифрования.

Мы далее используем эту функцию в коде для создания общих ключей для Боба и Алисы.

/*
Derive an AES-GCM key, given:
- our X25519 private key
- their X25519 public key
*/
function deriveSecretKey(privateKey, publicKey) {
  return window.crypto.subtle.deriveKey(
    {
      name: "X25519",
      public: publicKey,
    },
    privateKey,
    {
      name: "AES-GCM",
      length: 256,
    },
    false,
    ["encrypt", "decrypt"],
  );
}

Далее мы определяем функции, которые будет использовать Алиса для кодирования своего открытого текста в UTF-8 и его последующего шифрования, а Боб будет использовать для дешифрования и декодирования сообщения. Обе они принимают в качестве аргументов общий ключ AES, вектор инициализации и текст для шифрования или дешифрования.

Один и тот же вектор инициализации должен использоваться для шифрования и дешифрования, но он не должен быть секретным, поэтому обычно он передаётся вместе с зашифрованным сообщением. Однако в данном случае, так как мы фактически не отправляем сообщение, мы просто делаем его непосредственно доступным.

async function encryptMessage(key, initializationVector, message) {
  try {
    const encoder = new TextEncoder();
    encodedMessage = encoder.encode(message);
    // iv will be needed for decryption
    return await window.crypto.subtle.encrypt(
      { name: "AES-GCM", iv: initializationVector },
      key,
      encodedMessage,
    );
  } catch (e) {
    console.log(e);
    return `Encoding error`;
  }
}

async function decryptMessage(key, initializationVector, ciphertext) {
  try {
    const decryptedText = await window.crypto.subtle.decrypt(
      // The iv value must be the same as that used for encryption
      { name: "AES-GCM", iv: initializationVector },
      key,
      ciphertext,
    );

    const utf8Decoder = new TextDecoder();
    return utf8Decoder.decode(decryptedText);
  } catch (e) {
    console.log(e);
    return "Decryption error";
  }
}

Функция agreeSharedSecretKey() ниже вызывается при загрузке для генерации пар и общих ключей для Алисы и Боба. Она также добавляет обработчик щелчка для кнопки "Зашифровать", который будет запускать шифрование и затем дешифрование текста, определённого в первом <input>. Обратите внимание, что весь код находится внутри обработчика try...catch, чтобы убедиться, что мы можем записать случай, когда генерация ключа терпит неудачу, поскольку алгоритм X25519 не поддерживается.

async function agreeSharedSecretKey() {
  try {
    // Generate 2 X25519 key pairs: one for Alice and one for Bob
    // In more normal usage, they would generate their key pairs
    // separately and exchange public keys securely
    const aliceKeyPair = await window.crypto.subtle.generateKey(
      {
        name: "X25519",
      },
      false,
      ["deriveKey"],
    );

    log(
      `Created Alice's key pair: (algorithm: ${JSON.stringify(
        aliceKeyPair.privateKey.algorithm,
      )}, usages: ${aliceKeyPair.privateKey.usages})`,
    );

    const bobKeyPair = await window.crypto.subtle.generateKey(
      {
        name: "X25519",
      },
      false,
      ["deriveKey"],
    );

    log(
      `Created Bob's key pair: (algorithm: ${JSON.stringify(
        bobKeyPair.privateKey.algorithm,
      )}, usages: ${bobKeyPair.privateKey.usages})`,
    );

    // Alice then generates a secret key using her private key and Bob's public key.
    const aliceSecretKey = await deriveSecretKey(
      aliceKeyPair.privateKey,
      bobKeyPair.publicKey,
    );

    log(
      `aliceSecretKey: ${aliceSecretKey.type} (algorithm: ${JSON.stringify(
        aliceSecretKey.algorithm,
      )}, usages: ${aliceSecretKey.usages}), `,
    );

    // Bob generates the same secret key using his private key and Alice's public key.
    const bobSecretKey = await deriveSecretKey(
      bobKeyPair.privateKey,
      aliceKeyPair.publicKey,
    );

    log(
      `bobSecretKey: ${bobSecretKey.type} (algorithm: ${JSON.stringify(
        bobSecretKey.algorithm,
      )}, usages: ${bobSecretKey.usages}), \n`,
    );

    // Get access for the encrypt button and the three inputs
    const encryptButton = document.querySelector("#encrypt-button");
    const messageInput = document.querySelector("#message");
    const encryptedInput = document.querySelector("#encrypted");
    const decryptedInput = document.querySelector("#decrypted");

    encryptButton.addEventListener("click", async () => {
      log(`Plaintext: ${messageInput.value}`);

      // Define the initialization vector used when encrypting and decrypting.
      // This must be regenerated for every message!
      const initializationVector = window.crypto.getRandomValues(
        new Uint8Array(8),
      );

      // Alice can use her copy of the shared key to encrypt the message.
      const encryptedMessage = await encryptMessage(
        aliceSecretKey,
        initializationVector,
        messageInput.value,
      );

      // We then display part of the encrypted buffer and log the encrypted message
      let buffer = new Uint8Array(encryptedMessage, 0, 5);
      encryptedInput.value = `${buffer}...[${encryptedMessage.byteLength} bytes total]`;

      log(
        `encryptedMessage: ${buffer}...[${encryptedMessage.byteLength} bytes total]`,
      );

      // Bob uses his shared secret key to decrypt the message.
      const decryptedCiphertext = await decryptMessage(
        bobSecretKey,
        initializationVector,
        encryptedMessage,
      );

      decryptedInput.value = decryptedCiphertext;
      log(`decryptedCiphertext: ${decryptedCiphertext}\n`);
    });
  } catch (e) {
    log(e);
  }
}

// Finally we call the method to set the example running.
agreeSharedSecretKey();

Результат

Нажмите кнопку "Зашифровать", чтобы зашифровать текст в верхнем <input> элементе, отобразив зашифрованный шифротекст и расшифрованный текст в следующих двух элементах. Область журнала внизу содержит информацию о ключах, которые генерируются кодом.

PBKDF2: вывод ключа AES из пароля

В этом примере мы запрашиваем у пользователя пароль, затем используем его для вывода ключа AES с помощью PBKDF2, а затем используем ключ AES для шифрования сообщения. См. полный код на GitHub.

/*
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),
    "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);
}

HKDF: вывод ключа AES из общего секрета

В этом примере мы шифруем сообщение plainText с учётом общего секрета secret, который сам может быть выведен с помощью такого алгоритма, как ECDH. Вместо непосредственного использования общего секрета, мы используем его в качестве ключевого материала для функции HKDF, чтобы вывести ключ шифрования AES-GCM, который затем используется для шифрования сообщения. См. полный код на GitHub.

/*
  Given some key material and some random salt,
  derive an AES-GCM key using HKDF.
  */
function getKey(keyMaterial, salt) {
  return window.crypto.subtle.deriveKey(
    {
      name: "HKDF",
      salt: salt,
      info: new TextEncoder().encode("Encryption example"),
      hash: "SHA-256",
    },
    keyMaterial,
    { name: "AES-GCM", length: 256 },
    true,
    ["encrypt", "decrypt"],
  );
}

async function encrypt(secret, plainText) {
  const message = {
    salt: window.crypto.getRandomValues(new Uint8Array(16)),
    iv: window.crypto.getRandomValues(new Uint8Array(12)),
  };

  const key = await getKey(secret, message.salt);

  message.ciphertext = await window.crypto.subtle.encrypt(
    {
      name: "AES-GCM",
      iv: message.iv,
    },
    key,
    plainText,
  );

  return message;
}

Спецификации

Спецификация
API криптографии веб-браузера
# SubtleCrypto-method-deriveKey

Совместимость с браузерами

Рабочий стол Мобильные устройства
Chrome Edge Firefox Opera Safari Chrome Android Firefox для Android Opera Android Safari на IOS Samsung Internet WebView Android
deriveKey 41 79
12–79["Не поддерживается: ECDH.", "Не поддерживаются: HKDF, PBKDF2."]
34 28 11 41 34 28 11 4.0 41
derivedKeyAlgorithm_option_aes 41 79 34 28 11 41 34 28 11 4.0 41
derivedKeyAlgorithm_option_hkdf 41 79 119 28 11 41 119 28 11 4.0 41
derivedKeyAlgorithm_option_hmac 41 79 34 28 11 41 34 28 11 4.0 41
derivedKeyAlgorithm_option_pbkdf2 41 79 119 28 11 41 119 28 11 4.0 41
x25519 133 133 130 Нет 17 133 130 Нет 17 Нет 133

См. также

  • Спецификация HKDF.
  • Рекомендации NIST по выводу ключей на основе паролей.
  • Справочник по хранению паролей.
  • Советы по выбору количества итераций для PBKDF2.

© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API