Skip to content

Instantly share code, notes, and snippets.

@zillwc
Created April 17, 2020 11:00
Show Gist options
  • Save zillwc/187834a5f830a5948c19fe564728e1ac to your computer and use it in GitHub Desktop.
Save zillwc/187834a5f830a5948c19fe564728e1ac to your computer and use it in GitHub Desktop.
Testing webcrypto functionality: encrypt, decrypt, wrapKey, unwrapKey, importKey, generateKey, and deriveKey
const KEY_OP_ALGO = 'AES-GCM';
const KEY_WRAPPING_ALGO = 'AES-KW';
const KEY_DERIVATION_ALGO = 'PBKDF2';
const encode = (t) => (new TextEncoder()).encode(t);
const decode = (t) => (new TextDecoder()).decode(t);
const bytesToArrayBuffer = (b) => new Uint8Array(b);
const areBuffersEqual = (buf1, buf2) => {
const a1 = bytesToArrayBuffer(buf1);
const a2 = bytesToArrayBuffer(buf2);
return !(a1.some((elem, idx) => elem !== a2[idx]));
};
function generateNewKey() {
const algorithm = { name: KEY_OP_ALGO, length: 256 };
const extractable = true;
const keyUsages = [ 'encrypt', 'decrypt' ];
return crypto.subtle.generateKey(algorithm, extractable, keyUsages);
}
async function encrypt(algorithm, key, plaintextSecret) {
const plaintext = encode(plaintextSecret);
return window.crypto.subtle.encrypt(algorithm, key, plaintext);
}
async function decrypt(algorithm, key, cipher) {
const decrypted = await window.crypto.subtle.decrypt(algorithm, key, cipher);
return decode(decrypted);
}
function getWrappingKey(salt, keyMaterial) {
const algorithm = { name: KEY_DERIVATION_ALGO, salt, iterations: 100000, hash: 'SHA-256' };
const derivedKeyAlgorithm = { name: KEY_WRAPPING_ALGO, length: 256 };
const extractable = true;
const keyUsages = [ 'wrapKey', 'unwrapKey' ];
return window.crypto.subtle.deriveKey(
algorithm, keyMaterial, derivedKeyAlgorithm, extractable, keyUsages
);
}
function getKeyMaterial(plaintextSecret) {
const keyData = encode(plaintextSecret);
const format = 'raw';
const algorithm = { name: KEY_DERIVATION_ALGO };
const extractable = false;
const keyUsages = [ 'deriveBits', 'deriveKey' ];
return window.crypto.subtle.importKey(format, keyData, algorithm, extractable, keyUsages);
}
async function wrapKey(wrappingKeySecret, keyToWrap) {
const keyMaterial = await getKeyMaterial(wrappingKeySecret);
const salt = window.crypto.getRandomValues(new Uint8Array(16));
const wrappingKey = await getWrappingKey(salt, keyMaterial);
const format = 'raw';
const algorithm = KEY_WRAPPING_ALGO;
const wrappedKey = await window.crypto.subtle.wrapKey(
format, keyToWrap, wrappingKey, algorithm
);
return { wrappedKeySalt: salt, wrappedKey };
}
async function unwrapKey(wrappedKeySalt, wrappedKey, wrappingKeySecret) {
const keyMaterial = await getKeyMaterial(wrappingKeySecret);
const unwrappingKey = await getWrappingKey(wrappedKeySalt, keyMaterial);
const wrappedKeyBuffer = bytesToArrayBuffer(wrappedKey);
const format = 'raw';
const extractable = true;
const keyUsages = [ 'encrypt', 'decrypt' ];
return window.crypto.subtle.unwrapKey(
format, wrappedKeyBuffer, unwrappingKey, KEY_WRAPPING_ALGO, KEY_OP_ALGO, extractable, keyUsages
);
}
async function testEncryptionDecryption() {
const plaintext = "s3cret";
const masterKey = await generateNewKey();
const iv = window.crypto.getRandomValues(new Uint8Array(12));
const algorithm = { name: KEY_OP_ALGO, iv };
const cipher = await encrypt(algorithm, masterKey, plaintext);
const decrypted = await decrypt(algorithm, masterKey, cipher);
const wasSuccessful = decrypted === plaintext;
console.assert(wasSuccessful, 'ENCRYPT/DECRYPT FAILED');
}
async function testKeyWrapUnwrap() {
const plaintext = "s3cret";
const masterKey = await generateNewKey();
const wrappingKeySecret = 'wr4pM3';
const { wrappedKeySalt, wrappedKey } = await wrapKey(wrappingKeySecret, masterKey);
const unwrappedMasterKey = await unwrapKey(wrappedKeySalt, wrappedKey, wrappingKeySecret);
const iv = window.crypto.getRandomValues(new Uint8Array(12));
const algorithm = { name: KEY_OP_ALGO, iv };
const encrypted = await encrypt(algorithm, masterKey, plaintext);
const reencrypted = await encrypt(algorithm, unwrappedMasterKey, plaintext);
const wasSuccessful = areBuffersEqual(encrypted, reencrypted);
console.assert(wasSuccessful, 'KEY WRAP/UNWRAP FAILED');
}
async function test() {
await testEncryptionDecryption();
await testKeyWrapUnwrap();
}
test();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment