sm-crypto-v2 1.4.0 → 1.5.0
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/CHANGELOG.md +9 -0
- package/README.md +30 -26
- package/benchmark/index.js +14 -14
- package/benchmark/pnpm-lock.yaml +5 -1
- package/dist/index.d.ts +45 -61
- package/dist/index.js +412 -269
- package/dist/index.mjs +408 -265
- package/package.json +1 -1
- package/pnpm-lock.yaml +5 -1
- package/src/index.ts +2 -2
- package/src/sm2/ec.ts +8 -85
- package/src/sm2/hmac.ts +76 -0
- package/src/sm2/index.ts +31 -59
- package/src/sm2/kx.ts +15 -11
- package/src/sm2/rng.ts +71 -0
- package/src/sm2/sm3.ts +187 -120
- package/src/sm2/utils.ts +1 -23
- package/src/sm3/index.ts +7 -4
- package/src/sm3/utils.ts +117 -0
- package/src/sm4/index.ts +10 -12
package/src/sm2/sm3.ts
CHANGED
@@ -1,8 +1,21 @@
|
|
1
|
-
import
|
2
|
-
|
3
|
-
|
4
|
-
const
|
5
|
-
const
|
1
|
+
// import assert from './_assert.js';
|
2
|
+
import { Hash, createView, Input, toBytes, wrapConstructor } from '../sm3/utils.js';
|
3
|
+
|
4
|
+
const BoolA = (A: number, B: number, C: number) => ((A & B) | (A & C)) | (B & C)
|
5
|
+
const BoolB = (A: number, B: number, C: number) => ((A ^ B) ^ C)
|
6
|
+
const BoolC = (A: number, B: number, C: number) => (A & B) | ((~A) & C)
|
7
|
+
// Polyfill for Safari 14
|
8
|
+
function setBigUint64(view: DataView, byteOffset: number, value: bigint, isLE: boolean): void {
|
9
|
+
if (typeof view.setBigUint64 === 'function') return view.setBigUint64(byteOffset, value, isLE);
|
10
|
+
const _32n = BigInt(32);
|
11
|
+
const _u32_max = BigInt(0xffffffff);
|
12
|
+
const wh = Number((value >> _32n) & _u32_max);
|
13
|
+
const wl = Number(value & _u32_max);
|
14
|
+
const h = isLE ? 4 : 0;
|
15
|
+
const l = isLE ? 0 : 4;
|
16
|
+
view.setUint32(byteOffset + h, wh, isLE);
|
17
|
+
view.setUint32(byteOffset + l, wl, isLE);
|
18
|
+
}
|
6
19
|
|
7
20
|
/**
|
8
21
|
* 循环左移
|
@@ -35,83 +48,167 @@ function P1(X: number) {
|
|
35
48
|
return (X ^ rotl(X, 15)) ^ rotl(X, 23)
|
36
49
|
}
|
37
50
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
//
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
51
|
+
// from noble-hashes (https://github.com/paulmillr/noble-hashes#hmac)
|
52
|
+
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
53
|
+
|
54
|
+
// Base SHA2 class (RFC 6234)
|
55
|
+
export abstract class SHA2<T extends SHA2<T>> extends Hash<T> {
|
56
|
+
protected abstract process(buf: DataView, offset: number): void;
|
57
|
+
protected abstract get(): number[];
|
58
|
+
protected abstract set(...args: number[]): void;
|
59
|
+
abstract destroy(): void;
|
60
|
+
protected abstract roundClean(): void;
|
61
|
+
// For partial updates less than block size
|
62
|
+
protected buffer: Uint8Array;
|
63
|
+
protected view: DataView;
|
64
|
+
protected finished = false;
|
65
|
+
protected length = 0;
|
66
|
+
protected pos = 0;
|
67
|
+
protected destroyed = false;
|
68
|
+
|
69
|
+
constructor(
|
70
|
+
readonly blockLen: number,
|
71
|
+
public outputLen: number,
|
72
|
+
readonly padOffset: number,
|
73
|
+
readonly isLE: boolean
|
74
|
+
) {
|
75
|
+
super();
|
76
|
+
this.buffer = new Uint8Array(blockLen);
|
77
|
+
this.view = createView(this.buffer);
|
78
|
+
}
|
79
|
+
update(data: Input): this {
|
80
|
+
const { view, buffer, blockLen } = this;
|
81
|
+
data = toBytes(data);
|
82
|
+
const len = data.length;
|
83
|
+
for (let pos = 0; pos < len; ) {
|
84
|
+
const take = Math.min(blockLen - this.pos, len - pos);
|
85
|
+
// Fast path: we have at least one block in input, cast it to view and process
|
86
|
+
if (take === blockLen) {
|
87
|
+
const dataView = createView(data);
|
88
|
+
for (; blockLen <= len - pos; pos += blockLen) this.process(dataView, pos);
|
89
|
+
continue;
|
90
|
+
}
|
91
|
+
buffer.set(data.subarray(pos, pos + take), this.pos);
|
92
|
+
this.pos += take;
|
93
|
+
pos += take;
|
94
|
+
if (this.pos === blockLen) {
|
95
|
+
this.process(view, 0);
|
96
|
+
this.pos = 0;
|
97
|
+
}
|
62
98
|
}
|
99
|
+
this.length += data.length;
|
100
|
+
this.roundClean();
|
101
|
+
return this;
|
63
102
|
}
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
//
|
75
|
-
|
76
|
-
|
77
|
-
|
103
|
+
digestInto(out: Uint8Array) {
|
104
|
+
this.finished = true;
|
105
|
+
// Padding
|
106
|
+
// We can avoid allocation of buffer for padding completely if it
|
107
|
+
// was previously not allocated here. But it won't change performance.
|
108
|
+
const { buffer, view, blockLen, isLE } = this;
|
109
|
+
let { pos } = this;
|
110
|
+
// append the bit '1' to the message
|
111
|
+
buffer[pos++] = 0b10000000;
|
112
|
+
this.buffer.subarray(pos).fill(0);
|
113
|
+
// we have less than padOffset left in buffer, so we cannot put length in current block, need process it and pad again
|
114
|
+
if (this.padOffset > blockLen - pos) {
|
115
|
+
this.process(view, 0);
|
116
|
+
pos = 0;
|
78
117
|
}
|
118
|
+
// Pad until full block byte with zeros
|
119
|
+
for (let i = pos; i < blockLen; i++) buffer[i] = 0;
|
120
|
+
// Note: sha512 requires length to be 128bit integer, but length in JS will overflow before that
|
121
|
+
// You need to write around 2 exabytes (u64_max / 8 / (1024**6)) for this to happen.
|
122
|
+
// So we just write lowest 64 bits of that value.
|
123
|
+
setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE);
|
124
|
+
this.process(view, 0);
|
125
|
+
const oview = createView(out);
|
126
|
+
const len = this.outputLen;
|
127
|
+
// NOTE: we do division by 4 later, which should be fused in single op with modulo by JIT
|
128
|
+
if (len % 4) throw new Error('_sha2: outputLen should be aligned to 32bit');
|
129
|
+
const outLen = len / 4;
|
130
|
+
const state = this.get();
|
131
|
+
if (outLen > state.length) throw new Error('_sha2: outputLen bigger than state');
|
132
|
+
for (let i = 0; i < outLen; i++) oview.setUint32(4 * i, state[i], isLE);
|
133
|
+
}
|
134
|
+
digest() {
|
135
|
+
const { buffer, outputLen } = this;
|
136
|
+
this.digestInto(buffer);
|
137
|
+
const res = buffer.slice(0, outputLen);
|
138
|
+
this.destroy();
|
139
|
+
return res;
|
140
|
+
}
|
141
|
+
_cloneInto(to?: T): T {
|
142
|
+
to ||= new (this.constructor as any)() as T;
|
143
|
+
to.set(...this.get());
|
144
|
+
const { blockLen, buffer, length, finished, destroyed, pos } = this;
|
145
|
+
to.length = length;
|
146
|
+
to.pos = pos;
|
147
|
+
to.finished = finished;
|
148
|
+
to.destroyed = destroyed;
|
149
|
+
if (length % blockLen) to.buffer.set(buffer);
|
150
|
+
return to;
|
151
|
+
}
|
152
|
+
}
|
79
153
|
|
80
|
-
|
81
|
-
|
82
|
-
|
154
|
+
const IV = new Uint32Array([0x7380166f, 0x4914b2b9, 0x172442d7, 0xda8a0600, 0xa96f30bc, 0x163138aa, 0xe38dee4d, 0xb0fb0e4e])
|
155
|
+
const SM3_W = new Uint32Array(68)
|
156
|
+
const SM3_M = new Uint32Array(64)
|
157
|
+
|
158
|
+
const T1 = 0x79cc4519
|
159
|
+
const T2 = 0x7a879d8a
|
160
|
+
|
161
|
+
class SM3 extends SHA2<SM3> {
|
162
|
+
// We cannot use array here since array allows indexing by variable
|
163
|
+
// which means optimizer/compiler cannot use registers.
|
164
|
+
A = IV[0] | 0;
|
165
|
+
B = IV[1] | 0;
|
166
|
+
C = IV[2] | 0;
|
167
|
+
D = IV[3] | 0;
|
168
|
+
E = IV[4] | 0;
|
169
|
+
F = IV[5] | 0;
|
170
|
+
G = IV[6] | 0;
|
171
|
+
H = IV[7] | 0;
|
172
|
+
|
173
|
+
constructor() {
|
174
|
+
super(64, 32, 8, false);
|
175
|
+
}
|
176
|
+
protected get(): [number, number, number, number, number, number, number, number] {
|
177
|
+
const { A, B, C, D, E, F, G, H } = this;
|
178
|
+
return [A, B, C, D, E, F, G, H];
|
179
|
+
}
|
180
|
+
// prettier-ignore
|
181
|
+
protected set(
|
182
|
+
A: number, B: number, C: number, D: number, E: number, F: number, G: number, H: number
|
183
|
+
) {
|
184
|
+
this.A = A | 0;
|
185
|
+
this.B = B | 0;
|
186
|
+
this.C = C | 0;
|
187
|
+
this.D = D | 0;
|
188
|
+
this.E = E | 0;
|
189
|
+
this.F = F | 0;
|
190
|
+
this.G = G | 0;
|
191
|
+
this.H = H | 0;
|
192
|
+
}
|
193
|
+
protected process(view: DataView, offset: number): void {
|
194
|
+
// Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array
|
195
|
+
for (let i = 0; i < 16; i++, offset += 4) SM3_W[i] = view.getUint32(offset, false);
|
196
|
+
for (let i = 16; i < 68; i++) {
|
197
|
+
SM3_W[i] = (P1((SM3_W[i- 16] ^ SM3_W[i - 9]) ^ rotl(SM3_W[i - 3], 15)) ^ rotl(SM3_W[i - 13], 7)) ^ SM3_W[i - 6]
|
83
198
|
}
|
84
|
-
|
85
|
-
|
86
|
-
for (let j = 0; j < 64; j++) {
|
87
|
-
M[j] = W[j] ^ W[j + 4]
|
199
|
+
for (let i = 0; i < 64; i++) {
|
200
|
+
SM3_M[i] = SM3_W[i] ^ SM3_W[i + 4]
|
88
201
|
}
|
89
|
-
|
90
|
-
|
91
|
-
const T1 = 0x79cc4519
|
92
|
-
const T2 = 0x7a879d8a
|
93
|
-
// 字寄存器
|
94
|
-
let A = V[0]
|
95
|
-
let B = V[1]
|
96
|
-
let C = V[2]
|
97
|
-
let D = V[3]
|
98
|
-
let E = V[4]
|
99
|
-
let F = V[5]
|
100
|
-
let G = V[6]
|
101
|
-
let H = V[7]
|
102
|
-
// 中间变量
|
103
|
-
let SS1: number
|
104
|
-
let SS2: number
|
105
|
-
let TT1: number
|
106
|
-
let TT2: number
|
107
|
-
let T: number
|
202
|
+
// Compression function main loop, 64 rounds
|
203
|
+
let { A, B, C, D, E, F, G, H } = this;
|
108
204
|
for (let j = 0; j < 64; j++) {
|
109
|
-
|
110
|
-
|
111
|
-
|
205
|
+
let small = j >= 0 && j <= 15
|
206
|
+
let T = small ? T1 : T2
|
207
|
+
let SS1 = rotl(rotl(A, 12) + E + rotl(T, j), 7)
|
208
|
+
let SS2 = SS1 ^ rotl(A, 12)
|
112
209
|
|
113
|
-
TT1 = (
|
114
|
-
TT2 = (
|
210
|
+
let TT1 = ((small ? BoolB(A, B, C) : BoolA(A, B, C)) + D + SS2 + SM3_M[j]) | 0
|
211
|
+
let TT2 = ((small ? BoolB(E, F, G) : BoolC(E, F, G)) + H + SS1 + SM3_W[j]) | 0
|
115
212
|
|
116
213
|
D = C
|
117
214
|
C = rotl(B, 9)
|
@@ -122,53 +219,23 @@ export function sm3(array: Uint8Array) {
|
|
122
219
|
F = E
|
123
220
|
E = P0(TT2)
|
124
221
|
}
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
222
|
+
// Add the compressed chunk to the current hash value
|
223
|
+
A = (A ^ this.A) | 0;
|
224
|
+
B = (B ^ this.B) | 0;
|
225
|
+
C = (C ^ this.C) | 0;
|
226
|
+
D = (D ^ this.D) | 0;
|
227
|
+
E = (E ^ this.E) | 0;
|
228
|
+
F = (F ^ this.F) | 0;
|
229
|
+
G = (G ^ this.G) | 0;
|
230
|
+
H = (H ^ this.H) | 0;
|
231
|
+
this.set(A, B, C, D, E, F, G, H);
|
134
232
|
}
|
135
|
-
|
136
|
-
|
137
|
-
const result = new Uint8Array(V.length * 4)
|
138
|
-
for (let i = 0, len = V.length; i < len; i++) {
|
139
|
-
const word = V[i]
|
140
|
-
result[i * 4] = (word & 0xff000000) >>> 24
|
141
|
-
result[i * 4 + 1] = (word & 0xff0000) >>> 16
|
142
|
-
result[i * 4 + 2] = (word & 0xff00) >>> 8
|
143
|
-
result[i * 4 + 3] = word & 0xff
|
233
|
+
protected roundClean() {
|
234
|
+
SM3_W.fill(0);
|
144
235
|
}
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
/**
|
150
|
-
* hmac 实现
|
151
|
-
*/
|
152
|
-
const blockLen = 64
|
153
|
-
const iPad = new Uint8Array(blockLen)
|
154
|
-
const oPad = new Uint8Array(blockLen)
|
155
|
-
for (let i = 0; i < blockLen; i++) {
|
156
|
-
iPad[i] = 0x36
|
157
|
-
oPad[i] = 0x5c
|
158
|
-
}
|
159
|
-
|
160
|
-
export function hmac(input: Uint8Array, key: Uint8Array) {
|
161
|
-
// 密钥填充
|
162
|
-
if (key.length > blockLen) key = sm3(key)
|
163
|
-
while (key.length < blockLen) {
|
164
|
-
const padKey = new Uint8Array(blockLen)
|
165
|
-
padKey.set(key)
|
166
|
-
key = padKey
|
236
|
+
destroy() {
|
237
|
+
this.set(0, 0, 0, 0, 0, 0, 0, 0);
|
238
|
+
this.buffer.fill(0);
|
167
239
|
}
|
168
|
-
|
169
|
-
const iPadKey = xor(key, iPad)
|
170
|
-
const oPadKey = xor(key, oPad)
|
171
|
-
|
172
|
-
const hash = sm3(concatArray(iPadKey, input))
|
173
|
-
return sm3(concatArray(oPadKey, hash))
|
174
240
|
}
|
241
|
+
export const sm3 = wrapConstructor(() => new SM3());
|
package/src/sm2/utils.ts
CHANGED
@@ -146,9 +146,7 @@ export function verifyPublicKey(publicKey: string) {
|
|
146
146
|
const x = point.x
|
147
147
|
const y = point.y
|
148
148
|
// 验证 y^2 是否等于 x^3 + ax + b
|
149
|
-
|
150
|
-
return sm2Fp.sqr(y) === sm2Fp.add(sm2Fp.add(sm2Fp.mul(x, sm2Fp.sqr(x)), sm2Fp.mul(x, sm2Curve.CURVE.a)), sm2Curve.CURVE.b)
|
151
|
-
// return y ** 2n === (x ** 3n + sm2Curve.CURVE.a * x + sm2Curve.CURVE.b)
|
149
|
+
return sm2Fp.sqr(y) === sm2Fp.add(sm2Fp.addN(sm2Fp.mulN(x, sm2Fp.sqrN(x)), sm2Fp.mulN(x, sm2Curve.CURVE.a)), sm2Curve.CURVE.b)
|
152
150
|
}
|
153
151
|
|
154
152
|
/**
|
@@ -163,23 +161,3 @@ export function comparePublicKeyHex(publicKey1: string, publicKey2: string) {
|
|
163
161
|
|
164
162
|
return point1.equals(point2)
|
165
163
|
}
|
166
|
-
|
167
|
-
|
168
|
-
export function concatArray(...arrays: Uint8Array[]) {
|
169
|
-
// sum of individual array lengths
|
170
|
-
let totalLength = arrays.reduce((acc, value) => acc + value.length, 0);
|
171
|
-
|
172
|
-
if (!arrays.length) return new Uint8Array();
|
173
|
-
|
174
|
-
let result = new Uint8Array(totalLength);
|
175
|
-
|
176
|
-
// for each array - copy it over result
|
177
|
-
// next array is copied right after the previous one
|
178
|
-
let length = 0;
|
179
|
-
for (let array of arrays) {
|
180
|
-
result.set(array, length);
|
181
|
-
length += array.length;
|
182
|
-
}
|
183
|
-
|
184
|
-
return result;
|
185
|
-
}
|
package/src/sm3/index.ts
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
import { hmac
|
1
|
+
import { hmac } from '@/sm2/hmac'
|
2
|
+
import { sm3 as sm2sm3 } from '../sm2/sm3'
|
2
3
|
import { arrayToHex, hexToArray, leftPad } from '../sm2/utils'
|
4
|
+
import { bytesToHex } from './utils'
|
3
5
|
|
4
6
|
/**
|
5
7
|
* 补全16进制字符串
|
@@ -62,8 +64,9 @@ export function sm3(input: string | Uint8Array, options?: {
|
|
62
64
|
if (!key) throw new Error('invalid key')
|
63
65
|
|
64
66
|
key = typeof key === 'string' ? hexToArray(key) : key
|
65
|
-
return
|
67
|
+
return bytesToHex(hmac(sm2sm3, key, input));
|
66
68
|
}
|
67
|
-
|
68
|
-
return arrayToHex(Array.from(sm2sm3(input)))
|
69
|
+
return bytesToHex(sm2sm3(input))
|
69
70
|
}
|
71
|
+
|
72
|
+
export default sm3
|
package/src/sm3/utils.ts
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
// prettier-ignore
|
2
|
+
export type TypedArray = Int8Array | Uint8ClampedArray | Uint8Array |
|
3
|
+
Uint16Array | Int16Array | Uint32Array | Int32Array;
|
4
|
+
|
5
|
+
const u8a = (a: any): a is Uint8Array => a instanceof Uint8Array;
|
6
|
+
// Cast array to different type
|
7
|
+
export const u8 = (arr: TypedArray) => new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
|
8
|
+
export const u32 = (arr: TypedArray) =>
|
9
|
+
new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
10
|
+
|
11
|
+
// Cast array to view
|
12
|
+
export const createView = (arr: TypedArray) =>
|
13
|
+
new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
14
|
+
|
15
|
+
// The rotate right (circular right shift) operation for uint32
|
16
|
+
export const rotr = (word: number, shift: number) => (word << (32 - shift)) | (word >>> shift);
|
17
|
+
|
18
|
+
// big-endian hardware is rare. Just in case someone still decides to run hashes:
|
19
|
+
// early-throw an error because we don't support BE yet.
|
20
|
+
export const isLE = new Uint8Array(new Uint32Array([0x11223344]).buffer)[0] === 0x44;
|
21
|
+
if (!isLE) throw new Error('Non little-endian hardware is not supported');
|
22
|
+
|
23
|
+
const hexes = Array.from({ length: 256 }, (v, i) => i.toString(16).padStart(2, '0'));
|
24
|
+
/**
|
25
|
+
* @example bytesToHex(Uint8Array.from([0xca, 0xfe, 0x01, 0x23])) // 'cafe0123'
|
26
|
+
*/
|
27
|
+
export function bytesToHex(bytes: Uint8Array): string {
|
28
|
+
if (!u8a(bytes)) throw new Error('Uint8Array expected');
|
29
|
+
// pre-caching improves the speed 6x
|
30
|
+
let hex = '';
|
31
|
+
for (let i = 0; i < bytes.length; i++) {
|
32
|
+
hex += hexes[bytes[i]];
|
33
|
+
}
|
34
|
+
return hex;
|
35
|
+
}
|
36
|
+
|
37
|
+
/**
|
38
|
+
* @example hexToBytes('cafe0123') // Uint8Array.from([0xca, 0xfe, 0x01, 0x23])
|
39
|
+
*/
|
40
|
+
export function hexToBytes(hex: string): Uint8Array {
|
41
|
+
if (typeof hex !== 'string') throw new Error('hex string expected, got ' + typeof hex);
|
42
|
+
const len = hex.length;
|
43
|
+
if (len % 2) throw new Error('padded hex string expected, got unpadded hex of length ' + len);
|
44
|
+
const array = new Uint8Array(len / 2);
|
45
|
+
for (let i = 0; i < array.length; i++) {
|
46
|
+
const j = i * 2;
|
47
|
+
const hexByte = hex.slice(j, j + 2);
|
48
|
+
const byte = Number.parseInt(hexByte, 16);
|
49
|
+
if (Number.isNaN(byte) || byte < 0) throw new Error('Invalid byte sequence');
|
50
|
+
array[i] = byte;
|
51
|
+
}
|
52
|
+
return array;
|
53
|
+
}
|
54
|
+
|
55
|
+
// Global symbols in both browsers and Node.js since v11
|
56
|
+
// See https://github.com/microsoft/TypeScript/issues/31535
|
57
|
+
declare const TextEncoder: any;
|
58
|
+
|
59
|
+
/**
|
60
|
+
* @example utf8ToBytes('abc') // new Uint8Array([97, 98, 99])
|
61
|
+
*/
|
62
|
+
export function utf8ToBytes(str: string): Uint8Array {
|
63
|
+
if (typeof str !== 'string') throw new Error(`utf8ToBytes expected string, got ${typeof str}`);
|
64
|
+
return new Uint8Array(new TextEncoder().encode(str)); // https://bugzil.la/1681809
|
65
|
+
}
|
66
|
+
|
67
|
+
export type Input = Uint8Array | string;
|
68
|
+
/**
|
69
|
+
* Normalizes (non-hex) string or Uint8Array to Uint8Array.
|
70
|
+
* Warning: when Uint8Array is passed, it would NOT get copied.
|
71
|
+
* Keep in mind for future mutable operations.
|
72
|
+
*/
|
73
|
+
export function toBytes(data: Input): Uint8Array {
|
74
|
+
if (typeof data === 'string') data = utf8ToBytes(data);
|
75
|
+
if (!u8a(data)) throw new Error(`expected Uint8Array, got ${typeof data}`);
|
76
|
+
return data;
|
77
|
+
}
|
78
|
+
|
79
|
+
|
80
|
+
// For runtime check if class implements interface
|
81
|
+
export abstract class Hash<T extends Hash<T>> {
|
82
|
+
abstract blockLen: number; // Bytes per block
|
83
|
+
abstract outputLen: number; // Bytes in output
|
84
|
+
abstract update(buf: Input): this;
|
85
|
+
// Writes digest into buf
|
86
|
+
abstract digestInto(buf: Uint8Array): void;
|
87
|
+
abstract digest(): Uint8Array;
|
88
|
+
/**
|
89
|
+
* Resets internal state. Makes Hash instance unusable.
|
90
|
+
* Reset is impossible for keyed hashes if key is consumed into state. If digest is not consumed
|
91
|
+
* by user, they will need to manually call `destroy()` when zeroing is necessary.
|
92
|
+
*/
|
93
|
+
abstract destroy(): void;
|
94
|
+
/**
|
95
|
+
* Clones hash instance. Unsafe: doesn't check whether `to` is valid. Can be used as `clone()`
|
96
|
+
* when no options are passed.
|
97
|
+
* Reasons to use `_cloneInto` instead of clone: 1) performance 2) reuse instance => all internal
|
98
|
+
* buffers are overwritten => causes buffer overwrite which is used for digest in some cases.
|
99
|
+
* There are no guarantees for clean-up because it's impossible in JS.
|
100
|
+
*/
|
101
|
+
abstract _cloneInto(to?: T): T;
|
102
|
+
// Safe version that clones internal state
|
103
|
+
clone(): T {
|
104
|
+
return this._cloneInto();
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
export type CHash = ReturnType<typeof wrapConstructor>;
|
109
|
+
|
110
|
+
export function wrapConstructor<T extends Hash<T>>(hashCons: () => Hash<T>) {
|
111
|
+
const hashC = (msg: Input): Uint8Array => hashCons().update(toBytes(msg)).digest();
|
112
|
+
const tmp = hashCons();
|
113
|
+
hashC.outputLen = tmp.outputLen;
|
114
|
+
hashC.blockLen = tmp.blockLen;
|
115
|
+
hashC.create = () => hashCons();
|
116
|
+
return hashC;
|
117
|
+
}
|
package/src/sm4/index.ts
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import { bytesToHex } from '@/sm3/utils'
|
1
2
|
import { rotl } from '../sm2/sm3'
|
2
3
|
import { arrayToHex, arrayToUtf8, hexToArray } from '../sm2/utils'
|
3
4
|
import { utf8ToArray } from '../sm3'
|
@@ -64,11 +65,10 @@ function l2(b: number) {
|
|
64
65
|
/**
|
65
66
|
* 以一组 128 比特进行加密/解密操作
|
66
67
|
*/
|
68
|
+
const x = new Uint32Array(4)
|
69
|
+
const tmp = new Uint32Array(4)
|
67
70
|
function sms4Crypt(input: Uint8Array, output: Uint8Array, roundKey: Uint32Array) {
|
68
|
-
const x = new Uint32Array(4)
|
69
|
-
|
70
71
|
// 字节数组转成字数组(此处 1 字 = 32 比特)
|
71
|
-
const tmp = new Uint32Array(4)
|
72
72
|
for (let i = 0; i < 4; i++) {
|
73
73
|
tmp[0] = input[4 * i] & 0xff
|
74
74
|
tmp[1] = input[4 * i + 1] & 0xff
|
@@ -105,10 +105,8 @@ function sms4Crypt(input: Uint8Array, output: Uint8Array, roundKey: Uint32Array)
|
|
105
105
|
* 密钥扩展算法
|
106
106
|
*/
|
107
107
|
function sms4KeyExt(key: Uint8Array, roundKey: Uint32Array, cryptFlag: 0 | 1) {
|
108
|
-
const x = new Uint32Array(4)
|
109
108
|
|
110
109
|
// 字节数组转成字数组(此处 1 字 = 32 比特)
|
111
|
-
const tmp = new Uint32Array(4)
|
112
110
|
for (let i = 0; i < 4; i++) {
|
113
111
|
tmp[0] = key[0 + 4 * i] & 0xff
|
114
112
|
tmp[1] = key[1 + 4 * i] & 0xff
|
@@ -154,6 +152,7 @@ export interface SM4Options {
|
|
154
152
|
output?: 'string' | 'array'
|
155
153
|
}
|
156
154
|
|
155
|
+
const blockOutput = new Uint8Array(16)
|
157
156
|
export function sm4(inArray: Uint8Array | string, key: Uint8Array | string, cryptFlag: 0 | 1, options: SM4Options = {}) {
|
158
157
|
let {
|
159
158
|
padding = 'pkcs#7',
|
@@ -208,8 +207,7 @@ export function sm4(inArray: Uint8Array | string, key: Uint8Array | string, cryp
|
|
208
207
|
let restLen = inArray.length
|
209
208
|
let point = 0
|
210
209
|
while (restLen >= BLOCK) {
|
211
|
-
const input = inArray.
|
212
|
-
const output = new Uint8Array(16)
|
210
|
+
const input = inArray.subarray(point, point + 16)
|
213
211
|
|
214
212
|
if (mode === 'cbc') {
|
215
213
|
for (let i = 0; i < BLOCK; i++) {
|
@@ -220,24 +218,24 @@ export function sm4(inArray: Uint8Array | string, key: Uint8Array | string, cryp
|
|
220
218
|
}
|
221
219
|
}
|
222
220
|
|
223
|
-
sms4Crypt(input,
|
221
|
+
sms4Crypt(input, blockOutput, roundKey)
|
224
222
|
|
225
223
|
|
226
224
|
for (let i = 0; i < BLOCK; i++) {
|
227
225
|
if (mode === 'cbc') {
|
228
226
|
if (cryptFlag === DECRYPT) {
|
229
227
|
// 解密过程在组解密后进行异或
|
230
|
-
|
228
|
+
blockOutput[i] ^= lastVector[i]
|
231
229
|
}
|
232
230
|
}
|
233
231
|
|
234
|
-
outArray[point + i] =
|
232
|
+
outArray[point + i] = blockOutput[i]
|
235
233
|
}
|
236
234
|
|
237
235
|
if (mode === 'cbc') {
|
238
236
|
if (cryptFlag !== DECRYPT) {
|
239
237
|
// 使用上一次输出作为加密向量
|
240
|
-
lastVector =
|
238
|
+
lastVector = blockOutput
|
241
239
|
} else {
|
242
240
|
// 使用上一次输入作为解密向量
|
243
241
|
lastVector = input
|
@@ -262,7 +260,7 @@ export function sm4(inArray: Uint8Array | string, key: Uint8Array | string, cryp
|
|
262
260
|
if (output !== 'array') {
|
263
261
|
if (cryptFlag !== DECRYPT) {
|
264
262
|
// 加密,输出转 16 进制串
|
265
|
-
return
|
263
|
+
return bytesToHex(outArray)
|
266
264
|
} else {
|
267
265
|
// 解密,输出转 utf8 串
|
268
266
|
return arrayToUtf8(outArray)
|