SubtleCrypto: deriveKey() 方法
Baseline 廣泛可用 *
注意:此功能在 Web Workers 中可用。
deriveKey() 方法是 SubtleCrypto 介面的一個方法,用於從主金鑰派生出金鑰。
它接收一些初始金鑰材料、要使用的派生演算法以及派生金鑰所需的屬性作為引數。它返回一個 Promise,該 Promise 將以一個代表新金鑰的 CryptoKey 物件的形式得到滿足。
值得注意的是,支援的金鑰派生演算法具有非常不同的特性,適用於各種不同的場景。有關詳細資訊,請參閱 支援的演算法。
語法
deriveKey(algorithm, baseKey, derivedKeyType, extractable, keyUsages)
引數
algorithm-
一個定義要使用的派生演算法的物件。
- 要使用 ECDH,請傳遞一個
EcdhKeyDeriveParams物件,並將name屬性指定為字串ECDH。 - 要使用 HKDF,請傳遞一個
HkdfParams物件。 - 要使用 PBKDF2,請傳遞一個
Pbkdf2Params物件。 - 要使用 X25519,請傳遞一個
EcdhKeyDeriveParams物件,並將name屬性指定為字串X25519。
- 要使用 ECDH,請傳遞一個
baseKey-
一個表示派生演算法輸入的
CryptoKey。如果algorithm是 ECDH 或 X25519,那麼它將是 ECDH 或 X25519 私鑰。否則,它將是派生函式的初始金鑰材料:例如,對於 PBKDF2,它可能是密碼,使用SubtleCrypto.importKey()匯入為CryptoKey。 derivedKeyType-
一個定義派生金鑰將用於的演算法的物件
- 對於 HMAC,請傳遞一個
HmacImportParams物件。 - 對於 AES-CTR、AES-CBC、AES-GCM 或 AES-KW,請傳遞一個
AesDerivedKeyParams物件。
- 對於 HMAC,請傳遞一個
extractable-
一個布林值,指示是否可以使用
SubtleCrypto.exportKey()或SubtleCrypto.wrapKey()匯出金鑰。 keyUsages-
一個
Array,指示可以使用派生金鑰執行的操作。請注意,金鑰用法必須由derivedKeyAlgorithm中設定的演算法允許。陣列的可能值為:
返回值
異常
當遇到以下任一異常時,Promise 會被拒絕:
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 橢圓曲線,這是 RFC 8032 中定義的愛德華茲曲線數字簽名演算法 (EdDSA) 系列演算法的一部分。
Curve25519 演算法在密碼學中被廣泛使用,並被認為是最高效/最快的演算法之一。與 ECDH 使用的 NIST(美國國家標準與技術研究院)曲線金鑰交換演算法相比,Curve25519 實現起來更簡單,並且其非政府來源意味著其設計選擇背後的決策是透明和公開的。
X25519 在 RFC 7748 中有規定。
示例
注意: 您可以在 GitHub 上 嘗試執行示例。
ECDH:派生共享金鑰
在此示例中,Alice 和 Bob 各自生成一個 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:派生共享金鑰
在此示例中,Alice 和 Bob 各自生成一個 X25519 金鑰對,然後交換公鑰。然後,他們各自使用 deriveKey() 從自己的私鑰和對方的公鑰派生一個共享的 AES 金鑰。他們可以使用此共享金鑰來加密和解密他們交換的訊息。
HTML
首先,我們定義一個 HTML <input> 元素,您將使用它來輸入“Alice”將要傳送的明文訊息,以及一個按鈕,您可以點選它來啟動加密過程。
<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" />
接著是另外兩個元素,用於顯示 Alice 使用她的金鑰副本加密明文後的密文,以及 Bob 使用他的金鑰副本解密後的文字。
<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 金鑰。我們還將派生金鑰設定為不可匯出,並使其適用於加密和解密。
我們在程式碼的稍後位置使用此函式來為 Bob 和 Alice 建立共享金鑰。
/*
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"],
);
}
接下來,我們定義 Alice 將用於 UTF-8 編碼然後加密其明文訊息的函式,以及 Bob 將用於解密然後解碼訊息的函式。它們都接受共享的 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() 函式在載入時被呼叫,以生成 Alice 和 Bob 的金鑰對和共享金鑰。它還為“Encrypt”按鈕添加了一個點選事件處理程式,該處理程式將觸發對第一個 <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();
結果
點選“Encrypt”按鈕來加密頂部 <input> 元素中的文字,並在接下來的兩個元素中顯示加密後的密文和解密後的密文。底部的日誌區域提供了有關程式碼生成的金鑰的資訊。
PBKDF2:從密碼派生 AES 金鑰
在此示例中,我們要求使用者輸入密碼,然後使用該密碼透過 PBKDF2 派生一個 AES 金鑰,然後使用該 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 金鑰
在此示例中,我們使用給定的共享金鑰 secret 加密訊息 plainText,該共享金鑰本身可能已透過 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,
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;
}
規範
| 規範 |
|---|
| Web 加密級別 2 # SubtleCrypto-method-deriveKey |
瀏覽器相容性
載入中…