export default class Crypto { constructor(key) {//{{{ if(key === null) throw new Error("No key provided") if(typeof key === 'string') this.key = sjcl.codec.base64.toBits(base64_key) else this.key = key this.aes = new sjcl.cipher.aes(this.key) }//}}} static generate_key() {//{{{ return sjcl.random.randomWords(8) }//}}} static pass_to_key(pass, salt = null) {//{{{ if(salt === null) salt = sjcl.random.randomWords(4) // 128 bits (16 bytes) let key = sjcl.misc.pbkdf2(pass, salt, 10000) return { salt, key, } }//}}} encrypt(plaintext_data_in_bits, counter, return_encoded = true) {//{{{ // 8 bytes of random data, (1 word = 4 bytes) * 2 // with 8 bytes of byte encoded counter is used as // IV to guarantee a non-repeated IV (which is a catastrophe). // Assumes counter value is kept unique. Counter is taken from // Postgres sequence. let random_bits = sjcl.random.randomWords(2) let iv_bytes = sjcl.codec.bytes.fromBits(random_bits) for (let i = 0; i < 8; ++i) { let mask = 0xffn << BigInt(i*8) let counter_i_byte = (counter & mask) >> BigInt(i*8) iv_bytes[15-i] = Number(counter_i_byte) } let iv = sjcl.codec.bytes.toBits(iv_bytes) let encrypted = sjcl.mode['ccm'].encrypt( this.aes, plaintext_data_in_bits, iv, ) // Returning 16 bytes (4 words) IV + encrypted data. if(return_encoded) return sjcl.codec.base64.fromBits( iv.concat(encrypted) ) else return iv.concat(encrypted) }//}}} decrypt(encrypted_base64_data) {//{{{ try { let encoded = sjcl.codec.base64.toBits(encrypted_base64_data) let iv = encoded.slice(0, 4) // in words (4 bytes), not bytes let encrypted_data = encoded.slice(4) return sjcl.mode['ccm'].decrypt(this.aes, encrypted_data, iv) } catch(err) { if(err.message == `ccm: tag doesn't match`) throw('Decryption failed') else throw(err) } }//}}} } // vim: foldmethod=marker