Skip to content

Instantly share code, notes, and snippets.

@rafaelsq
Last active January 3, 2020 00:12
Show Gist options
  • Save rafaelsq/5af573af7e2d763869e2f4cce0a8357a to your computer and use it in GitHub Desktop.
Save rafaelsq/5af573af7e2d763869e2f4cce0a8357a to your computer and use it in GitHub Desktop.
WebCrypto - PBKDF2-HMAC-256 + encode AES-CBC/AES-GCM
// PBKDF2 HMAC-256
const pbkdf2 = (password, salt, iterations, hash, mode) =>
crypto.subtle.importKey("raw", password, {name: "PBKDF2"}, false, ["deriveKey"])
.then(baseKey => crypto.subtle.deriveKey({name: "PBKDF2", salt, iterations, hash}, baseKey, {"name": mode, "length": 256}, true, ["encrypt", "decrypt"]))
.then(key => crypto.subtle.exportKey("raw", key))
let password = strToBuf("password"),
salt = strToBuf("salt"),
iterations = 1000,
hash = "SHA-256",
mode = "AES-GCM"
pbkdf2(password, salt, iterations, hash, mode)
.then(keyBytes => console.log("hexKey", bufToHex(keyBytes)))
// param mode: string; "AES-GCM" | "AES-CBC"
const encrypt = (data, key, iv, mode) =>
crypto.subtle.importKey("raw", key, {name: mode}, true, ["encrypt", "decrypt"])
.then(bufKey => crypto.subtle.encrypt({name: mode, iv}, bufKey, data))
//.then(buf => new Uint8Array(buf))
const decrypt = (data, key, iv, mode) =>
crypto.subtle.importKey("raw", key, {name: mode}, true, ["encrypt", "decrypt"])
.then(bufKey => crypto.subtle.decrypt({name: mode, iv}, bufKey, data))
//.then(buf => new Uint8Array(buf))
let data = strToBuf("á ✓ ᕦ(ò_óˇ)ᕤ ♥ à"),
key = hexToBuf("0CD1D07EB67E19EF56EA0F3A9A8F8A7C957A2CB208327E0E536608FF83256C96"),
iv = window.crypto.getRandomValues(new Uint8Array(16))
encrypt(data, key, iv, "AES-GCM")
.then(buf => decrypt(buf, key, iv, "AES-GCM"))
.then(buf => console.log("data:", bufToStr(buf)))
export const EncodeGCM = (pass, salt, iv, iterations, data) =>
new window.Promise((ok, fail) => {
pbkdf2(strToBuf(pass), salt, iterations, 'SHA-256', 'AES-GCM')
.then(key => {
const chunkSize = 2 << 10
let len = data.byteLength / chunkSize + (data.byteLength % chunkSize ? 1 : 0) | 0
let out = new window.Uint8Array([...salt, ...iv])
for (let i = 0; i < len; i++) {
encrypt(data.slice(i * chunkSize, i * chunkSize + chunkSize), key, iv, 'AES-GCM')
.then(buf => {
out = new window.Uint8Array([...out, ...new window.Uint8Array(buf)])
if (i == len - 1) {
ok(out)
}
})
.catch(e => {
throw Error(e)
})
}
})
.catch(fail)
})
export const DecodeGCM = (pass, iterations, rawBuf) =>
new window.Promise((ok, fail) => {
const salt = rawBuf.slice(0, 8)
const iv = rawBuf.slice(8, 20)
const buffer = rawBuf.slice(20)
pbkdf2(strToBuf(pass), salt, iterations, 'SHA-256', 'AES-GCM')
.then(key => {
const chunkSize = 2 << 10
let len = buffer.byteLength / (chunkSize + 16) + (buffer.byteLength % (chunkSize + 16) ? 1 : 0) | 0
let out = new window.Uint8Array(len * chunkSize)
for (
let i = 0, s = 0, e = chunkSize + 16;
i < len;
i++, s = i * (chunkSize + 16), e = s + chunkSize + 16
) {
if (e > buffer.byteLength) e = buffer.byteLength
decrypt(buffer.slice(s, e), key, iv, 'AES-GCM')
.then(buf => {
out.set(new window.Uint8Array(buf), i * chunkSize)
if (i == len - 1) {
ok(out.slice(0, i * chunkSize + buf.byteLength))
}
})
.catch(e => {
throw Error(e)
})
}
})
.catch(fail)
})
// ex
EncodeGCM("pass", salt, iv, iterations, strToBuf("content"))
.then(buf => {
console.log("out", bufToHex(buf))
})
.catch(err => {
console.error(err)
})
DecodeGCM("pass", iterations, hexToBuf(state.enc))
.then(buf => {
console.log("out", bufToStr(buf))
})
.catch(err => {
console.error(err)
})
const hexToBuf = hex => {
for (var bytes = [], c = 0; c < hex.length; c += 2)
bytes.push(parseInt(hex.substr(c, 2), 16));
return new Uint8Array(bytes);
};
const bufToHex = buf => {
var byteArray = new Uint8Array(buf);
var hexString = "";
var nextHexByte;
for (var i=0; i<byteArray.byteLength; i++) {
nextHexByte = byteArray[i].toString(16);
if (nextHexByte.length < 2) {
nextHexByte = "0" + nextHexByte;
}
hexString += nextHexByte;
}
return hexString;
};
const strToBuf = str => (new TextEncoder().encode(str));
const bufToStr = str => (new TextDecoder().decode(str));
const strToB64 = str => btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) => String.fromCharCode('0x' + p1)));
const b64ToStr = str => decodeURIComponent(Array.prototype.map.call(atob(str), c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join(''));
// const bufToB64 = buf => btoa(Array.prototype.map.call(buf, ch => String.fromCharCode(ch)).join(''));
const bufToB64 = buf => btoa(String.fromCharCode(...new Uint8Array(buf)))
const b64ToBuf = b64 => {
const binstr = atob(b64),
buf = new Uint8Array(binstr.length);
Array.prototype.forEach.call(binstr, (ch, i) => {
buf[i] = ch.charCodeAt(0);
});
return buf;
}
@rafaelsq
Copy link
Author

rafaelsq commented Jan 31, 2017

@rafaelsq
Copy link
Author

rafaelsq commented Feb 3, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment