edge-core-js
Version:
Edge account & wallet management library
131 lines (111 loc) • 3.42 kB
JavaScript
import aesjs from 'aes-js'
import { utf8 } from '../encoding'
import { sha256 } from './hashes'
import { verifyData } from './verify'
const AesCbc = aesjs.ModeOfOperation.cbc
/**
* Some of our data contains terminating null bytes due to an old bug,
* so this function handles text decryption as a special case.
*/
export function decryptText(box, key) {
const data = decrypt(box, key)
if (data[data.length - 1] === 0) {
return utf8.stringify(data.subarray(0, -1))
}
return utf8.stringify(data)
}
/**
* @param box an Airbitz JSON encryption box
* @param key a key, as an ArrayBuffer
*/
export function decrypt(box, key) {
// Check JSON:
if (box.encryptionType !== 0) {
throw new Error('Unknown encryption type')
}
const iv = box.iv_hex
const ciphertext = box.data_base64
// Decrypt:
const cipher = new AesCbc(key, iv)
const raw = cipher.decrypt(ciphertext)
// Calculate data locations:
const headerStart = 1
const headerSize = raw[0]
const dataStart = headerStart + headerSize + 4
const dataSize =
(raw[dataStart - 4] << 24) |
(raw[dataStart - 3] << 16) |
(raw[dataStart - 2] << 8) |
raw[dataStart - 1]
const footerStart = dataStart + dataSize + 1
const footerSize = raw[footerStart - 1]
const hashStart = footerStart + footerSize
const paddingStart = hashStart + 32
// Verify SHA-256 checksum:
const hash = sha256(raw.subarray(0, hashStart))
if (!verifyData(hash, raw.subarray(hashStart, paddingStart))) {
throw new Error('Invalid checksum')
}
// Verify pkcs7 padding:
const padding = pkcs7(paddingStart)
if (!verifyData(padding, raw.subarray(paddingStart))) {
throw new Error('Invalid PKCS7 padding')
}
// Return the payload:
return raw.subarray(dataStart, dataStart + dataSize)
}
/**
* @param payload an ArrayBuffer of data
* @param key a key, as an ArrayBuffer
*/
export function encrypt(
io,
data,
key
) {
// Calculate data locations:
const headerStart = 1
const headerSize = io.random(1)[0] & 0x1f
const dataStart = headerStart + headerSize + 4
const dataSize = data.length
const footerStart = dataStart + dataSize + 1
const footerSize = io.random(1)[0] & 0x1f
const hashStart = footerStart + footerSize
const paddingStart = hashStart + 32
// Initialize the buffer with padding:
const padding = pkcs7(paddingStart)
const raw = new Uint8Array(paddingStart + padding.length)
raw.set(padding, paddingStart)
// Add header:
raw[0] = headerSize
raw.set(io.random(headerSize), headerStart)
// Add payload:
raw[dataStart - 4] = (dataSize >> 24) & 0xff
raw[dataStart - 3] = (dataSize >> 16) & 0xff
raw[dataStart - 2] = (dataSize >> 8) & 0xff
raw[dataStart - 1] = dataSize & 0xff
raw.set(data, dataStart)
// Add footer:
raw[footerStart - 1] = footerSize
raw.set(io.random(footerSize), footerStart)
// Add SHA-256 checksum:
raw.set(sha256(raw.subarray(0, hashStart)), hashStart)
// Encrypt to JSON:
const iv = io.random(16)
const cipher = new AesCbc(key, iv)
const ciphertext = cipher.encrypt(raw)
return {
encryptionType: 0,
iv_hex: iv,
data_base64: ciphertext
}
}
/**
* Generates the pkcs7 padding data that should be appended to
* data of a particular length.
*/
function pkcs7(length) {
const out = new Uint8Array(16 - (length & 0xf))
for (let i = 0; i < out.length; ++i) out[i] = out.length
return out
}