0
$\begingroup$

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 and decKey two different keys thus making this example a case for asymmetric key?
  • Is it this possible because of PBKDF2? if so, why i can't find anywhere reference to key usage in relation of PBKDF2?
  • Or, is it still considered a case of symmetric key as I'm still using same salt, iv and password?
  • 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?
$\endgroup$
2
  • $\begingroup$ Hi @kelalaka you totally right. JavaScript return a CryptoKey object for the keys. If i export both keys in jwk I've got: k:"LlrzHkIzqJGrB0NbRw1do2JD9iHkR31i1zXNaWJewKQ", key_ops: ['encrypt'] and k:"LlrzHkIzqJGrB0NbRw1do2JD9iHkR31i1zXNaWJewKQ", key_ops: ['decrypt']. The key is the same. It's js enforicing the enc / dec... $\endgroup$ Sep 22, 2023 at 0:25
  • $\begingroup$ Converted the comments into an answer. $\endgroup$
    – kelalaka
    Sep 22, 2023 at 8:58

1 Answer 1

1
$\begingroup$
  • How it comes that is this possible? Aren't encKey and decKey two different keys thus making this example a case for asymmetric key?

AES is a block-ciper that are synonym to Pseudo Random Permutation (PRF). As we understand from the word permutation, they have the inverse ( decryption) and you will get true result as long as you provide the same key as the encryption

Well, in probabilistic encryption what IV/nonce gives us can cause a different (IV/nonce,key)-pair decrypt correctly, however the probability so low that you may not see in your real life - except with some quantum help.

As you finally point out that the keys are the same.

*Is it this possible because of PBKDF2? if so, why i can't find anywhere reference to key usage in relation of PBKDF2?

PBKDF ( in longer form Password-Based Key Derivation Function 2) is a Key derivation function from password. It is quite outdated since it was designed in 2000 in rfc2898.

For reducing the attacker capabilities it only facilitates iteration count that can reduce the parallelization proportionally. For JS, there is also Scrypt (2009) that is better than PBKDF2 since it additionally reduces the attacker capability with large memory requirements. Attackers can easily parallelize PBKDF2 with ASIC/PFGA, however, the memory requirements of Scrypt almost eliminate this case.

There are modern Password Based KDF, however, you may not find them officially for JS. Argon2 (2015) (memory-hard, cpu threads-hard (requires parallelization), iteration, side-channel resistance), Balloon hashing (2016) (proven memory-hard, side-channel resistance) and a new one bscrypt (2022) (cache hard, memory hard, iteration, parallelization)

I don't see where did you failed to see, there are tons of example over the net. Even you use the Chat-GPT or the Bard or similar ones they will provide some insight about PBKDF and AES usage in JavaScrypt ( though do not expect that is true since there are lots of false/outdated information on the net, the LLM AIs is what internet provides us!)

  • Or, is it still considered a case of symmetric key as I'm still using same salt, iv and password?
  • Salt here it is used in PBKDF2 to produce different values even you use the same passwords , in addition to this, it prevents the rainbow tables that is very practical attack against passwords upto some space if not salt is used. In some sense it randomizes the key generation since you password is fixed.

  • IV here is used for the AES-GCM encryption and there are many pitfall for using AES-GCM and especialy for this never reuse an $(IV,key)$ pair again, in other words, don't use twice or more an IV under a key. For more details of AES-GCM see this What are the rules for using AES-GCM correctly? answer.

  • password : the password is used for key generation with a good PBKDF ( mind missing 2) with good parameters. One may make a simple calculation;

    • my password strength is 121 ( yes can be calcualted if used correct generatioon like bip-39 or dicewire)
    • My itertaion count $2^{20}\approx 1M$
    • My PBKDF has memory hardness

    so you will have 141 password strength at all. You are secure against the bitcoin mines that can reach double SHA256 above $2^{93}$ in a year. that is the openly known largest computing powers.

  • 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?

This is rather library creator preference and each had different goodness/badness. Stackoverlow has tons of question about these libraries and inter-operation one to another since we live in a complex world.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.