Can we have an asymmetric key in AES? Clarification about PBKDF2 and AES-GCM in WebCrypto
According to wikipedia AES page, AES is a symmetric-key algorithm.
The algorithm described by AES is a symmetric-key algorithm, meaning the same key is used for both encrypting and decrypting the data.
While PBKDF2, always according to wikipedia, is a pseudorandom function that take as input a password and salt to produce a derived key, which can then be used as a cryptographic key.
PBKDF2 applies a pseudorandom function, such as hash-based message authentication code (HMAC), to the input password or passphrase along with a salt value and repeats the process many times to produce a derived key, which can then be used as a cryptographic key in subsequent operations.
Javascript provides the Web Crypto API which allows you to use PBKDF2 to genrate derived key and AES-GCM for encryption and decryption.
Basic example
// see https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey
/*
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);
}
Now, you can see that the last parameter of deriveKey is called key.usage and accepts as values ["encrypt", "decrypt"].
But because it an array you can pass just one flag. Eg.: ["encrypt"], ["decrypt"].
Advanced Example, encrypt only and decrypt only
/*
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 = 'banana';
const enc = new TextEncoder();
return window.crypto.subtle.importKey(
"raw",
enc.encode(password),
"PBKDF2",
false,
["deriveBits", "deriveKey"],
);
}
async function encryptKey(salt) {
const keyMaterial = await getKeyMaterial();
return window.crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt,
iterations: 100000,
hash: "SHA-256",
},
keyMaterial,
{ name: "AES-GCM", length: 256 },
false,
["encrypt"],
);
}
async function decryptKey(salt) {
const keyMaterial = await getKeyMaterial();
return window.crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt,
iterations: 100000,
hash: "SHA-256",
},
keyMaterial,
{ name: "AES-GCM", length: 256 },
false,
["decrypt"],
);
}
function encrypt(key, iv, plaintext) {
const enc = new TextEncoder();
return window.crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, enc.encode(plaintext));
}
function decrypt(key, iv, ciphertext) {
const dec = new TextDecoder();
return window.crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ciphertext)
.then((val) => dec.decode(val));
}
async function test() {
const plaintext = 'secret test';
const salt = crypto.getRandomValues(new Uint8Array(64));
const iv = crypto.getRandomValues(new Uint8Array(128));
const encKey = await encryptKey(salt);
const decKey = await decryptKey(salt);
const ciphertext = await encrypt(encKey, iv, plaintext);
console.log('cipher:' + ciphertext);
const plain2 = await decrypt(decKey, iv, ciphertext);
console.log('plain2:' + plain2);
try {
const plain1 = await decrypt(encKey, iv, ciphertext);
console.log('plain1:' + plain1);
} catch (e) {
console.log('plain1:' + e);
}
}
This returns
// Running test returns
cipher:[object ArrayBuffer]
VM161:76 plain2:secret test
VM161:83 plain1:InvalidAccessError: key.usages does not permit this operation
Which means we do actually have two keys one for encryption and the other one for decryption.
But this shouldn't be possible, isn't it?
So, here my questions:
- How it comes that is this possible? Aren't
encKeyanddecKeytwo different keys thus making this example a case forasymmetric key? - Is it this possible because of
PBKDF2? if so, why i can't find anywhere reference tokey usagein relation ofPBKDF2? - Or, is it still considered a case of
symmetric keyas I'm still using samesalt,ivandpassword? - And lastly, how it comes that i don't see
key.usagein any other language except JavaScript? why did JavaScript goes in that way for the implementation? And how it comes that AES works at all under these conditions?
CryptoKeyobject for the keys. If i export both keys injwkI've got:k:"LlrzHkIzqJGrB0NbRw1do2JD9iHkR31i1zXNaWJewKQ", key_ops: ['encrypt']andk:"LlrzHkIzqJGrB0NbRw1do2JD9iHkR31i1zXNaWJewKQ", key_ops: ['decrypt']. The key is the same. It's js enforicing the enc / dec... – borracciaBlu Sep 22 '23 at 00:25