Skip to content

Instantly share code, notes, and snippets.

@geekman
Last active April 11, 2019 17:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save geekman/f9735602f744ebe5fa812f8ba17518c4 to your computer and use it in GitHub Desktop.
Save geekman/f9735602f744ebe5fa812f8ba17518c4 to your computer and use it in GitHub Desktop.
Web Crypto using HKDF
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<style>
* { font-family: sans-serif; font-size: 16px; }
input { font-family: monospace; }
input[readonly] { border: 1px solid #eee; }
div.row { padding: .8em 0; }
input[type=radio] {
opacity: 0;
position: absolute;
}
input[type=radio] + label {
border: 1px solid #ccc;
padding: .3em 1em;
}
input[type=radio]:checked + label {
background: #ddd;
}
input[type=radio]:focus + label {
outline: rgba(77, 97, 171, 0.5) auto 3px;
}
</style>
<script>
function mkurlsafe(b64) {
return b64
.replace(/\//g, '_')
.replace(/\+/g, '-')
.replace(/=*$/, '');
}
function urlsafe2arr(b64) {
b64 = b64
.replace(/_/g, '/')
.replace(/-/g, '+');
b64 += '==='.slice((b64.length + 3) % 4);
var b = atob(b64)
.split('')
.map(s => s.charCodeAt(0));
return new Uint8Array(b);
}
function arr2b64(arr) { return btoa(String.fromCharCode.apply(null, new Uint8Array(arr))) }
function arr2hex(arr) { return [].map.call(new Uint8Array(arr), n => ('0' + n.toString(16)).slice(-2) ).join('') }
function txt(str) { return new TextEncoder().encode(str); }
var crypto = window.crypto;
function randB64(len) {
var buf = new Uint8Array(len);
crypto.getRandomValues(buf);
return mkurlsafe(arr2b64(buf));
}
function importKeyB64(b64) {
return crypto.subtle.importKey('raw', urlsafe2arr(b64), 'HKDF', false, ['deriveKey'])
}
function makeEncKey(key, info) {
return crypto.subtle.deriveKey(
{name: 'HKDF', salt: new Uint8Array(), info: txt(info), hash: 'SHA-256'},
key,
{name: 'AES-GCM', length: 128},
true,
['encrypt', 'decrypt']);
}
function makeSignKey(key, info) {
return crypto.subtle.deriveKey(
{name: 'HKDF', salt: new Uint8Array(), info: txt(info), hash: 'SHA-256'},
key,
{name: 'HMAC', hash: 'SHA-256'},
true,
['sign']);
}
async function showKeyValue(elem, key) {
var e = document.getElementById(elem);
var keyBytes = await crypto.subtle.exportKey('raw', key);
e.dataset.hex_value = arr2hex(keyBytes);
e.dataset.b64_value = arr2b64(keyBytes);
}
function refreshKeyValues() {
document.querySelector('input[name=display_format]:checked').onchange();
}
async function generateSubkeys(k) {
const masterKey = await importKeyB64(k);
const encKey = await makeEncKey(masterKey, 'encryption');
const authKey = await makeSignKey(masterKey, 'authentication');
await showKeyValue('enckey', encKey);
await showKeyValue('authkey', authKey);
refreshKeyValues();
}
function randomKey(elem) {
var e = document.getElementById(elem);
e.value = randB64(16);
e.onchange();
}
function changeDisplayFormat(e) {
var dispType = e.value;
var l = document.querySelectorAll('input[id$=key]');
for (var i = 0; i < l.length; i++) {
var e = l[i];
var v = e.dataset[dispType + '_value'];
if (v) e.value = v;
}
}
</script>
<div class="row">
<label for="masterkey">Master key:</label>
<input type="text" id="masterkey" size=25 onchange="generateSubkeys(this.value)">
<button id="generate" onclick="randomKey('masterkey')">Generate</button>
</div>
<div class="row">
Display format:
<input id="show_hex" type="radio" name="display_format" value="hex" onchange="changeDisplayFormat(this);" checked>
<label for="show_hex">Hex</label>
<input id="show_b64" type="radio" name="display_format" value="b64" onchange="changeDisplayFormat(this);">
<label for="show_b64">Base64</label>
</div>
<div class="row">
<label for="enckey">Encryption key:</label>
<input type="text" id="enckey" size=40 readonly>
</div>
<div class="row">
<label for="authkey">Auth key:</label>
<input type="text" id="authkey" size=40 readonly>
</div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment