@ivujs/i-utils
Version:
前端模块化 JavaScript 工具库
530 lines (459 loc) • 21.9 kB
JavaScript
import * as index from '../base64/index.mjs';
const { encode, decode, utf8Decode, utf8Encode } = index;
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* AES implementation in JavaScript (c) Chris Veness 2005-2019 */
/* MIT Licence */
/* www.movable-type.co.uk/scripts/aes.html */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/**
* AES (Rijndael cipher) encryption routines reference implementation,
*
* This is an annotated direct implementation of FIPS 197, without any optimisations. It is
* intended to aid understanding of the algorithm rather than for production use.
*
* While it could be used where performance is not critical, I would recommend using the ‘Web
* Cryptography API’ (developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt) for the browser,
* or the ‘crypto’ library (nodejs.org/api/crypto.html#crypto_class_cipher) in Node.js.
*
* See csrc.nist.gov/publications/fips/fips197/fips-197.pdf
*/
class Aes {
/**
* AES Cipher function: encrypt 'input' state with Rijndael algorithm [§5.1];
* applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage.
*
* @param {number[]} input - 16-byte (128-bit) input state array.
* @param {number[][]} w - Key schedule as 2D byte-array (Nr+1 × Nb bytes).
* @returns {number[]} Encrypted output state array.
*/
static cipher(input, w) {
const Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
const Nr = w.length / Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys
let state = [[], [], [], []]; // initialise 4×Nb byte-array 'state' with input [§3.4]
for (let i = 0; i < 4 * Nb; i++) state[i % 4][Math.floor(i / 4)] = input[i];
state = Aes.addRoundKey(state, w, 0, Nb);
for (let round = 1; round < Nr; round++) {
state = Aes.subBytes(state, Nb);
state = Aes.shiftRows(state, Nb);
state = Aes.mixColumns(state, Nb);
state = Aes.addRoundKey(state, w, round, Nb);
}
state = Aes.subBytes(state, Nb);
state = Aes.shiftRows(state, Nb);
state = Aes.addRoundKey(state, w, Nr, Nb);
const output = new Array(4 * Nb); // convert state to 1-d array before returning [§3.4]
for (let i = 0; i < 4 * Nb; i++) output[i] = state[i % 4][Math.floor(i / 4)];
return output;
}
/**
* Perform key expansion to generate a key schedule from a cipher key [§5.2].
*
* @param {number[]} key - Cipher key as 16/24/32-byte array.
* @returns {number[][]} Expanded key schedule as 2D byte-array (Nr+1 × Nb bytes).
*/
static keyExpansion(key) {
const Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES)
const Nk = key.length / 4; // key length (in words): 4/6/8 for 128/192/256-bit keys
const Nr = Nk + 6; // no of rounds: 10/12/14 for 128/192/256-bit keys
const w = new Array(Nb * (Nr + 1));
let temp = new Array(4);
// initialise first Nk words of expanded key with cipher key
for (let i = 0; i < Nk; i++) {
const r = [key[4 * i], key[4 * i + 1], key[4 * i + 2], key[4 * i + 3]];
w[i] = r;
}
// expand the key into the remainder of the schedule
for (let i = Nk; i < Nb * (Nr + 1); i++) {
w[i] = new Array(4);
for (let t = 0; t < 4; t++) temp[t] = w[i - 1][t];
// each Nk'th word has extra transformation
if (i % Nk == 0) {
temp = Aes.subWord(Aes.rotWord(temp));
for (let t = 0; t < 4; t++) temp[t] ^= Aes.rCon[i / Nk][t];
}
// 256-bit key has subWord applied every 4th word
else if (Nk > 6 && i % Nk == 4) {
temp = Aes.subWord(temp);
}
// xor w[i] with w[i-1] and w[i-Nk]
for (let t = 0; t < 4; t++) w[i][t] = w[i - Nk][t] ^ temp[t];
}
return w;
}
/**
* Apply SBox to state S [§5.1.1].
*
* @private
*/
static subBytes(s, Nb) {
for (let r = 0; r < 4; r++) {
for (let c = 0; c < Nb; c++) s[r][c] = Aes.sBox[s[r][c]];
}
return s;
}
/**
* Shift row r of state S left by r bytes [§5.1.2].
*
* @private
*/
static shiftRows(s, Nb) {
const t = new Array(4);
for (let r = 1; r < 4; r++) {
for (let c = 0; c < 4; c++) t[c] = s[r][(c + r) % Nb]; // shift into temp copy
for (let c = 0; c < 4; c++) s[r][c] = t[c]; // and copy back
} // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):
return s; // see asmaes.sourceforge.net/rijndael/rijndaelImplementation.pdf
}
/**
* Combine bytes of each col of state S [§5.1.3].
*
* @private
*/
static mixColumns(s, Nb) {
for (let c = 0; c < Nb; c++) {
const a = new Array(Nb); // 'a' is a copy of the current column from 's'
const b = new Array(Nb); // 'b' is a•{02} in GF(2^8)
for (let r = 0; r < 4; r++) {
a[r] = s[r][c];
b[r] = s[r][c] & 0x80 ? (s[r][c] << 1) ^ 0x011b : s[r][c] << 1;
}
// a[n] ^ b[n] is a•{03} in GF(2^8)
s[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; // {02}•a0 + {03}•a1 + a2 + a3
s[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 • {02}•a1 + {03}•a2 + a3
s[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + {02}•a2 + {03}•a3
s[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; // {03}•a0 + a1 + a2 + {02}•a3
}
return s;
}
/**
* Xor Round Key into state S [§5.1.4].
*
* @private
*/
static addRoundKey(state, w, rnd, Nb) {
for (let r = 0; r < 4; r++) {
for (let c = 0; c < Nb; c++) state[r][c] ^= w[rnd * 4 + c][r];
}
return state;
}
/**
* Apply SBox to 4-byte word w.
*
* @private
*/
static subWord(w) {
for (let i = 0; i < 4; i++) w[i] = Aes.sBox[w[i]];
return w;
}
/**
* Rotate 4-byte word w left by one byte.
*
* @private
*/
static rotWord(w) {
const tmp = w[0];
for (let i = 0; i < 3; i++) w[i] = w[i + 1];
w[3] = tmp;
return w;
}
}
// sBox is pre-computed multiplicative inverse in GF(2^8) used in subBytes and keyExpansion [§5.1.1]
Aes.sBox = [
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9,
0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f,
0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07,
0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3,
0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58,
0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3,
0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f,
0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac,
0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a,
0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70,
0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11,
0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42,
0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
];
// rCon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [§5.2]
Aes.rCon = [
[0x00, 0x00, 0x00, 0x00],
[0x01, 0x00, 0x00, 0x00],
[0x02, 0x00, 0x00, 0x00],
[0x04, 0x00, 0x00, 0x00],
[0x08, 0x00, 0x00, 0x00],
[0x10, 0x00, 0x00, 0x00],
[0x20, 0x00, 0x00, 0x00],
[0x40, 0x00, 0x00, 0x00],
[0x80, 0x00, 0x00, 0x00],
[0x1b, 0x00, 0x00, 0x00],
[0x36, 0x00, 0x00, 0x00],
];
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* AES counter-mode (CTR) implementation in JavaScript (c) Chris Veness 2005-2019 */
/* MIT Licence */
/* www.movable-type.co.uk/scripts/aes.html */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* global WorkerGlobalScope */
/**
* AesCtr: Counter-mode (CTR) wrapper for AES.
*
* This encrypts a Unicode string to produces a base64 ciphertext using 128/192/256-bit AES,
* and the converse to decrypt an encrypted ciphertext.
*
* See csrc.nist.gov/publications/detail/sp/800-38a/final
*/
class AesCtr extends Aes {
/**
* Encrypt a text using AES encryption in Counter mode of operation.
*
* Unicode multi-byte character safe.
*
* @param {string} plaintext - Source text to be encrypted.
* @param {string} password - The password to use to generate a key for encryption.
* @param {number} nBits - Number of bits to be used in the key; 128 / 192 / 256.
* @returns {string} Encrypted text, base-64 encoded.
*
* @example
* const encr = AesCtr.encrypt('big secret', 'pāşšŵōřđ', 256); // 'lwGl66VVwVObKIr6of8HVqJr'
*/
static encrypt(plaintext, password, nBits) {
if (![128, 192, 256].includes(nBits)) throw new Error("Key size is not 128 / 192 / 256");
plaintext = AesCtr.utf8Encode(String(plaintext));
password = AesCtr.utf8Encode(String(password));
// use AES itself to encrypt password to get cipher key (using plain password as source for key
// expansion) to give us well encrypted key (in real use hashed password could be used for key)
const nBytes = nBits / 8; // no bytes in key (16/24/32)
const pwBytes = new Array(nBytes);
for (let i = 0; i < nBytes; i++) {
// use 1st 16/24/32 chars of password for key
pwBytes[i] = i < password.length ? password.charCodeAt(i) : 0;
}
let key = Aes.cipher(pwBytes, Aes.keyExpansion(pwBytes)); // gives us 16-byte key
key = key.concat(key.slice(0, nBytes - 16)); // expand key to 16/24/32 bytes long
// initialise 1st 8 bytes of counter block with nonce (NIST SP 800-38A §B.2): [0-1] = millisec,
// [2-3] = random, [4-7] = seconds, together giving full sub-millisec uniqueness up to Feb 2106
const timestamp = new Date().getTime(); // milliseconds since 1-Jan-1970
const nonceMs = timestamp % 1000;
const nonceSec = Math.floor(timestamp / 1000);
const nonceRnd = Math.floor(Math.random() * 0xffff);
// for debugging: const [ nonceMs, nonceSec, nonceRnd ] = [ 0, 0, 0 ];
const counterBlock = [
// 16-byte array; blocksize is fixed at 16 for AES
nonceMs & 0xff,
(nonceMs >>> 8) & 0xff,
nonceRnd & 0xff,
(nonceRnd >>> 8) & 0xff,
nonceSec & 0xff,
(nonceSec >>> 8) & 0xff,
(nonceSec >>> 16) & 0xff,
(nonceSec >>> 24) & 0xff,
0,
0,
0,
0,
0,
0,
0,
0,
];
// and convert nonce to a string to go on the front of the ciphertext
const nonceStr = counterBlock
.slice(0, 8)
.map((i) => String.fromCharCode(i))
.join("");
// convert (utf-8) plaintext to byte array
const plaintextBytes = plaintext.split("").map((ch) => ch.charCodeAt(0));
// ------------ perform encryption ------------
const ciphertextBytes = AesCtr.nistEncryption(plaintextBytes, key, counterBlock);
// convert byte array to (utf-8) ciphertext string
const ciphertextUtf8 = ciphertextBytes.map((i) => String.fromCharCode(i)).join("");
// base-64 encode ciphertext
const ciphertextB64 = AesCtr.base64Encode(nonceStr + ciphertextUtf8);
return ciphertextB64;
}
/**
* NIST SP 800-38A sets out recommendations for block cipher modes of operation in terms of byte
* operations. This implements the §6.5 Counter Mode (CTR).
*
* Oⱼ = CIPHₖ(Tⱼ) for j = 1, 2 … n
* Cⱼ = Pⱼ ⊕ Oⱼ for j = 1, 2 … n-1
* C*ₙ = P* ⊕ MSBᵤ(Oₙ) final (partial?) block
* where CIPHₖ is the forward cipher function, O output blocks, P plaintext blocks, C
* ciphertext blocks
*
* @param {number[]} plaintext - Plaintext to be encrypted, as byte array.
* @param {number[]} key - Key to be used to encrypt plaintext.
* @param {number[]} counterBlock - Initial 16-byte CTR counter block (with nonce & 0 counter).
* @returns {number[]} Ciphertext as byte array.
*
* @private
*/
static nistEncryption(plaintext, key, counterBlock) {
const blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
// generate key schedule - an expansion of the key into distinct Key Rounds for each round
const keySchedule = Aes.keyExpansion(key);
const blockCount = Math.ceil(plaintext.length / blockSize);
const ciphertext = new Array(plaintext.length);
for (let b = 0; b < blockCount; b++) {
// ---- encrypt counter block; Oⱼ = CIPHₖ(Tⱼ) ----
const cipherCntr = Aes.cipher(counterBlock, keySchedule);
// block size is reduced on final block
const blockLength = b < blockCount - 1 ? blockSize : ((plaintext.length - 1) % blockSize) + 1;
// ---- xor plaintext with ciphered counter byte-by-byte; Cⱼ = Pⱼ ⊕ Oⱼ ----
for (let i = 0; i < blockLength; i++) {
ciphertext[b * blockSize + i] = cipherCntr[i] ^ plaintext[b * blockSize + i];
}
// increment counter block (counter in 2nd 8 bytes of counter block, big-endian)
counterBlock[blockSize - 1]++;
// and propagate carry digits
for (let i = blockSize - 1; i >= 8; i--) {
counterBlock[i - 1] += counterBlock[i] >> 8;
counterBlock[i] &= 0xff;
}
// if within web worker, announce progress every 1000 blocks (roughly every 50ms)
if (typeof WorkerGlobalScope != "undefined" && self instanceof WorkerGlobalScope) {
if (b % 1000 == 0) self.postMessage({ progress: b / blockCount });
}
}
return ciphertext;
}
/**
* Decrypt a text encrypted by AES in counter mode of operation.
*
* @param {string} ciphertext - Cipher text to be decrypted.
* @param {string} password - Password to use to generate a key for decryption.
* @param {number} nBits - Number of bits to be used in the key; 128 / 192 / 256.
* @returns {string} Decrypted text
*
* @example
* const decr = AesCtr.decrypt('lwGl66VVwVObKIr6of8HVqJr', 'pāşšŵōřđ', 256); // 'big secret'
*/
static decrypt(ciphertext, password, nBits) {
if (![128, 192, 256].includes(nBits)) throw new Error("Key size is not 128 / 192 / 256");
ciphertext = AesCtr.base64Decode(String(ciphertext));
password = AesCtr.utf8Encode(String(password));
// use AES to encrypt password (mirroring encrypt routine)
const nBytes = nBits / 8; // no bytes in key
const pwBytes = new Array(nBytes);
for (let i = 0; i < nBytes; i++) {
// use 1st nBytes chars of password for key
pwBytes[i] = i < password.length ? password.charCodeAt(i) : 0;
}
let key = Aes.cipher(pwBytes, Aes.keyExpansion(pwBytes));
key = key.concat(key.slice(0, nBytes - 16)); // expand key to 16/24/32 bytes long
// recover nonce from 1st 8 bytes of ciphertext into 1st 8 bytes of counter block
const counterBlock = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
for (let i = 0; i < 8; i++) counterBlock[i] = ciphertext.charCodeAt(i);
// convert ciphertext to byte array (skipping past initial 8 bytes)
const ciphertextBytes = new Array(ciphertext.length - 8);
for (let i = 8; i < ciphertext.length; i++) ciphertextBytes[i - 8] = ciphertext.charCodeAt(i);
// ------------ perform decryption ------------
const plaintextBytes = AesCtr.nistDecryption(ciphertextBytes, key, counterBlock);
// convert byte array to (utf-8) plaintext string
const plaintextUtf8 = plaintextBytes.map((i) => String.fromCharCode(i)).join("");
// decode from UTF8 back to Unicode multi-byte chars
const plaintext = AesCtr.utf8Decode(plaintextUtf8);
return plaintext;
}
/**
* NIST SP 800-38A sets out recommendations for block cipher modes of operation in terms of byte
* operations. This implements the §6.5 Counter Mode (CTR).
*
* Oⱼ = CIPHₖ(Tⱼ) for j = 1, 2 … n
* Pⱼ = Cⱼ ⊕ Oⱼ for j = 1, 2 … n-1
* P*ₙ = C* ⊕ MSBᵤ(Oₙ) final (partial?) block
* where CIPHₖ is the forward cipher function, O output blocks, C ciphertext blocks, P
* plaintext blocks
*
* @param {number[]} ciphertext - Ciphertext to be decrypted, as byte array.
* @param {number[]} key - Key to be used to decrypt ciphertext.
* @param {number[]} counterBlock - Initial 16-byte CTR counter block (with nonce & 0 counter).
* @returns {number[]} Plaintext as byte array.
*
* @private
*/
static nistDecryption(ciphertext, key, counterBlock) {
const blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
// generate key schedule - an expansion of the key into distinct Key Rounds for each round
const keySchedule = Aes.keyExpansion(key);
const blockCount = Math.ceil(ciphertext.length / blockSize);
const plaintext = new Array(ciphertext.length);
for (let b = 0; b < blockCount; b++) {
// ---- decrypt counter block; Oⱼ = CIPHₖ(Tⱼ) ----
const cipherCntr = Aes.cipher(counterBlock, keySchedule);
// block size is reduced on final block
const blockLength = b < blockCount - 1 ? blockSize : ((ciphertext.length - 1) % blockSize) + 1;
// ---- xor ciphertext with ciphered counter byte-by-byte; Pⱼ = Cⱼ ⊕ Oⱼ ----
for (let i = 0; i < blockLength; i++) {
plaintext[b * blockSize + i] = cipherCntr[i] ^ ciphertext[b * blockSize + i];
}
// increment counter block (counter in 2nd 8 bytes of counter block, big-endian)
counterBlock[blockSize - 1]++;
// and propagate carry digits
for (let i = blockSize - 1; i >= 8; i--) {
counterBlock[i - 1] += counterBlock[i] >> 8;
counterBlock[i] &= 0xff;
}
// if within web worker, announce progress every 1000 blocks (roughly every 50ms)
if (typeof WorkerGlobalScope != "undefined" && self instanceof WorkerGlobalScope) {
if (b % 1000 == 0) self.postMessage({ progress: b / blockCount });
}
}
return plaintext;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/**
* Encodes multi-byte string to utf8.
*
* Note utf8Encode is an identity function with 7-bit ascii strings, but not with 8-bit strings;
* utf8Encode('x') = 'x', but utf8Encode('ça') = 'ça', and utf8Encode('ça') = 'ça'.
*/
static utf8Encode(str) {
// 注释第三方js的代码
// try {
// return new TextEncoder().encode(str, "utf-8").reduce((prev, curr) => prev + String.fromCharCode(curr), "");
// } catch (e) {
// // no TextEncoder available?
// return unescape(encodeURIComponent(str)); // monsur.hossa.in/2012/07/20/utf-8-in-javascript.html
// }
return utf8Encode(str);
}
/**
* Decodes utf8 string to multi-byte.
*/
static utf8Decode(str) {
// 注释第三方js的代码
// try {
// return new TextEncoder().decode(str, "utf-8").reduce((prev, curr) => prev + String.fromCharCode(curr), "");
// } catch (e) {
// // no TextEncoder available?
// return decodeURIComponent(escape(str)); // monsur.hossa.in/2012/07/20/utf-8-in-javascript.html
// }
return utf8Decode(str);
}
/*
* Encodes string as base-64.
*
* - developer.mozilla.org/en-US/docs/Web/API/window.btoa, nodejs.org/api/buffer.html
* - note: btoa & Buffer/binary work on single-byte Unicode (C0/C1), so ok for utf8 strings, not for general Unicode...
* - note: if btoa()/atob() are not available (eg IE9-), try github.com/davidchambers/Base64.js
*/
static base64Encode(str) {
// 注释第三方js的代码
// if (typeof btoa != "undefined") return btoa(str); // browser
// if (typeof Buffer != "undefined") return new Buffer(str, "binary").toString("base64"); // Node.js
// throw new Error("No Base64 Encode");
return encode(str);
}
/*
* Decodes base-64 encoded string.
*/
static base64Decode(str) {
// if (typeof atob != "undefined") return atob(str); // browser
// if (typeof Buffer != "undefined") return new Buffer(str, "base64").toString("binary"); // Node.js
// throw new Error("No Base64 Decode");
return decode(str);
}
}
export { AesCtr as default };