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.
Mit der Methode deriveKey() der Schnittstelle SubtleCrypto lässt sich aus einem Hauptschlüssel ein geheimer Schlüssel ableiten.
Sie nimmt als Argumente ein anfängliches Schlüsselmaterial, den zu verwendenden Ableitungsalgorithmus und die gewünschten Eigenschaften des abzuleitenden Schlüssels entgegen. Sie gibt eine Promise zurück, die 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, bei dem die Eigenschaftnameauf den StringECDHgesetzt ist. - Für HKDF übergeben Sie ein
HkdfParams-Objekt. - Für PBKDF2 übergeben Sie ein
Pbkdf2Params-Objekt. - Für X25519 übergeben Sie ein
EcdhKeyDeriveParams-Objekt, bei dem die Eigenschaftnameauf den StringX25519gesetzt ist.
- 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 um den privaten ECDH- bzw. X25519-Schlüssel. Andernfalls ist es das anfängliche Schlüsselmaterial für die Ableitungsfunktion: Bei PBKDF2 könnte es beispielsweise ein Passwort sein, das mitSubtleCrypto.importKey()alsCryptoKeyimportiert wurde. derivedKeyType-
Ein Objekt, das den Algorithmus definiert, für den der abgeleitete Schlüssel verwendet werden soll:
- 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 mit
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 Verwendungsmöglichkeiten des Schlüssels durch den inderivedKeyAlgorithmfestgelegten Algorithmus erlaubt 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 Wrappen eines Schlüssels verwendet werden.unwrapKey: Der Schlüssel kann zum Entpacken eines gewrappten Schlüssels verwendet werden.
Rückgabewert
Ausnahmen
Die 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 ein unbekannter oder für die Ableitung ungeeigneter Algorithmus verwendet werden soll 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 wurde entwickelt, um Schlüsselmaterial aus einer Eingabe mit hoher Entropie abzuleiten, etwa aus dem Ergebnis eines ECDH-Schlüsselaustauschs.
Sie ist nicht dafür gedacht, 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 wurde entwickelt, um Schlüsselmaterial aus einer Eingabe mit vergleichsweise geringer Entropie abzuleiten, etwa 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. Das erschwert es Angreifern, den Schlüssel mithilfe eines Wörterbuchangriffs durch Ausprobieren zu ermitteln.
PBKDF2 ist in RFC 2898 spezifiziert.
Schlüsselaustauschalgorithmen
ECDH
ECDH (Elliptic Curve Diffie–Hellman) ist ein Schlüsselaustauschalgorithmus. Er ermöglicht es zwei Personen, die jeweils ein öffentliches und ein privates ECDH-Schlüsselpaar besitzen, ein gemeinsames Geheimnis zu erzeugen, das nur sie beide kennen. Dieses 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üsselaustauschalgorithmus, 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 beim ECDH-Schlüsselaustausch verwendeten Kurven des NIST (National Institute of Standards and Technology) ist Curve25519 einfacher zu implementieren. Da Curve25519 nicht von einer staatlichen Stelle entwickelt wurde, sind außerdem die Entscheidungen hinter seinem Entwurf transparent und offen nachvollziehbar.
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. Danach leiten sie mit deriveKey() einen gemeinsamen AES-Schlüssel ab, 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. Danach leitet jede Person mit deriveKey() aus ihrem eigenen privaten Schlüssel und dem öffentlichen Schlüssel der anderen Person einen gemeinsamen AES-Schlüssel ab. Mit diesem gemeinsamen Schlüssel können sie die Nachrichten, die sie austauschen, verschlüsseln und entschlüsseln.
HTML
Zuerst definieren wir ein HTML-<input>, in das Sie die Klartextnachricht eingeben, die „Alice“ senden soll, 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 Geheimtexts, nachdem Alice den Klartext mit ihrer Kopie des geheimen Schlüssels verschlüsselt hat, und eines zur Anzeige des Texts, nachdem Bob ihn mit seiner Kopie 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 und für die Ver- und Entschlüsselung geeignet ist.
Weiter unten im Code verwenden wir diese Funktion, um gemeinsame Schlüssel für Bob und Alice zu erzeugen.
/*
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 nehmen den gemeinsamen AES-Schlüssel, einen Initialisierungsvektor und den zu verschlüsselnden oder zu entschlüsselnden Text als Argumente entgegen.
Für die Ver- und Entschlüsselung muss derselbe Initialisierungsvektor verwendet werden. Er muss jedoch nicht geheim sein und wird daher üblicherweise zusammen mit der verschlüsselten Nachricht übertragen. Da wir in diesem Fall keine Nachricht tatsächlich übertragen, stellen wir ihn direkt bereit.
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. Sie fügt außerdem einen Klick-Handler für die Schaltfläche „Encrypt“ hinzu, der die Ver- und anschließende Entschlüsselung des Texts im ersten <input> auslöst. Beachten Sie, dass sich der gesamte Code in einem try...catch-Block befindet. So können wir protokollieren, wenn 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. Der verschlüsselte Geheimtext und der wieder entschlüsselte Text werden in den beiden folgenden Elementen 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 bitten wir die Benutzerin oder den Benutzer um ein Passwort, leiten daraus mit PBKDF2 einen AES-Schlüssel ab und verschlüsseln anschließend mit dem AES-Schlüssel eine Nachricht. 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 möglicherweise selbst mit einem Algorithmus wie ECDH abgeleitet wurde. Anstatt das gemeinsame Geheimnis direkt zu verwenden, nutzen wir es als Schlüsselmaterial für die HKDF-Funktion. Damit leiten wir einen AES-GCM-Verschlüsselungsschlü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> |