SubtleCrypto: Methode deriveKey()
Baseline
Weitgehend verfügbar
*
Diese Funktion ist gut etabliert und funktioniert auf vielen Geräten und in vielen Browserversionen. Sie ist seit Januar 2020 browserübergreifend verfügbar.
* Einige Teile dieser Funktion werden möglicherweise unterschiedlich gut unterstützt.
Sicherer Kontext: Diese Funktion ist nur in sicheren Kontexten (HTTPS) in einigen oder allen unterstützenden Browsern verfügbar.
Hinweis: Diese Funktion ist in Web Workers verfügbar.
Die Methode deriveKey() der Schnittstelle SubtleCrypto kann verwendet werden, um aus einem Hauptschlüssel einen geheimen Schlüssel abzuleiten.
Sie erhält als Argumente das anfängliche Schlüsselmaterial, den zu verwendenden Ableitungsalgorithmus und die gewünschten Eigenschaften des abzuleitenden Schlüssels. Sie gibt ein Promise zurück, das mit einem CryptoKey-Objekt erfüllt wird, das den neuen Schlüssel repräsentiert.
Die unterstützten Schlüsselableitungsalgorithmen haben sehr unterschiedliche Eigenschaften und eignen sich für unterschiedliche Situationen. Weitere Einzelheiten finden Sie unter Unterstützte Algorithmen.
Syntax
deriveKey(algorithm, baseKey, derivedKeyType, extractable, keyUsages)
Parameter
algorithm-
Ein Objekt, das den zu verwendenden Ableitungsalgorithmus definiert.
- Für ECDH übergeben Sie ein
EcdhKeyDeriveParams-Objekt und geben die ZeichenfolgeECDHals Eigenschaftnamean. - Für HKDF übergeben Sie ein
HkdfParams-Objekt. - Für PBKDF2 übergeben Sie ein
Pbkdf2Params-Objekt. - Für X25519 übergeben Sie ein
EcdhKeyDeriveParams-Objekt und geben die ZeichenfolgeX25519als Eigenschaftnamean.
- Für ECDH übergeben Sie ein
baseKey-
Ein
CryptoKey, der die Eingabe für den Ableitungsalgorithmus repräsentiert. WennalgorithmECDH oder X25519 ist, handelt es sich dabei um den privaten ECDH- beziehungsweise X25519-Schlüssel. Andernfalls handelt es sich um das anfängliche Schlüsselmaterial für die Ableitungsfunktion: Bei PBKDF2 könnte dies beispielsweise ein Passwort sein, das mithilfe vonSubtleCrypto.importKey()alsCryptoKeyimportiert wurde. derivedKeyType-
Ein Objekt, das den Algorithmus definiert, für den der abgeleitete Schlüssel verwendet wird:
- Für HMAC übergeben Sie ein
HmacImportParams-Objekt. - Für AES-CTR, AES-CBC, AES-GCM oder AES-KW übergeben Sie ein
AesDerivedKeyParams-Objekt.
- Für HMAC übergeben Sie ein
extractable-
Ein boolescher Wert, der angibt, ob der Schlüssel mithilfe von
SubtleCrypto.exportKey()oderSubtleCrypto.wrapKey()exportiert werden kann. keyUsages-
Ein
Array, das angibt, wofür der abgeleitete Schlüssel verwendet werden kann. Beachten Sie, dass die Verwendungszwecke des Schlüssels durch den inderivedKeyTypefestgelegten Algorithmus zulässig sein müssen. Mögliche Werte des Arrays sind:encrypt: Der Schlüssel kann zum Verschlüsseln von Nachrichten verwendet werden.decrypt: Der Schlüssel kann zum Entschlüsseln von Nachrichten verwendet werden.sign: Der Schlüssel kann zum Signieren von Nachrichten verwendet werden.verify: Der Schlüssel kann zum Überprüfen von Signaturen verwendet werden.deriveKey: Der Schlüssel kann zum Ableiten eines neuen Schlüssels verwendet werden.deriveBits: Der Schlüssel kann zum Ableiten von Bits verwendet werden.wrapKey: Der Schlüssel kann zum Verpacken eines Schlüssels verwendet werden.unwrapKey: Der Schlüssel kann zum Entpacken eines Schlüssels verwendet werden.
Rückgabewert
Ausnahmen
Das Promise wird zurückgewiesen, wenn eine der folgenden Ausnahmen auftritt:
InvalidAccessErrorDOMException-
Wird ausgelöst, wenn der Hauptschlüssel kein Schlüssel für den angeforderten Ableitungsalgorithmus ist oder wenn der Wert
keyUsagesdieses SchlüsselsderiveKeynicht enthält. NotSupportedErrorDOMException-
Wird ausgelöst, wenn versucht wird, einen unbekannten oder für die Ableitung ungeeigneten Algorithmus zu verwenden, oder wenn der für den abgeleiteten Schlüssel angeforderte Algorithmus keine Schlüssellänge definiert.
SyntaxErrorDOMException-
Wird ausgelöst, wenn
keyUsagesleer ist, der entpackte Schlüssel aber den Typsecretoderprivatehat.
Unterstützte Algorithmen
Die von deriveKey() unterstützten Algorithmen haben sehr unterschiedliche Eigenschaften und eignen sich für unterschiedliche Situationen.
Schlüsselableitungsalgorithmen
HKDF
HKDF ist eine Schlüsselableitungsfunktion. Sie ist dafür konzipiert, Schlüsselmaterial aus einer Eingabe mit hoher Entropie abzuleiten, beispielsweise aus dem Ergebnis einer ECDH-Schlüsselvereinbarung.
Sie ist nicht dafür konzipiert, Schlüssel aus Eingaben mit vergleichsweise geringer Entropie wie Passwörtern abzuleiten. Verwenden Sie dafür PBKDF2.
HKDF ist in RFC 5869 spezifiziert.
PBKDF2
PBKDF2 ist ebenfalls eine Schlüsselableitungsfunktion. Sie ist dafür konzipiert, Schlüsselmaterial aus einer Eingabe mit vergleichsweise geringer Entropie abzuleiten, beispielsweise aus einem Passwort. Dazu wird eine Funktion wie HMAC zusammen mit einem Salt auf das eingegebene Passwort angewendet und dieser Vorgang vielfach wiederholt. Je häufiger der Vorgang wiederholt wird, desto rechenaufwendiger ist die Schlüsselableitung. Dadurch wird es für Angreifer schwieriger, den Schlüssel mittels Brute-Force und eines Wörterbuchangriffs zu ermitteln.
PBKDF2 ist in RFC 2898 spezifiziert.
Schlüsselvereinbarungsalgorithmen
ECDH
ECDH (Elliptic Curve Diffie–Hellman) ist ein Schlüsselvereinbarungsalgorithmus. Er ermöglicht es zwei Personen, die jeweils ein öffentliches und privates ECDH-Schlüsselpaar besitzen, ein gemeinsames Geheimnis zu erzeugen, das nur sie beide kennen. Dieses gemeinsame Geheimnis können sie anschließend als symmetrischen Schlüssel zur Absicherung ihrer Kommunikation verwenden oder als Eingabe nutzen, um einen solchen Schlüssel abzuleiten, beispielsweise mit dem HKDF-Algorithmus.
ECDH ist in RFC 6090 spezifiziert.
X25519
X25519 ist wie ECDH ein Schlüsselvereinbarungsalgorithmus, basiert jedoch auf der elliptischen Kurve Curve25519, die zur Familie der in RFC 8032 definierten Edwards-Curve-Digital-Signature-Algorithmus-Verfahren (EdDSA) gehört.
Die Curve25519-Algorithmen sind in der Kryptografie weit verbreitet und gelten als besonders effizient und schnell. Im Vergleich zu den bei ECDH verwendeten Kurven für den Schlüsselaustausch des NIST (National Institute of Standards and Technology) ist Curve25519 einfacher zu implementieren. Da Curve25519 nicht von einer staatlichen Stelle stammt, sind die Entscheidungen hinter seiner Gestaltung zudem transparent und offen.
X25519 ist in RFC 7748 spezifiziert.
Beispiele
Hinweis: Sie können die funktionsfähigen Beispiele auf GitHub ausprobieren.
ECDH: Gemeinsamen geheimen Schlüssel ableiten
In diesem Beispiel erzeugen Alice und Bob jeweils ein ECDH-Schlüsselpaar und tauschen anschließend ihre öffentlichen Schlüssel aus. Dann verwenden sie deriveKey(), um einen gemeinsamen AES-Schlüssel abzuleiten, mit dem sie Nachrichten verschlüsseln könnten. Sehen Sie sich den vollständigen Code auf GitHub an.
/*
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: Gemeinsamen geheimen Schlüssel ableiten
In diesem Beispiel erzeugen Alice und Bob jeweils ein X25519-Schlüsselpaar und tauschen anschließend ihre öffentlichen Schlüssel aus. Dann verwenden beide deriveKey(), um aus ihrem eigenen privaten Schlüssel und dem öffentlichen Schlüssel der jeweils anderen Person einen gemeinsamen AES-Schlüssel abzuleiten. Mit diesem gemeinsamen Schlüssel können sie die Nachrichten, die sie austauschen, verschlüsseln und entschlüsseln.
HTML
Zunächst definieren wir ein HTML-<input>-Element, in das Sie die Klartextnachricht eingeben, die „Alice“ senden wird, sowie eine Schaltfläche, mit der Sie den Verschlüsselungsvorgang starten können.
<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" />
Darauf folgen zwei weitere Elemente: eines zur Anzeige des Chiffretexts, nachdem Alice den Klartext mit ihrem Exemplar des geheimen Schlüssels verschlüsselt hat, und eines zur Anzeige des Textes, nachdem Bob ihn mit seinem Exemplar des geheimen Schlüssels entschlüsselt hat.
<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
Der folgende Code zeigt, wie wir deriveKey() verwenden. Wir übergeben den öffentlichen X25519-Schlüssel der anderen Partei und den privaten X25519-Schlüssel der lokalen Partei und legen fest, dass der abgeleitete Schlüssel ein AES-GCM-Schlüssel sein soll. Außerdem legen wir fest, dass der abgeleitete Schlüssel nicht exportierbar ist und zum Ver- und Entschlüsseln verwendet werden kann.
Diese Funktion verwenden wir weiter unten im Code, um gemeinsame Schlüssel für Bob und Alice zu erstellen.
/*
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"],
);
}
Als Nächstes definieren wir die Funktionen, mit denen Alice ihre Klartextnachricht in UTF-8 kodiert und anschließend verschlüsselt und mit denen Bob die Nachricht entschlüsselt und anschließend dekodiert. Beide Funktionen erhalten als Argumente den gemeinsamen AES-Schlüssel, einen Initialisierungsvektor und den zu verschlüsselnden beziehungsweise zu entschlüsselnden Text.
Für die Ver- und Entschlüsselung muss derselbe Initialisierungsvektor verwendet werden. Er muss jedoch nicht geheim sein und wird daher normalerweise zusammen mit der verschlüsselten Nachricht gesendet. Da wir in diesem Fall keine Nachricht tatsächlich versenden, stellen wir ihn direkt zur Verfügung.
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";
}
}
Die folgende Funktion agreeSharedSecretKey() wird beim Laden aufgerufen, um Schlüsselpaare und gemeinsame Schlüssel für Alice und Bob zu erzeugen. Außerdem fügt sie der Schaltfläche „Encrypt“ einen Klick-Handler hinzu, der die Ver- und anschließende Entschlüsselung des im ersten <input>-Element eingegebenen Textes auslöst. Beachten Sie, dass sich der gesamte Code in einem try...catch-Handler befindet. So können wir den Fall protokollieren, dass die Schlüsselerzeugung fehlschlägt, weil der X25519-Algorithmus nicht unterstützt wird.
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();
Ergebnis
Klicken Sie auf die Schaltfläche „Encrypt“, um den Text im oberen <input>-Element zu verschlüsseln. In den beiden folgenden Elementen werden der verschlüsselte Chiffretext und der entschlüsselte Text angezeigt. Der Protokollbereich am unteren Rand enthält Informationen über die vom Code erzeugten Schlüssel.
PBKDF2: AES-Schlüssel aus einem Passwort ableiten
In diesem Beispiel fragen wir die nutzende Person nach einem Passwort. Daraus leiten wir mit PBKDF2 einen AES-Schlüssel ab und verwenden diesen anschließend, um eine Nachricht zu verschlüsseln. Sehen Sie sich den vollständigen Code auf GitHub an.
/*
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-Schlüssel aus einem gemeinsamen Geheimnis ableiten
In diesem Beispiel verschlüsseln wir eine Nachricht plainText mithilfe eines gemeinsamen Geheimnisses secret, das seinerseits mit einem Algorithmus wie ECDH abgeleitet worden sein könnte. Anstatt das gemeinsame Geheimnis direkt zu verwenden, nutzen wir es als Schlüsselmaterial für die HKDF-Funktion. Damit leiten wir einen AES-GCM-Schlüssel ab, mit dem wir anschließend die Nachricht verschlüsseln. Sehen Sie sich den vollständigen Code auf GitHub an.
/*
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,
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;
}
Spezifikationen
| Spezifikation |
|---|
| Web Cryptography Level 2> # SubtleCrypto-method-deriveKey> |