В криптографических системах часто возникает задача: из одного исходного секрета получить несколько независимых ключей с разными назначениями. Такой исходный секрет обычно называют мастер-ключом или корневым материалом (keying material). В Web Crypto API это реализуется через механизм деривации ключей, позволяющий получать новые криптографические ключи без повторного хранения или передачи исходного секрета.
K_{derived} = (K_{master}, , )
Ключевой принцип: один и тот же мастер-ключ должен порождать разные производные ключи при разных контекстных параметрах, сохраняя криптографическую независимость результатов.
Разделение ключей по назначению — базовая практика криптографической инженерии:
Использование одного и того же ключа для всех операций приводит к следующим проблемам:
Деривация ключей позволяет устранить эти риски за счет строгого разделения контекстов.
В Web Crypto API основными механизмами генерации производных ключей являются:
Создаёт новый криптографический ключ из исходного материала.
crypto.subtle.deriveKey(
algorithm,
baseKey,
derivedKeyAlgorithm,
extractable,
keyUsages
)
Генерирует необработанные байты ключевого материала:
crypto.subtle.deriveBits(
algorithm,
baseKey,
length
)
Используется для загрузки мастер-ключа в криптографический контекст:
crypto.subtle.importKey(
format,
keyData,
algorithm,
extractable,
usages
)
HKDF (HMAC-based Key Derivation Function) — наиболее рекомендуемый способ получения нескольких ключей из одного мастер-секрета. Он основан на HMAC и поддерживает строгую изоляцию контекстов через параметр info.
K_{out} = (K_{in}, , )
const masterKeyMaterial = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode("master-secret"),
"HKDF",
false,
["deriveKey", "deriveBits"]
);
const encryptionKey = await crypto.subtle.deriveKey(
{
name: "HKDF",
hash: "SHA-256",
salt: crypto.getRandomValues(new Uint8Array(16)),
info: new TextEncoder().encode("encryption-key")
},
masterKeyMaterial,
{
name: "AES-GCM",
length: 256
},
false,
["encrypt", "decrypt"]
);
const signingKey = await crypto.subtle.deriveKey(
{
name: "HKDF",
hash: "SHA-256",
salt: crypto.getRandomValues(new Uint8Array(16)),
info: new TextEncoder().encode("signing-key")
},
masterKeyMaterial,
{
name: "HMAC",
hash: "SHA-256",
length: 256
},
false,
["sign", "verify"]
);
PBKDF2 чаще используется для получения мастер-ключа из пароля, но также может служить базой для дальнейшей деривации.
K = (P, S, c, dkLen)
Где:
Пример:
const baseKey = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode("user-password"),
"PBKDF2",
false,
["deriveKey"]
);
const masterKey = await crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: crypto.getRandomValues(new Uint8Array(16)),
iterations: 100000,
hash: "SHA-256"
},
baseKey,
{
name: "HKDF"
},
false,
["deriveKey"]
);
PBKDF2 редко используется напрямую для создания нескольких независимых ключей, но часто является стартовой точкой цепочки деривации.
Типовая архитектура включает два уровня:
const master = await crypto.subtle.importKey(
"raw",
crypto.getRandomValues(new Uint8Array(32)),
"HKDF",
false,
["deriveKey"]
);
const contexts = [
"db-encryption",
"session-auth",
"file-storage"
];
const keys = {};
for (const ctx of contexts) {
keys[ctx] = await crypto.subtle.deriveKey(
{
name: "HKDF",
hash: "SHA-256",
salt: crypto.getRandomValues(new Uint8Array(16)),
info: new TextEncoder().encode(ctx)
},
master,
{
name: "AES-GCM",
length: 256
},
false,
["encrypt", "decrypt"]
);
}
Ключевой элемент безопасности — уникальность параметра info. Он выполняет роль «пространства имен» для ключа.
K_1 K_2 info
Даже при одинаковом мастер-ключе изменение info приводит к полностью независимым производным ключам.
Типичные значения info:
Использование одинаковых параметров приводит к одинаковым ключам.
Если info слишком общий (например, “key”), возникает риск коллизий.
Даже при деривации ключи должны строго соответствовать назначению (AES ≠ HMAC).
Мастер-ключ должен существовать только в памяти Web Crypto и быть недоступен напрямую.
В сложных системах используется иерархическая структура:
K_{service} = (K_{master}, info_{service}) K_{function} = (K_{service}, info_{function})
Такой подход позволяет строить безопасную многоуровневую систему ключей без увеличения количества хранимых секретов.