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
encKey
anddecKey
two 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 usage
in relation ofPBKDF2
? - Or, is it still considered a case of
symmetric key
as I'm still using samesalt
,iv
andpassword
? - And lastly, how it comes that i don't see
key.usage
in 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?
CryptoKey
object for the keys. If i export both keys injwk
I've got:k:"LlrzHkIzqJGrB0NbRw1do2JD9iHkR31i1zXNaWJewKQ", key_ops: ['encrypt']
andk:"LlrzHkIzqJGrB0NbRw1do2JD9iHkR31i1zXNaWJewKQ", key_ops: ['decrypt']
. The key is the same. It's js enforicing the enc / dec... $\endgroup$