@cloudpss/crypto 0.5.24 → 0.5.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/benchmark.js +44 -0
  2. package/dist/encryption/browser.d.ts +3 -3
  3. package/dist/encryption/browser.js.map +1 -1
  4. package/dist/encryption/common.d.ts +45 -16
  5. package/dist/encryption/common.js +57 -9
  6. package/dist/encryption/common.js.map +1 -1
  7. package/dist/encryption/index.d.ts +4 -21
  8. package/dist/encryption/index.js +11 -63
  9. package/dist/encryption/index.js.map +1 -1
  10. package/dist/encryption/js/aes.d.ts +20 -0
  11. package/dist/encryption/js/aes.js +151 -0
  12. package/dist/encryption/js/aes.js.map +1 -0
  13. package/dist/encryption/js/gcm.d.ts +26 -0
  14. package/dist/encryption/js/gcm.js +226 -0
  15. package/dist/encryption/js/gcm.js.map +1 -0
  16. package/dist/encryption/module.d.ts +22 -0
  17. package/dist/encryption/module.js +62 -0
  18. package/dist/encryption/module.js.map +1 -0
  19. package/dist/encryption/node.d.ts +3 -3
  20. package/dist/encryption/node.js +19 -15
  21. package/dist/encryption/node.js.map +1 -1
  22. package/dist/encryption/pure-js.d.ts +3 -3
  23. package/dist/encryption/pure-js.js +70 -42
  24. package/dist/encryption/pure-js.js.map +1 -1
  25. package/dist/encryption/web.d.ts +3 -3
  26. package/dist/encryption/web.js +17 -15
  27. package/dist/encryption/web.js.map +1 -1
  28. package/dist/index.d.ts +1 -1
  29. package/dist/index.js +1 -1
  30. package/dist/index.js.map +1 -1
  31. package/package.json +7 -4
  32. package/src/encryption/browser.ts +3 -3
  33. package/src/encryption/common.ts +79 -16
  34. package/src/encryption/index.ts +12 -71
  35. package/src/encryption/js/aes.ts +191 -0
  36. package/src/encryption/js/gcm.ts +258 -0
  37. package/src/encryption/module.ts +94 -0
  38. package/src/encryption/node.ts +24 -15
  39. package/src/encryption/pure-js.ts +89 -46
  40. package/src/encryption/web.ts +24 -15
  41. package/src/index.ts +1 -1
  42. package/tests/encryption.js +126 -49
@@ -1 +1 @@
1
- {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/encryption/web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,gBAAgB,EAAyB,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEpH,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;AAElC,wBAAwB;AACxB,KAAK,UAAU,SAAS,CAAC,UAAkB,EAAE,IAAgB;IACzD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAC9G,MAAM,YAAY,GAAiB;QAC/B,IAAI,EAAE,QAAQ;QACd,IAAI;QACJ,UAAU,EAAE,iBAAiB;QAC7B,IAAI,EAAE,SAAS;KAClB,CAAC;IACF,OAAO,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE;QAC3G,SAAS;QACT,SAAS;KACZ,CAAC,CAAC;AACP,CAAC;AAED,wBAAwB;AACxB,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,IAAgB,EAAE,UAAkB;IAC9D,MAAM,IAAI,GAAG,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,gBAAgB,CAAC,CAAC,CAAC;IACtE,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAC9C,MAAM,EAAE,GAAG,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;IAC/D,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,OAAO,CACzC;QACI,IAAI,EAAE,SAAS;QACf,EAAE;KACL,EACD,GAAG,EACH,IAAI,CACP,CAAC;IACF,OAAO;QACH,IAAI,EAAE,IAAI;QACV,EAAE,EAAE,EAAE;QACN,IAAI,EAAE,IAAI,UAAU,CAAC,SAAS,CAAC;KAClC,CAAC;AACN,CAAC;AAED,wBAAwB;AACxB,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAoB,EAAE,UAAkB;IAClF,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;IAC9C,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,OAAO,CACzC;QACI,IAAI,EAAE,SAAS;QACf,EAAE;KACL,EACD,GAAG,EACH,IAAI,CACP,CAAC;IACF,OAAO,IAAI,UAAU,CAAC,SAAS,CAAC,CAAC;AACrC,CAAC"}
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/encryption/web.ts"],"names":[],"mappings":"AAAA,OAAO,EACH,UAAU,EACV,YAAY,EACZ,YAAY,EAGZ,iBAAiB,GACpB,MAAM,aAAa,CAAC;AAErB,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;AAElC,wBAAwB;AACxB,KAAK,UAAU,SAAS,CAAC,UAAkB,EAAE,IAAgB;IACzD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;IAC9G,MAAM,YAAY,GAAiB;QAC/B,IAAI,EAAE,QAAQ;QACd,IAAI;QACJ,UAAU,EAAE,iBAAiB;QAC7B,IAAI,EAAE,SAAS;KAClB,CAAC;IACF,OAAO,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,YAAY,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE;QAC3G,SAAS;QACT,SAAS;KACZ,CAAC,CAAC;AACP,CAAC;AAED,wBAAwB;AACxB,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,EAAa,EAAE,UAAkB;IACtE,MAAM,KAAK,GAAG,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;IACjE,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAC/C,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,OAAO,CACzC;QACI,IAAI,EAAE,SAAS;QACf,EAAE,EAAE,KAAK;QACT,SAAS,EAAE,YAAY,GAAG,CAAC;QAC3B,cAAc,EAAE,GAAG;KACtB,EACD,GAAG,EACH,IAAI,CACP,CAAC;IACF,OAAO;QACH,KAAK;QACL,IAAI,EAAE,IAAI,UAAU,CAAC,SAAS,CAAC;KAClC,CAAC;AACN,CAAC;AAED,wBAAwB;AACxB,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAiB,EAAE,UAAkB;IACjF,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAC/C,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,OAAO,CACzC;QACI,IAAI,EAAE,SAAS;QACf,EAAE,EAAE,KAAK;QACT,SAAS,EAAE,YAAY,GAAG,CAAC;QAC3B,cAAc,EAAE,GAAG;KACtB,EACD,GAAG,EACH,IAAI,CACP,CAAC;IACF,OAAO,EAAE,IAAI,EAAE,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;AAC/C,CAAC"}
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { isEncrypted, encrypt, decrypt } from './encryption/index.js';
1
+ export { isEncrypted, encrypt, decrypt, encryptAad, extractAad } from './encryption/index.js';
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- export { isEncrypted, encrypt, decrypt } from './encryption/index.js';
1
+ export { isEncrypted, encrypt, decrypt, encryptAad, extractAad } from './encryption/index.js';
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudpss/crypto",
3
- "version": "0.5.24",
3
+ "version": "0.5.25",
4
4
  "author": "CloudPSS",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -16,16 +16,19 @@
16
16
  }
17
17
  },
18
18
  "dependencies": {
19
- "crypto-js": "^4.2.0",
20
- "hash-wasm": "^4.11.0"
19
+ "sjcl": "^1.0.8"
21
20
  },
22
21
  "devDependencies": {
23
- "@types/crypto-js": "^4.2.2"
22
+ "crypto-js": "^4.2.0",
23
+ "hash-wasm": "^4.11.0",
24
+ "@types/crypto-js": "^4.2.2",
25
+ "@types/sjcl": "^1.0.34"
24
26
  },
25
27
  "scripts": {
26
28
  "start": "pnpm clean && tsc --watch",
27
29
  "build": "pnpm clean && tsc",
28
30
  "test": "NODE_OPTIONS=\"${NODE_OPTIONS:-} --experimental-vm-modules\" jest",
31
+ "benchmark": "node ./benchmark",
29
32
  "clean": "rimraf dist"
30
33
  }
31
34
  }
