Skip to content

Instantly share code, notes, and snippets.

@le0pard
Last active December 28, 2020 20:07
Show Gist options
  • Save le0pard/3a18a3cac4cf636ffa096f4011082869 to your computer and use it in GitHub Desktop.
Save le0pard/3a18a3cac4cf636ffa096f4011082869 to your computer and use it in GitHub Desktop.
AES-256-GCM on Ruby and JS (Web Crypto API and forge.js)
// you need to use https://github.com/digitalbazaar/forge
const encryptData = (key, data) => {
const mdKey = forge.md.sha256.create()
mdKey.update(key)
const iv = forge.random.getBytesSync(12)
const cipher = forge.cipher.createCipher('AES-GCM', mdKey.digest())
cipher.start({
iv: iv,
additionalData: '',
tagLength: 128
});
cipher.update(forge.util.createBuffer(data))
cipher.finish()
const encrypted = cipher.output
const tag = cipher.mode.tag
return `v1--${forge.util.encode64(encrypted.getBytes())}--${forge.util.encode64(iv)}--${forge.util.encode64(tag.getBytes())}`
}
const decryptData = (key, data) => {
const mdKey = forge.md.sha256.create()
mdKey.update(key)
const [version, encoded, iv, authTag] = data.split('--')
const decipher = forge.cipher.createDecipher('AES-GCM', mdKey.digest())
decipher.start({
iv: forge.util.createBuffer(forge.util.decode64(iv)),
additionalData: '', // optional
tagLength: 128, // optional, defaults to 128 bits
tag: forge.util.createBuffer(forge.util.decode64(authTag)) // authentication tag from encryption
})
decipher.update(forge.util.createBuffer(forge.util.decode64(encoded)))
var pass = decipher.finish()
// pass is false if there was a failure (eg: authentication tag didn't match)
if (pass) {
// outputs decrypted hex
return decipher.output.data
} else {
throw new Error('invalid decode')
}
}
require 'openssl'
require 'base64'
def aes256_encrypt(key, data)
key = Digest::SHA256.digest(key)
cipher = OpenSSL::Cipher.new('AES-256-GCM')
iv = cipher.random_iv
cipher.encrypt
cipher.iv = iv
cipher.key = key
cipher.auth_data = ''
encrypted_data = cipher.update(data)
encrypted_data << cipher.final
"v1--#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}--#{::Base64.strict_encode64 cipher.auth_tag}"
end
def aes256_decrypt(key, data)
key = Digest::SHA256.digest(key)
cipher = OpenSSL::Cipher.new('AES-256-GCM')
version, encrypted_data, iv, auth_tag = data.split("--".freeze)
cipher.decrypt
cipher.key = key
cipher.iv = ::Base64.strict_decode64(iv)
cipher.auth_tag = ::Base64.strict_decode64(auth_tag)
cipher.auth_data = ''
decrypted_data = cipher.update(::Base64.strict_decode64(encrypted_data))
decrypted_data << cipher.final
decrypted_data
end
const bufferToBase64 = (buffer) => {
let bytes = new Uint8Array(buffer);
let binary = "";
let len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
const base64ToBuffer = (base64) => {
let binary_string = window.atob(base64);
let len = binary_string.length;
let bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
const encryptData = async (key, data) => {
const encoder = new TextEncoder('utf-8')
const keyData = encoder.encode(key)
key = await crypto.subtle.digest('SHA-256', keyData)
const resKey = await window.crypto.subtle.importKey(
"raw",
key,
{
name: "AES-GCM",
},
false,
["encrypt", "decrypt"]
)
const iv = window.crypto.getRandomValues(new Uint8Array(12))
const enc = new TextEncoder('utf-8')
const encoded = enc.encode(data)
const additionalDataEnc = new TextEncoder('utf-8')
const additionalData = additionalDataEnc.encode('')
const tagLength = 128
const dataAndAuthTag = await window.crypto.subtle.encrypt(
{
name: "AES-GCM",
iv: iv,
additionalData: additionalData,
tagLength: tagLength
},
resKey,
encoded
)
const encryptedData = dataAndAuthTag.slice(0, dataAndAuthTag.byteLength - ((tagLength + 7) >> 3))
const authTag = dataAndAuthTag.slice(dataAndAuthTag.byteLength - ((tagLength + 7) >> 3))
return `v1--${bufferToBase64(encryptedData)}--${bufferToBase64(iv)}--${bufferToBase64(authTag)}`
}
const decryptData = async (key, data) => {
const encoder = new TextEncoder('utf-8')
const keyData = encoder.encode(key)
key = await crypto.subtle.digest('SHA-256', keyData)
const resKey = await window.crypto.subtle.importKey(
"raw",
key,
{
name: "AES-GCM",
},
false,
["encrypt", "decrypt"]
)
const [version, ...base64Data] = data.split('--')
const [encoded, iv, authTag] = base64Data.map((i) => base64ToBuffer(i))
const dataAndAuthTag = new Uint8Array(encoded.byteLength + authTag.byteLength)
dataAndAuthTag.set(new Uint8Array(encoded), 0)
dataAndAuthTag.set(new Uint8Array(authTag), encoded.byteLength)
const additionalDataEnc = new TextEncoder('utf-8')
const additionalData = additionalDataEnc.encode('')
const decrypted = await window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: iv,
additionalData: additionalData,
tagLength: 128
},
resKey,
dataAndAuthTag
)
return new TextDecoder("utf8").decode(decrypted)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment