@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.
- package/benchmark.js +44 -0
- package/dist/encryption/browser.d.ts +3 -3
- package/dist/encryption/browser.js.map +1 -1
- package/dist/encryption/common.d.ts +45 -16
- package/dist/encryption/common.js +57 -9
- package/dist/encryption/common.js.map +1 -1
- package/dist/encryption/index.d.ts +4 -21
- package/dist/encryption/index.js +11 -63
- package/dist/encryption/index.js.map +1 -1
- package/dist/encryption/js/aes.d.ts +20 -0
- package/dist/encryption/js/aes.js +151 -0
- package/dist/encryption/js/aes.js.map +1 -0
- package/dist/encryption/js/gcm.d.ts +26 -0
- package/dist/encryption/js/gcm.js +226 -0
- package/dist/encryption/js/gcm.js.map +1 -0
- package/dist/encryption/module.d.ts +22 -0
- package/dist/encryption/module.js +62 -0
- package/dist/encryption/module.js.map +1 -0
- package/dist/encryption/node.d.ts +3 -3
- package/dist/encryption/node.js +19 -15
- package/dist/encryption/node.js.map +1 -1
- package/dist/encryption/pure-js.d.ts +3 -3
- package/dist/encryption/pure-js.js +70 -42
- package/dist/encryption/pure-js.js.map +1 -1
- package/dist/encryption/web.d.ts +3 -3
- package/dist/encryption/web.js +17 -15
- package/dist/encryption/web.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +7 -4
- package/src/encryption/browser.ts +3 -3
- package/src/encryption/common.ts +79 -16
- package/src/encryption/index.ts +12 -71
- package/src/encryption/js/aes.ts +191 -0
- package/src/encryption/js/gcm.ts +258 -0
- package/src/encryption/module.ts +94 -0
- package/src/encryption/node.ts +24 -15
- package/src/encryption/pure-js.ts +89 -46
- package/src/encryption/web.ts +24 -15
- package/src/index.ts +1 -1
- package/tests/encryption.js +126 -49
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/encryption/web.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
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.
|
|
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
|
-
"
|
|
20
|
-
"hash-wasm": "^4.11.0"
|
|
19
|
+
"sjcl": "^1.0.8"
|
|
21
20
|
},
|
|
22
21
|
"devDependencies": {
|
|
23
|
-
"
|
|
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 {
|
|
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:
|
|
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:
|
|
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
|
}
|
package/src/encryption/common.ts
CHANGED
|
@@ -1,22 +1,85 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*/
|
|
1
|
+
import { toUint8Array } from '../utils.js';
|
|
2
|
+
|
|
3
|
+
/** PBKDF2 迭代次数 */
|
|
4
4
|
export const PBKDF2_ITERATIONS = 100_000;
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
*/
|
|
8
|
-
export const
|
|
9
|
-
/**
|
|
10
|
-
export const
|
|
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
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
|
|
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
|
+
}
|
package/src/encryption/index.ts
CHANGED
|
@@ -1,84 +1,25 @@
|
|
|
1
1
|
import * as impl from '#encryption';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
-
|
|
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
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
+
}
|