@@ -1,4 +1,4 @@
1
- import type { EncryptionResult } from './common.js';
1
+ import type { EncryptedData, PlainData } from './common.js';
2
2
 
3
3
  // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
4
4
  const module = () => {
@@ -18,13 +18,13 @@ const module = () => {
18
18
  };
19
19
 
20
20
  /** browser encrypt */
21
- export async function encrypt(data: Uint8Array, passphrase: string): Promise<EncryptionResult> {
21
+ export async function encrypt(data: PlainData, passphrase: string): Promise<EncryptedData> {
22
22
  const { encrypt } = await module();
23
23
  return await encrypt(data, passphrase);
24
24
  }
25
25
 
26
26
  /** browser decrypt */
27
- export async function decrypt(data: EncryptionResult, passphrase: string): Promise<Uint8Array> {
27
+ export async function decrypt(data: EncryptedData, passphrase: string): Promise<PlainData> {
28
28
  const { decrypt } = await module();
29
29
  return await decrypt(data, passphrase);
30
30
  }
@@ -1,22 +1,85 @@
1
- /**
2
- * PBKDF2 迭代次数
3
- */
1
+ import { toUint8Array } from '../utils.js';
2
+
3
+ /** PBKDF2 迭代次数 */
4
4
  export const PBKDF2_ITERATIONS = 100_000;
5
- /**
6
- * PBKDF2 盐值长度(byte)
7
- */
8
- export const PBKDF2_SALT_SIZE = 128 / 8;
9
- /** IV 长度(byte) */
10
- export const AES_IV_SIZE = 128 / 8;
5
+ /** NONCE 长度(byte) */
6
+ export const NONCE_SIZE = 96 / 8;
7
+ /** AAD 最大长度(byte) */
8
+ export const AAD_MAX_SIZE = 1024 * 1024 * 1024; // 1GiB
9
+ /** AAD 长度字段长度(byte) */
10
+ export const AAD_LEN_SIZE = 4;
11
+
12
+ // 与 AES 一致对齐
13
+ /** AAD padding */
14
+ export const AAD_PADDING = 128 / 8;
11
15
  /** KEY 长度(byte) */
12
16
  export const AES_KEY_SIZE = 256 / 8;
17
+ /** Auth tag 长度(byte) */
18
+ export const AES_TAG_SIZE = 128 / 8;
19
+
20
+ /** 加密输入/解密结果 */
21
+ export interface PlainData {
22
+ /** 附加数据 */
23
+ aad?: Uint8Array;
24
+ /** 明文数据 */
25
+ data: Uint8Array;
26
+ }
13
27
 
14
- /** 加密结果 */
15
- export interface EncryptionResult {
16
- /** 盐值 */
17
- salt: Uint8Array;
18
- /** 初始向量 */
19
- iv: Uint8Array;
20
- /** 加密后的数据 */
28
+ /** 解密输入/加密结果 */
29
+ export interface EncryptedData {
30
+ /** NONCE */
31
+ nonce: Uint8Array;
32
+ /** 附加数据 */
33
+ aad?: Uint8Array;
34
+ /** 加密后的数据和 tag */
21
35
  data: Uint8Array;
22
36
  }
37
+
38
+ /**
39
+ * CloudPSS 数据加密
40
+ * - 密钥生成算法:PBKDF2-HMAC-SHA256,盐长度 96,迭代 100,000 次
41
+ * - 加密算法:AES-256-GCM,使用与密钥生成算法的盐作为 NONCE,TAG 长度 128
42
+ * - 附加数据:最大长度 0x7fff_ffff
43
+ *
44
+ * - 文件格式:
45
+ * - Magic Number: 0e 02 49 29 3f 07 7b 0a
46
+ * - Nonce: 96 bits
47
+ * - Length of AAD: 4 bytes
48
+ * - AAD (if exists)
49
+ * - Encrypted Data
50
+ * - Auth Tag: 128 bits
51
+ */
52
+
53
+ /** CloudPSS 数据加密 */
54
+ export const MAGIC_NUMBER = Uint8Array.from([0x0e, 0x02, 0x49, 0x29, 0x3f, 0x07, 0x7b, 0x0a]);
55
+
56
+ const MIN_ENCRYPTED_SIZE = MAGIC_NUMBER.length + NONCE_SIZE + AAD_LEN_SIZE + AES_TAG_SIZE;
57
+
58
+ /** 计算对齐后的长度 */
59
+ export function padding(size: number, padding: number): number {
60
+ return (size + padding - 1) & ~(padding - 1);
61
+ }
62
+
63
+ /** 解析 CloudPSS 加密数据 */
64
+ export function parseEncrypted(data: BinaryData): EncryptedData | undefined {
65
+ const buffer = toUint8Array(data);
66
+ if (buffer.byteLength < MIN_ENCRYPTED_SIZE) return undefined;
67
+ if (!MAGIC_NUMBER.every((v, i) => buffer[i] === v)) return undefined;
68
+ const nonce = buffer.subarray(MAGIC_NUMBER.length, MAGIC_NUMBER.length + NONCE_SIZE);
69
+ const aadSize =
70
+ (buffer[MAGIC_NUMBER.length + NONCE_SIZE] << 24) |
71
+ (buffer[MAGIC_NUMBER.length + NONCE_SIZE + 1] << 16) |
72
+ (buffer[MAGIC_NUMBER.length + NONCE_SIZE + 2] << 8) |
73
+ buffer[MAGIC_NUMBER.length + NONCE_SIZE + 3];
74
+ if (aadSize > AAD_MAX_SIZE || aadSize < 0) return undefined;
75
+ const paddingAadSize = padding(aadSize, AAD_PADDING);
76
+ if (buffer.byteLength < paddingAadSize + MIN_ENCRYPTED_SIZE) return undefined;
77
+ const aad = aadSize
78
+ ? buffer.subarray(
79
+ MAGIC_NUMBER.length + NONCE_SIZE + AAD_LEN_SIZE,
80
+ MAGIC_NUMBER.length + NONCE_SIZE + AAD_LEN_SIZE + aadSize,
81
+ )
82
+ : undefined;
83
+ const encrypted = buffer.subarray(MAGIC_NUMBER.length + NONCE_SIZE + AAD_LEN_SIZE + paddingAadSize);
84
+ return { nonce, aad, data: encrypted };
85
+ }
@@ -1,84 +1,25 @@
1
1
  import * as impl from '#encryption';
2
- import { toUint8Array } from '../utils.js';
3
- import { AES_IV_SIZE, PBKDF2_SALT_SIZE } from './common.js';
2
+ import { parseEncrypted } from './common.js';
3
+ import { createModule } from './module.js';
4
4
 
5
- /**
6
- * CloudPSS 数据加密
7
- * - 密钥生成算法:PBKDF2-HMAC-SHA256,盐长度 128,迭代 100,000 次
8
- * - 加密算法:AES-256-CBC
9
- *
10
- * - 文件格式:
11
- * - Magic Number: 0e 02 49 29 3f 07 7b 0a
12
- * - Salt: 128 bits
13
- * - IV: 128 bits
14
- * - Encrypted Data
15
- */
16
-
17
- /** CloudPSS 数据加密 */
18
- export const MAGIC_NUMBER = Uint8Array.from([0x0e, 0x02, 0x49, 0x29, 0x3f, 0x07, 0x7b, 0x0a]);
19
-
20
- /** 检查是否为 CloudPSS 加密数据 */
21
- function isEncryptedImpl(data: Uint8Array): boolean {
22
- return (
23
- data.length > MAGIC_NUMBER.length + PBKDF2_SALT_SIZE + AES_IV_SIZE &&
24
- MAGIC_NUMBER.every((v, i) => data[i] === v)
25
- );
26
- }
5
+ export { MAGIC_NUMBER } from './common.js';
27
6
 
28
7
  /** 检查是否为 CloudPSS 加密数据 */
29
8
  export function isEncrypted(data: BinaryData): boolean {
30
- const buffer = toUint8Array(data);
31
- return isEncryptedImpl(buffer);
32
- }
33
-
34
- /** 检查密码 */
35
- function assertPassphrase(passphrase: string): void {
36
- if (typeof passphrase !== 'string') {
37
- throw new TypeError('Invalid passphrase, must be a string');
38
- }
39
- if (passphrase.length === 0) {
40
- throw new TypeError('Invalid passphrase, must not be empty');
41
- }
42
- }
43
-
44
- /**
45
- * 加密数据
46
- * @throws {TypeError} 如果密码无效
47
- */
48
- export async function encrypt(data: BinaryData, passphrase: string): Promise<Uint8Array> {
49
- assertPassphrase(passphrase);
50
- const buffer = toUint8Array(data);
51
- const encrypted = await impl.encrypt(buffer, passphrase);
52
- const result = new Uint8Array(MAGIC_NUMBER.length + PBKDF2_SALT_SIZE + AES_IV_SIZE + encrypted.data.length);
53
- result.set(MAGIC_NUMBER);
54
- result.set(encrypted.salt, MAGIC_NUMBER.length);
55
- result.set(encrypted.iv, MAGIC_NUMBER.length + PBKDF2_SALT_SIZE);
56
- result.set(encrypted.data, MAGIC_NUMBER.length + PBKDF2_SALT_SIZE + AES_IV_SIZE);
57
- return result;
9
+ return parseEncrypted(data) != null;
58
10
  }
59
11
 
60
12
  /**
61
- * 解密数据
13
+ * 从加密数据中提取附加数据
62
14
  * @throws {TypeError} 如果数据不是有效的加密数据
63
- * @throws {TypeError} 如果密码无效
64
15
  */
65
- export async function decrypt(data: BinaryData, passphrase: string): Promise<Uint8Array> {
66
- assertPassphrase(passphrase);
67
- const buffer = toUint8Array(data);
68
- if (!isEncryptedImpl(buffer)) {
16
+ export function extractAad(data: BinaryData): Uint8Array | undefined {
17
+ const encrypted = parseEncrypted(data);
18
+ if (encrypted == null) {
69
19
  throw new TypeError('Invalid encrypted data');
70
20
  }
71
- const encrypted = {
72
- salt: buffer.subarray(MAGIC_NUMBER.length, MAGIC_NUMBER.length + PBKDF2_SALT_SIZE),
73
- iv: buffer.subarray(
74
- MAGIC_NUMBER.length + PBKDF2_SALT_SIZE,
75
- MAGIC_NUMBER.length + PBKDF2_SALT_SIZE + AES_IV_SIZE,
76
- ),
77
- data: buffer.subarray(MAGIC_NUMBER.length + PBKDF2_SALT_SIZE + AES_IV_SIZE),
78
- };
79
- try {
80
- return await impl.decrypt(encrypted, passphrase);
81
- } catch (ex) {
82
- throw new Error('Wrong passphrase', { cause: ex });
83
- }
21
+ return encrypted.aad;
84
22
  }
23
+
24
+ // eslint-disable-next-line @typescript-eslint/unbound-method
25
+ export const { encrypt, decrypt, encryptAad } = createModule(impl);
@@ -0,0 +1,191 @@
1
+ /** AES S-box Tables */
2
+ type SBoxTable = [t0: Uint32Array, t1: Uint32Array, t2: Uint32Array, t3: Uint32Array, t4: Uint8Array];
3
+
4
+ /** Compute AES S-box Tables */
5
+ function createSBox(): [SBoxTable, SBoxTable] {
6
+ const encTable: SBoxTable = [
7
+ new Uint32Array(256),
8
+ new Uint32Array(256),
9
+ new Uint32Array(256),
10
+ new Uint32Array(256),
11
+ new Uint8Array(256),
12
+ ];
13
+ const decTable: SBoxTable = [
14
+ new Uint32Array(256),
15
+ new Uint32Array(256),
16
+ new Uint32Array(256),
17
+ new Uint32Array(256),
18
+ new Uint8Array(256),
19
+ ];
20
+
21
+ const sbox = encTable[4];
22
+ const sboxInv = decTable[4];
23
+
24
+ const d = new Uint8Array(256);
25
+ const th = new Uint8Array(256);
26
+
27
+ // Compute double and third tables
28
+ for (let i = 0; i < 256; i++) {
29
+ d[i] = (i << 1) ^ ((i >> 7) * 283);
30
+ th[d[i] ^ i] = i;
31
+ }
32
+
33
+ let x = 0,
34
+ xInv = 0,
35
+ x2 = 0,
36
+ x4 = 0,
37
+ x8 = 0;
38
+ for (; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
39
+ // Compute sbox
40
+ let s = xInv ^ (xInv << 1) ^ (xInv << 2) ^ (xInv << 3) ^ (xInv << 4);
41
+ s = (s >> 8) ^ (s & 255) ^ 99;
42
+ sbox[x] = s;
43
+ sboxInv[s] = x;
44
+
45
+ // Compute MixColumns
46
+ x8 = d[(x4 = d[(x2 = d[x])])];
47
+ let tDec = (x8 * 0x101_0101) ^ (x4 * 0x1_0001) ^ (x2 * 0x101) ^ (x * 0x101_0100);
48
+ let tEnc = (d[s] * 0x101) ^ (s * 0x101_0100);
49
+
50
+ for (let i = 0; i < 4; i++) {
51
+ encTable[i][x] = tEnc = (tEnc << 24) ^ (tEnc >>> 8);
52
+ decTable[i][s] = tDec = (tDec << 24) ^ (tDec >>> 8);
53
+ }
54
+ }
55
+
56
+ return [encTable, decTable];
57
+ }
58
+
59
+ let encryptTable: SBoxTable;
60
+ let decryptTable: SBoxTable;
61
+ /** 初始化 */
62
+ function init(): void {
63
+ if (!encryptTable) {
64
+ [encryptTable, decryptTable] = createSBox();
65
+ }
66
+ }
67
+ /** AES 算法 */
68
+ export class AES {
69
+ /** 加密密钥 */
70
+ private readonly encKey: Uint32Array;
71
+ /** 解密密钥 */
72
+ private readonly decKey: Uint32Array;
73
+ constructor(key: Uint32Array) {
74
+ if (key.length !== 4 && key.length !== 6 && key.length !== 8) {
75
+ throw new TypeError('Invalid aes key length');
76
+ }
77
+ init();
78
+
79
+ const sbox = encryptTable[4],
80
+ decTable = decryptTable,
81
+ keyLen = key.length,
82
+ rKeyLen = 4 * key.length + 28;
83
+
84
+ this.encKey = new Uint32Array(rKeyLen);
85
+ this.decKey = new Uint32Array(rKeyLen);
86
+ const { encKey, decKey } = this;
87
+
88
+ encKey.set(key);
89
+
90
+ // schedule encryption keys
91
+ let rcon = 1;
92
+ for (let i = keyLen; i < rKeyLen; i++) {
93
+ let tmp = this.encKey[i - 1];
94
+
95
+ // apply sbox
96
+ if (i % keyLen === 0 || (keyLen === 8 && i % keyLen === 4)) {
97
+ tmp =
98
+ (sbox[tmp >>> 24] << 24) ^
99
+ (sbox[(tmp >> 16) & 255] << 16) ^
100
+ (sbox[(tmp >> 8) & 255] << 8) ^
101
+ sbox[tmp & 255];
102
+
103
+ // shift rows and add rcon
104
+ if (i % keyLen === 0) {
105
+ tmp = (tmp << 8) ^ (tmp >>> 24) ^ (rcon << 24);
106
+ rcon = (rcon << 1) ^ ((rcon >> 7) * 283);
107
+ }
108
+ }
109
+
110
+ encKey[i] = encKey[i - keyLen] ^ tmp;
111
+ }
112
+
113
+ // schedule decryption keys
114
+ for (let i = rKeyLen, j = 0; i; j++, i--) {
115
+ const tmp = encKey[j & 3 ? i : i - 4];
116
+ if (i <= 4 || j < 4) {
117
+ decKey[j] = tmp;
118
+ } else {
119
+ decKey[j] =
120
+ decTable[0][sbox[tmp >>> 24]] ^
121
+ decTable[1][sbox[(tmp >> 16) & 255]] ^
122
+ decTable[2][sbox[(tmp >> 8) & 255]] ^
123
+ decTable[3][sbox[tmp & 255]];
124
+ }
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Encryption and decryption core.
130
+ */
131
+ private crypt(
132
+ input: Uint32Array,
133
+ inputOffset: number,
134
+ output: Uint32Array,
135
+ outputOffset: number,
136
+ decrypt: boolean,
137
+ ): void {
138
+ const key = decrypt ? this.decKey : this.encKey;
139
+ const [t0, t1, t2, t3, sbox] = decrypt ? decryptTable : encryptTable;
140
+
141
+ // state variables a,b,c,d are loaded with pre-whitened data
142
+ let a = input[inputOffset] ^ key[0],
143
+ b = input[inputOffset + (decrypt ? 3 : 1)] ^ key[1],
144
+ c = input[inputOffset + 2] ^ key[2],
145
+ d = input[inputOffset + (decrypt ? 1 : 3)] ^ key[3];
146
+
147
+ let kIndex = 4;
148
+
149
+ // Inner rounds. Cribbed from OpenSSL.
150
+ const nInnerRounds = key.length / 4 - 2;
151
+ for (let i = 0; i < nInnerRounds; i++) {
152
+ const a2 = t0[a >>> 24] ^ t1[(b >> 16) & 255] ^ t2[(c >> 8) & 255] ^ t3[d & 255] ^ key[kIndex];
153
+ const b2 = t0[b >>> 24] ^ t1[(c >> 16) & 255] ^ t2[(d >> 8) & 255] ^ t3[a & 255] ^ key[kIndex + 1];
154
+ const c2 = t0[c >>> 24] ^ t1[(d >> 16) & 255] ^ t2[(a >> 8) & 255] ^ t3[b & 255] ^ key[kIndex + 2];
155
+ d = t0[d >>> 24] ^ t1[(a >> 16) & 255] ^ t2[(b >> 8) & 255] ^ t3[c & 255] ^ key[kIndex + 3];
156
+ a = a2;
157
+ b = b2;
158
+ c = c2;
159
+ kIndex += 4;
160
+ }
161
+
162
+ // Last round.
163
+ for (let i = 0; i < 4; i++) {
164
+ output[outputOffset + (decrypt ? 3 & -i : i)] =
165
+ (sbox[a >>> 24] << 24) ^
166
+ (sbox[(b >> 16) & 255] << 16) ^
167
+ (sbox[(c >> 8) & 255] << 8) ^
168
+ sbox[d & 255] ^
169
+ key[kIndex++];
170
+ const a2 = a;
171
+ a = b;
172
+ b = c;
173
+ c = d;
174
+ d = a2;
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Encrypt a block of plain text.
180
+ */
181
+ encrypt(input: Uint32Array, inputOffset: number, output: Uint32Array, outputOffset: number): void {
182
+ return this.crypt(input, inputOffset, output, outputOffset, false);
183
+ }
184
+
185
+ /**
186
+ * Decrypt a block of cipher text.
187
+ */
188
+ decrypt(input: Uint32Array, inputOffset: number, output: Uint32Array, outputOffset: number): void {
189
+ return this.crypt(input, inputOffset, output, outputOffset, true);
190
+ }
191
+ }