Skip to content

Instantly share code, notes, and snippets.

@janek26
Last active February 24, 2022 18:26
Show Gist options
  • Save janek26/f66e914f507b3b41a3ce2f134ad7ef23 to your computer and use it in GitHub Desktop.
Save janek26/f66e914f507b3b41a3ce2f134ad7ef23 to your computer and use it in GitHub Desktop.
Simple aes and sha256 for browsers using window.crypto
// exports:
// - supportsCrypto() : bool
// - randomString(length : int) : string
// - aesEncrypt(value : string, password : string) : string
// - aesDecrypt(encryptedMsg : string, password : string) : string
// - sha256(value : string) : string
// if you use this code in Node import crypto and TextEncoder
import WebCrypto from 'node-webcrypto-ossl'
import { TextEncoder, TextDecoder } from 'util'
const crypto = new WebCrypto()
// In Browser use window.crypto instead
async function genEncryptionKey(password, salt, mode, length) {
const derived = { name: mode, length: length }
const encoded = new TextEncoder().encode(password)
const key = await crypto.subtle.importKey(
'raw',
encoded,
{ name: 'PBKDF2' },
false,
['deriveKey'],
)
return crypto.subtle.deriveKey(
{
name: 'PBKDF2',
hash: 'SHA-256',
salt: new TextEncoder().encode(salt),
iterations: 1000,
},
key,
derived,
false,
['encrypt', 'decrypt'],
)
}
function bufferToHex(buffer) {
return Array.from(new Uint8Array(buffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('')
}
export const isBrowser = new Function(
'try {return this===window;}catch(e){ return false;}',
)
export function supportsCrypto() {
return isBrowser()
? !!(window.crypto && crypto.subtle && window.TextEncoder)
: true
}
export function randomString(length) {
const charset =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
let i
let result = ''
const isOpera =
isBrowser() &&
Object.prototype.toString.call(window.opera) == '[object Opera]'
if (crypto && crypto.getRandomValues) {
let values = new Uint32Array(length)
crypto.getRandomValues(values)
for (i = 0; i < length; i++) {
result += charset[values[i] % charset.length]
}
return result
} else if (isOpera) {
//Opera's Math.random is secure, see http://lists.w3.org/Archives/Public/public-webcrypto/2013Jan/0063.html
for (i = 0; i < length; i++) {
result += charset[Math.floor(Math.random() * charset.length)]
}
return result
} else
throw new Error(
"Your browser sucks and can't generate secure random numbers",
)
}
export async function aesEncrypt(value, password) {
const iv = crypto.getRandomValues(new Uint8Array(12))
const salt = randomString(16)
const key = await genEncryptionKey(password, salt, 'AES-GCM', 256)
const encoded = new TextEncoder().encode(value)
const cipherText = await crypto.subtle.encrypt(
{
name: 'AES-GCM',
length: 256,
iv,
},
key,
encoded,
)
return bufferToHex(cipherText) + ':' + bufferToHex(iv.buffer) + ':' + salt
}
export async function aesDecrypt(encString, password) {
function toUint8Array(hexString) {
const result = []
while (hexString.length >= 2) {
result.push(parseInt(hexString.substring(0, 2), 16))
hexString = hexString.substring(2, hexString.length)
}
return new Uint8Array(result)
}
const [cipherTextHex, ivHex, salt] = encString.split(':')
const cipherText = toUint8Array(cipherTextHex).buffer
const iv = toUint8Array(ivHex)
var key = await genEncryptionKey(password, salt, 'AES-GCM', 256)
var decrypted = await crypto.subtle.decrypt(
{
name: 'AES-GCM',
length: 256,
iv,
},
key,
cipherText,
)
return new TextDecoder().decode(decrypted)
}
export async function sha256(str) {
return bufferToHex(
await crypto.subtle.digest(
{ name: 'SHA-256' },
new TextEncoder().encode(str),
),
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment