I'm trying to encrypt some text using window.crypto
:
await crypto.subtle.encrypt(algorithm, key, dataArrayBuffer).catch(error => console.error(error));
However I get this error AES key data must be 128 or 256 bits
. I'm using PBKDF2 to create a 256 bit key from a password and I specify a key length of 256
:
window.crypto.subtle.deriveKey(
{
"name": "PBKDF2",
"salt": salt,
"iterations": iterations,
"hash": hash
},
baseKey,
{"name": "AES-GCM", "length": 256}, //<------------
true,
["encrypt", "decrypt"]
);
But I end up getting this key edi5Fou4yCdSdx3DX3Org+L2XFAsVdomVgpVqUGjJ1g=
after I exportKey
it and convert it from an ArrayBuffer
to a string
with a length of 44
bytes and 352
bits...
Which would explain the error, but how can I create an actual 256
bit key from window.crypto
's PBKDF2
?
JSFiddle: https://jsfiddle.net/6Lyaoudc/1/
The displayed lengths refer to the Base64 encoded key. A 32 bytes key has a Base64 encoded length of 44 bytes (352 bits) in accordance with the displayed values, which are therefore correct, see e.g. here.
The bug is that in the 2nd importKey
(see comment Create CryptoKey for AES-GCM from import) the Base64 encoded key (i.e. keyString
) is passed instead of the binary key (i.e. the ArrayBuffer
keyBytes
). If keyBytes
is passed, the encryption works:
function deriveAKey(password, salt, iterations, hash) {
// First, create a PBKDF2 "key" containing the password
window.crypto.subtle.importKey(
"raw",
stringToArrayBuffer(password),
{"name": "PBKDF2"},
false,
["deriveKey"]).
then(function(baseKey){
// Derive a key from the password
return window.crypto.subtle.deriveKey(
{
"name": "PBKDF2",
"salt": salt,
"iterations": iterations,
"hash": hash
},
baseKey,
{"name": "AES-GCM", "length": 256}, // Key we want.Can be any AES algorithm ("AES-CTR", "AES-CBC", "AES-CMAC", "AES-GCM", "AES-CFB", "AES-KW", "ECDH", "DH", or "HMAC")
true, // Extractable
["encrypt", "decrypt"] // For new key
);
}).then(function(aesKey) {
// Export it so we can display it
return window.crypto.subtle.exportKey("raw", aesKey);
}).then(async function(keyBytes) {
// Display key in Base64 format
var keyS = arrayBufferToString(keyBytes);
var keyB64 = btoa (keyS);
console.log(keyB64);
console.log('Key byte size: ', byteCount(keyB64));
console.log('Key bit size: ', byteCount(keyB64) * 8);
var keyString = stringToArrayBuffer(keyB64);
const iv = window.crypto.getRandomValues(new Uint8Array(12));
const algorithm = {
name: 'AES-GCM',
iv: iv,
};
//Create CryptoKey for AES-GCM from import
const key = await crypto.subtle.importKey(
'raw',//Provided key will be of type ArrayBuffer
// keyString,
keyBytes, // 1. Use keyBytes instead of keyString
{
name: "AES-GCM",
},
false,//Key not extractable
['encrypt', 'decrypt']
);
//Convert data to ArrayBuffer
var data = "The quick brown fox jumps over the lazy dog"; // 2. Define data
const dataArrayBuffer = new TextEncoder().encode(data);
const encryptedArrayBuffer = await crypto.subtle.encrypt(algorithm, key, dataArrayBuffer).catch(error => console.error(error));
var ivB64 = btoa(arrayBufferToString(iv));
console.log(ivB64); // 3. Display (Base64 encoded) IV
var encB64 = btoa(arrayBufferToString(encryptedArrayBuffer));
console.log(encB64.replace(/(.{56})/g,'$1\n')); // 4. Display (Base64 encoded) ciphertext (different for each encryption, because of random salt and IV)
}).catch(function(err) {
console.error("Key derivation failed: " + err.message);
});
}
//Utility functions
function stringToArrayBuffer(byteString){
var byteArray = new Uint8Array(byteString.length);
for(var i=0; i < byteString.length; i++) {
byteArray[i] = byteString.codePointAt(i);
}
return byteArray;
}
function arrayBufferToString(buffer){
var byteArray = new Uint8Array(buffer);
var byteString = '';
for(var i=0; i < byteArray.byteLength; i++) {
byteString += String.fromCodePoint(byteArray[i]);
}
return byteString;
}
function byteCount(s) {
return encodeURI(s).split(/%..|./).length - 1;
}
var salt = window.crypto.getRandomValues(new Uint8Array(16));
var iterations = 5000;
var hash = "SHA-512";
var password = "password";
deriveAKey(password, salt, iterations, hash);
In addition to the key, the code also displays IV and ciphertext, each Base64 encoded. The ciphertext can be verified e.g. here using the Base64 encoded key and IV.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With