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.
- Для использования ECDH, передайте объект
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.
- Для HMAC передайте объект
extractable-
Булево значение, указывающее, будет ли возможен экспорт ключа с помощью
SubtleCrypto.exportKey()илиSubtleCrypto.wrapKey(). keyUsages-
Массив
Array, указывающий, что можно сделать с выведенным ключом. Обратите внимание, что разрешенные действия по использованию ключа должны быть разрешены алгоритмом, установленным вderivedKeyAlgorithm. Возможные значения массива:-
encrypt: Ключ может использоваться для шифрования сообщений. -
decrypt: Ключ может использоваться для дешифрования сообщений. -
sign: Ключ может использоваться для подписи сообщений. -
verify: Ключ может использоваться для проверки подписей. -
deriveKey: Ключ может использоваться для вывода нового ключа. -
deriveBits: Ключ может использоваться для вывода битов. -
wrapKey: Ключ может использоваться для заворачивания ключа. -
unwrapKey: Ключ может использоваться для разворачивания ключа.
-
Возвращаемое значение
Исключения
Обещание отклоняется, когда возникает одно из следующих исключений:
-
InvalidAccessErrorDOMException -
Возникает, когда мастер-ключ не является ключом для запрошенного алгоритма вывода или если значение
keyUsagesэтого ключа не содержитderiveKey. -
NotSupportedDOMException -
Возникает при попытке использовать алгоритм, который неизвестен или не подходит для вывода, или если алгоритм, запрошенный для выведенного ключа, не определяет длину ключа.
-
SyntaxErrorDOMException -
Возникает, когда
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;
}
Спецификации
Совместимость с браузерами
| Рабочий стол | Мобильные устройства | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
| Chrome | Edge | Firefox | Opera | Safari | Chrome Android | Firefox для Android | Opera Android | Safari на IOS | Samsung Internet | WebView Android | |
deriveKey |
41 | 7912–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 |
См. также
© 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