import sjcl from "../internal/sjcl.js" ; import { random } from "../random/Randomizer.js" ; import { bitArrayToUint8Array , uint8ArrayToBitArray } from "../misc/Utils.js" ; import { arrayEquals , concat , uint8ArrayToBase64 } from "@tutao/tutanota-utils" ; import { sha256Hash } from "../hashes/Sha256.js" ; import { CryptoError } from "../misc/CryptoError.js" ; import { sha512Hash } from "../hashes/Sha512.js" ; export const ENABLE_MAC = true ; export const IV_BYTE_LENGTH = 16 ; const KEY_LENGTH_BYTES_AES_256 = 32 ; const KEY_LENGTH_BITS_AES_256 = KEY_LENGTH_BYTES_AES_256 * 8 ; const KEY_LENGTH_BYTES_AES_128 = 16 ; const KEY_LENGTH_BITS_AES_128 = KEY_LENGTH_BYTES_AES_128 * 8 ; const MAC_ENABLED_PREFIX = 1 ; const MAC_LENGTH_BYTES = 32 ; export type Aes256Key = number [ ] ; export type Aes128Key = number [ ] ; export function aes256RandomKey ( ) { return uint8ArrayToBitArray ( random . generateRandomData ( KEY_LENGTH_BYTES_AES_256 ) ) ; } export function generateIV ( ) { return random . generateRandomData ( IV_BYTE_LENGTH ) ; } param key param bytes param iv param usePadding param useMac return export function aes256Encrypt ( key , bytes , iv , usePadding = true , useMac = true ) { verifyKeySize ( key , KEY_LENGTH_BITS_AES_256 ) ; if ( iv . length !== IV_BYTE_LENGTH ) { throw new CryptoError ( `Illegal IV length: ${ iv . length } (expected: ${ IV_BYTE_LENGTH }): ${ uint8ArrayToBase64 ( iv ) } ` ) ; } let subKeys = getAes256SubKeys ( key , useMac ) ; let encryptedBits = sjcl . mode . cbc . encrypt ( new sjcl . cipher . aes ( subKeys . cKey ) , uint8ArrayToBitArray ( bytes ) , uint8ArrayToBitArray ( iv ) , [ ] , usePadding ) ; let data = concat ( iv , bitArrayToUint8Array ( encryptedBits ) ) ; if ( useMac ) { let hmac = new sjcl . misc . hmac ( subKeys . mKey , sjcl . hash . sha256 ) ; let macBytes = bitArrayToUint8Array ( hmac . encrypt ( uint8ArrayToBitArray ( data ) ) ) ; data = concat ( new Uint8Array ( [ MAC_ENABLED_PREFIX ] ) , data , macBytes ) ; } return data ; } param key param encryptedBytes param usePadding param useMac return export function aes256Decrypt ( key , encryptedBytes , usePadding = true , useMac = true ) { verifyKeySize ( key , KEY_LENGTH_BITS_AES_256 ) ; let subKeys = getAes256SubKeys ( key , useMac ) ; let cipherTextWithoutMac ; if ( useMac ) { cipherTextWithoutMac = encryptedBytes . subarray ( 1 , encryptedBytes . length - MAC_LENGTH_BYTES ) ; let providedMacBytes = encryptedBytes . subarray ( encryptedBytes . length - MAC_LENGTH_BYTES ) ; let hmac = new sjcl . misc . hmac ( subKeys . mKey , sjcl . hash . sha256 ) ; let computedMacBytes = bitArrayToUint8Array ( hmac . encrypt ( uint8ArrayToBitArray ( cipherTextWithoutMac ) ) ) ; if ( ! arrayEquals ( providedMacBytes , computedMacBytes ) ) { throw new CryptoError ( "invalid mac" ) ; } } else { cipherTextWithoutMac = encryptedBytes ; } const iv = cipherTextWithoutMac . slice ( 0 , IV_BYTE_LENGTH ) ; if ( iv . length !== IV_BYTE_LENGTH ) { throw new CryptoError ( `Invalid IV length in AES256Decrypt: ${ iv . length } bytes, must be 16 bytes (128 bits)` ) ; } let ciphertext = cipherTextWithoutMac . slice ( IV_BYTE_LENGTH ) ; try { let decrypted = sjcl . mode . cbc . decrypt ( new sjcl . cipher . aes ( subKeys . cKey ) , uint8ArrayToBitArray ( ciphertext ) , uint8ArrayToBitArray ( iv ) , [ ] , usePadding ) ; return new Uint8Array ( bitArrayToUint8Array ( decrypted ) ) ; } catch ( e ) { throw new CryptoError ( "aes decryption failed" , e as Error ) ; } } function verifyKeySize ( key , bitLength ) { if ( sjcl . bitArray . bitLength ( key ) !== bitLength ) { throw new CryptoError ( `Illegal key length: ${ sjcl . bitArray . bitLength ( key ) } (expected: ${ bitLength })` ) ; } } /************************ Legacy AES128 ************************/ export function aes128RandomKey ( ) { return uint8ArrayToBitArray ( random . generateRandomData ( KEY_LENGTH_BYTES_AES_128 ) ) ; } param key param bytes param iv param usePadding return export function aes128Encrypt ( key , bytes , iv , usePadding , useMac ) { verifyKeySize ( key , KEY_LENGTH_BITS_AES_128 ) ; if ( iv . length !== IV_BYTE_LENGTH ) { throw new CryptoError ( `Illegal IV length: ${ iv . length } (expected: ${ IV_BYTE_LENGTH }): ${ uint8ArrayToBase64 ( iv ) } ` ) ; } let subKeys = getAes128SubKeys ( key , useMac ) ; let encryptedBits = sjcl . mode . cbc . encrypt ( new sjcl . cipher . aes ( subKeys . cKey ) , uint8ArrayToBitArray ( bytes ) , uint8ArrayToBitArray ( iv ) , [ ] , usePadding ) ; let data = concat ( iv , bitArrayToUint8Array ( encryptedBits ) ) ; if ( useMac ) { let hmac = new sjcl . misc . hmac ( subKeys . mKey , sjcl . hash . sha256 ) ; let macBytes = bitArrayToUint8Array ( hmac . encrypt ( uint8ArrayToBitArray ( data ) ) ) ; data = concat ( new Uint8Array ( [ MAC_ENABLED_PREFIX ] ) , data , macBytes ) ; } return data ; } param key param encryptedBytes param usePadding return export function aes128Decrypt ( key , encryptedBytes , usePadding = true ) { verifyKeySize ( key , KEY_LENGTH_BITS_AES_128 ) ; let useMac = encryptedBytes . length % 2 === 1 ; let subKeys = getAes128SubKeys ( key , useMac ) ; let cipherTextWithoutMac ; if ( useMac ) { cipherTextWithoutMac = encryptedBytes . subarray ( 1 , encryptedBytes . length - MAC_LENGTH_BYTES ) ; let providedMacBytes = encryptedBytes . subarray ( encryptedBytes . length - MAC_LENGTH_BYTES ) ; let hmac = new sjcl . misc . hmac ( subKeys . mKey , sjcl . hash . sha256 ) ; let computedMacBytes = bitArrayToUint8Array ( hmac . encrypt ( uint8ArrayToBitArray ( cipherTextWithoutMac ) ) ) ; if ( ! arrayEquals ( providedMacBytes , computedMacBytes ) ) { throw new CryptoError ( "invalid mac" ) ; } } else { cipherTextWithoutMac = encryptedBytes ; } const iv = cipherTextWithoutMac . slice ( 0 , IV_BYTE_LENGTH ) ; if ( iv . length !== IV_BYTE_LENGTH ) { throw new CryptoError ( `Invalid IV length in AES128Decrypt: ${ iv . length } bytes, must be 16 bytes (128 bits)` ) ; } let ciphertext = cipherTextWithoutMac . slice ( IV_BYTE_LENGTH ) ; try { let decrypted = sjcl . mode . cbc . decrypt ( new sjcl . cipher . aes ( subKeys . cKey ) , uint8ArrayToBitArray ( ciphertext ) , uint8ArrayToBitArray ( iv ) , [ ] , usePadding ) ; return new Uint8Array ( bitArrayToUint8Array ( decrypted ) ) ; } catch ( e ) { throw new CryptoError ( "aes decryption failed" , e as Error ) ; } } function getAes128SubKeys ( key , mac ) { if ( mac ) { let hashedKey = sha256Hash ( bitArrayToUint8Array ( key ) ) ; return { cKey : uint8ArrayToBitArray ( hashedKey . subarray ( 0 , KEY_LENGTH_BYTES_AES_128 ) ) , mKey : uint8ArrayToBitArray ( hashedKey . subarray ( KEY_LENGTH_BYTES_AES_128 , KEY_LENGTH_BYTES_AES_128 * 2 ) ) , } ; } else { return { cKey : key , mKey : null , } ; } } function getAes256SubKeys ( key , mac ) { if ( mac ) { let hashedKey = sha512Hash ( bitArrayToUint8Array ( key ) ) ; return { cKey : uint8ArrayToBitArray ( hashedKey . subarray ( 0 , KEY_LENGTH_BYTES_AES_256 ) ) , mKey : uint8ArrayToBitArray ( hashedKey . subarray ( KEY_LENGTH_BYTES_AES_256 , KEY_LENGTH_BYTES_AES_256 * 2 ) ) , } ; } else { return { cKey : key , mKey : null , } ; } }