Skip to content

Commit 01cb779

Browse files
committed
add sm3 hash
1 parent 2905748 commit 01cb779

7 files changed

Lines changed: 290 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
- Add toBase64
33
- Add fromArrayBuffer
44
- Add support of SHA512/t hash
5+
- Optimize hash padding
6+
- Add SM3 hash
57
* 0.8.3
68
- Fix Whirlpool bug
79
- Add compiled es5 version for nodejs

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
* [SHA512/t (SHA512/256 SHA512/224)](https://nf404.github.io/crypto-api/class/src/hasher/sha512.mjs~Sha512.html)
3939
* [Snefru v2.0 (2 rounds 128, 4 rounds 256), Snefru v2.5 (8 rounds)](https://nf404.github.io/crypto-api/class/src/hasher/snefru.mjs~Snefru.html)
4040
* [WHIRLPOOL (WHIRLPOOL-0, WHIRLPOOL-T)](https://nf404.github.io/crypto-api/class/src/hasher/whirlpool.mjs~Whirlpool.html)
41+
* [SM3](https://nf404.github.io/crypto-api/class/src/hasher/sm3.mjs~Sm3.html)
4142

4243
### MAC
4344
* [HMAC](https://nf404.github.io/crypto-api/class/src/mac/hmac.mjs~Hmac.html)
@@ -50,7 +51,7 @@
5051

5152
## Examples
5253

53-
### ES6 (recomended)
54+
### ES6 (recommended)
5455

5556
Calculates SHA256 hash from UTF string "message"
5657
```javascript

example/benchmark.mjs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Sha256 from "../src/hasher/sha256";
1111
import Sha512 from "../src/hasher/sha512";
1212
import Snefru from "../src/hasher/snefru";
1313
import Whirlpool from "../src/hasher/whirlpool";
14+
import Sm3 from "../src/hasher/sm3";
1415
import {fromUtf} from "../src/encoder/utf";
1516
import {toHex} from "../src/encoder/hex";
1617

@@ -90,6 +91,11 @@ suite('Hash from simple string with HEX result', function (suite) {
9091
suite.whirlpool.update(fromUtf('xxx'));
9192
toHex(suite.whirlpool.finalize());
9293
});
94+
bench('sm3', function () {
95+
suite.sm3 = new Sm3();
96+
suite.sm3.update(fromUtf('xxx'));
97+
toHex(suite.sm3.finalize());
98+
});
9399
});
94100
suite('Update', function (suite) {
95101
setup(function () {
@@ -108,6 +114,7 @@ suite('Update', function (suite) {
108114
suite.sha512 = new Sha512();
109115
suite.snefru = new Snefru();
110116
suite.whirlpool = new Whirlpool();
117+
suite.sm3 = new Sm3();
111118
});
112119
bench('md2', function () {
113120
suite.md2.update(fromUtf('xxx'));
@@ -154,4 +161,7 @@ suite('Update', function (suite) {
154161
bench('whirlpool', function () {
155162
suite.whirlpool.update(fromUtf('xxx'));
156163
});
164+
bench('sm3', function () {
165+
suite.sm3.update(fromUtf('xxx'));
166+
});
157167
});

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@
104104
"whirlpool",
105105
"whirlpool-0",
106106
"whirlpool-t",
107+
"sm3",
107108
"snefru",
108109
"hmac"
109110
]

src/hasher/sm3.mjs

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
'use strict';
2+
3+
import Hasher32be from "./hasher32be";
4+
import {rotateLeft} from "../tools/tools";
5+
6+
7+
/**
8+
* Calculates [SM3](https://tools.ietf.org/id/draft-oscca-cfrg-sm3-02.html) hash
9+
*
10+
* @example <caption>Calculates SM3 hash from string "message" - ES6 style</caption>
11+
* import Sm3 from "crypto-api/src/hasher/sm3";
12+
* import {toHex} from "crypto-api/src/encoder/hex";
13+
*
14+
* let hasher = new Sm3();
15+
* hasher.update('message');
16+
* console.log(toHex(hasher.finalize()));
17+
*
18+
* @example <caption>Calculates SM3 hash from UTF string "message" - ES6 style</caption>
19+
* import Sm3 from "crypto-api/src/hasher/sm3";
20+
* import {toHex} from "crypto-api/src/encoder/hex";
21+
* import {fromUtf} from "crypto-api/src/encoder/utf";
22+
*
23+
* let hasher = new Sm3();
24+
* hasher.update(fromUtf('message'));
25+
* console.log(toHex(hasher.finalize()));
26+
*
27+
* @example <caption>Calculates SM3 hash from string "message" - ES5 style</caption>
28+
* <script src="https://nf404.github.io/crypto-api/crypto-api.min.js"></script>
29+
* <script>
30+
* var hasher = CryptoApi.getHasher('sm3');
31+
* hasher.update('message');
32+
* console.log(CryptoApi.encoder.toHex(hasher.finalize()));
33+
* </script>
34+
*
35+
* @example <caption>Calculates SM3 hash from UTF string "message" - ES5 style</caption>
36+
* <script src="https://nf404.github.io/crypto-api/crypto-api.min.js"></script>
37+
* <script>
38+
* console.log(CryptoApi.hash('sm3', 'message'));
39+
* </script>
40+
*/
41+
class Sm3 extends Hasher32be {
42+
/**
43+
* @param {Object} [options]
44+
* @param {number} [options.rounds=64] - Number of rounds (Must be greater than 16)
45+
* @param {number} [options.length=256] - Length of hash result
46+
*/
47+
constructor(options) {
48+
options = options || {};
49+
options.length = options.length || 256;
50+
options.rounds = options.rounds || 64;
51+
super(options);
52+
53+
/**
54+
* Working variable (only for speed optimization)
55+
* @private
56+
* @ignore
57+
* @type {number[]}
58+
*/
59+
this.W = new Array(132);
60+
}
61+
62+
/**
63+
* Reset hasher to initial state
64+
*/
65+
reset() {
66+
super.reset();
67+
this.state.hash = [
68+
0x7380166f | 0, 0x4914b2b9 | 0, 0x172442d7 | 0, 0xda8a0600 | 0,
69+
0xa96f30bc | 0, 0x163138aa | 0, 0xe38dee4d | 0, 0xb0fb0e4e | 0
70+
];
71+
}
72+
73+
/**
74+
* @protected
75+
* @ignore
76+
* @param {number} x
77+
* @returns {number}
78+
*/
79+
static p0 (x) {
80+
return x ^ rotateLeft(x, 9) ^ rotateLeft(x, 17)
81+
}
82+
83+
/**
84+
* @protected
85+
* @ignore
86+
* @param {number} x
87+
* @returns {number}
88+
*/
89+
static p1 (x) {
90+
return x ^ rotateLeft(x, 15) ^ rotateLeft(x, 23)
91+
}
92+
93+
/**
94+
* @protected
95+
* @ignore
96+
* @param {number} i
97+
* @returns {number}
98+
*/
99+
static tj (i) {
100+
return i < 16 ? 0x79cc4519 : 0x7a879d8a;
101+
}
102+
103+
/**
104+
* @protected
105+
* @ignore
106+
* @param {number} i
107+
* @param {number} a
108+
* @param {number} b
109+
* @param {number} c
110+
* @returns {number}
111+
*/
112+
static ffj(i, a, b, c) {
113+
return i < 16 ? a ^ b ^ c : (a & b) | (a & c) | (b & c)
114+
}
115+
116+
/**
117+
* @protected
118+
* @ignore
119+
* @param {number} i
120+
* @param {number} e
121+
* @param {number} f
122+
* @param {number} g
123+
* @returns {number}
124+
*/
125+
static ggj(i, e, f, g) {
126+
return i < 16 ? e ^ f ^ g : (e & f) | (~e & g)
127+
}
128+
129+
/**
130+
* Process ready blocks
131+
*
132+
* @protected
133+
* @ignore
134+
* @param {number[]} block - Block
135+
*/
136+
processBlock(block) {
137+
// Working variables
138+
let a = this.state.hash[0] | 0;
139+
let b = this.state.hash[1] | 0;
140+
let c = this.state.hash[2] | 0;
141+
let d = this.state.hash[3] | 0;
142+
let e = this.state.hash[4] | 0;
143+
let f = this.state.hash[5] | 0;
144+
let g = this.state.hash[6] | 0;
145+
let h = this.state.hash[7] | 0;
146+
// Expand message
147+
for (let i = 0; i < 132; i++) {
148+
if (i < 16) {
149+
this.W[i] = block[i] | 0;
150+
} else if (i < 68) {
151+
this.W[i] = Sm3.p1(this.W[i - 16] ^ this.W[i - 9] ^ rotateLeft(this.W[i - 3], 15)) ^
152+
rotateLeft(this.W[i - 13], 7) ^ this.W[i - 6]
153+
} else {
154+
this.W[i] = this.W[i - 68] ^ this.W[i - 64]
155+
}
156+
}
157+
// Calculate hash
158+
for (let i = 0; i < this.options.rounds; i++) {
159+
let ss1 = rotateLeft((rotateLeft(a, 12) + e + rotateLeft(Sm3.tj(i), i % 32)) | 0,7)
160+
let ss2 = ss1 ^ rotateLeft(a, 12)
161+
let tt1 = (Sm3.ffj(i, a, b, c) + d + ss2 + this.W[i + 68]) | 0
162+
let tt2 = (Sm3.ggj(i, e, f, g) + h + ss1 + this.W[i]) | 0
163+
164+
d = c
165+
c = rotateLeft(b, 9)
166+
b = a
167+
a = tt1
168+
h = g
169+
g = rotateLeft(f, 19)
170+
f = e
171+
e = Sm3.p0(tt2)
172+
}
173+
174+
this.state.hash[0] = this.state.hash[0] ^ a;
175+
this.state.hash[1] = this.state.hash[1] ^ b;
176+
this.state.hash[2] = this.state.hash[2] ^ c;
177+
this.state.hash[3] = this.state.hash[3] ^ d;
178+
this.state.hash[4] = this.state.hash[4] ^ e;
179+
this.state.hash[5] = this.state.hash[5] ^ f;
180+
this.state.hash[6] = this.state.hash[6] ^ g;
181+
this.state.hash[7] = this.state.hash[7] ^ h;
182+
}
183+
184+
/**
185+
* Finalize hash and return result
186+
*
187+
* @returns {string}
188+
*/
189+
finalize() {
190+
this.addPaddingISO7816(
191+
this.state.message.length < 56 ?
192+
56 - this.state.message.length | 0 :
193+
120 - this.state.message.length | 0);
194+
this.addLengthBits();
195+
this.process();
196+
return this.getStateHash((this.options.length / 32) | 0);
197+
}
198+
}
199+
200+
export default Sm3;

test/hasher/sm3Test.mjs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*global describe, it */
2+
'use strict';
3+
import Sm3 from "../../src/hasher/sm3";
4+
import TestHasher from "../TestHasher";
5+
6+
// The SM3 test suite
7+
// https://tools.ietf.org/id/draft-oscca-cfrg-sm3-02.html#rfc.appendix.A.1
8+
// https://github.com/adamws/oscca-sm3/blob/master/example/example.c
9+
// https://dev.gnupg.org/source/libgcrypt/browse/master/tests/basic.c;ab57613f10ad57d2fec648017c18d7abb189863b$10642
10+
11+
class Sm3Test extends TestHasher {
12+
test() {
13+
let t = this;
14+
/** @test {Sm3} */
15+
describe('Hash sm3 tests', function () {
16+
it("sm3('')", function () {
17+
t.testHash({
18+
message: '',
19+
hash: '1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b'
20+
});
21+
});
22+
23+
it("sm3('a')", function () {
24+
t.testHash({
25+
message: 'a',
26+
hash: '623476ac18f65a2909e43c7fec61b49c7e764a91a18ccb82f1917a29c86c5e88'
27+
});
28+
});
29+
30+
it("sm3('abc')", function () {
31+
t.testHash({
32+
message: 'abc',
33+
hash: '66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0'
34+
});
35+
});
36+
37+
it("sm3('abcd' x 16)", function () {
38+
t.testHash({
39+
message: new Array(17).join('abcd'),
40+
hash: 'debe9ff92275b8a138604889c18e5a4d6fdb70e5387e5765293dcba39c0c5732'
41+
});
42+
});
43+
44+
it("sm3('a..z')", function () {
45+
t.testHash({
46+
message: 'abcdefghijklmnopqrstuvwxyz',
47+
hash: 'b80fe97a4da24afc277564f66a359ef440462ad28dcc6d63adb24d5c20a61595'
48+
});
49+
});
50+
51+
it("sm3('abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq')", function () {
52+
t.testHash({
53+
message: 'abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq',
54+
hash: '639b6cc5e64d9e37a390b192df4fa1ea0720ab747ff692b9f38c4e66ad7b8c05'
55+
});
56+
});
57+
58+
/**
59+
* @test {Sm3#setState}
60+
* @test {Sm3#getState}
61+
*/
62+
it('hash setState() getState()', function () {
63+
t.testSetGetState();
64+
});
65+
});
66+
}
67+
68+
getInstance(options) {
69+
return new Sm3();
70+
}
71+
}
72+
73+
export default Sm3Test;

test/test.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import Snefru256_8Test from "./hasher/snefru256_8Test";
2323
import Whirlpool0Test from "./hasher/whirlpool-0Test";
2424
import WhirlpoolTTest from "./hasher/whirlpool-tTest";
2525
import WhirlpoolTest from "./hasher/whirlpoolTest";
26+
import Sm3Test from './hasher/sm3Test'
2627

2728
import HmacHas160Test from "./mac/hmac-has160Test";
2829
import HmacMd5Test from "./mac/hmac-md5Test";
@@ -36,7 +37,6 @@ import UtfTest from "./encoder/UtfTest";
3637
import Base64Test from "./encoder/Base64Test";
3738
import ArrayBufferTest from "./encoder/ArrayBufferTest";
3839

39-
4040
// Hash tests
4141
(new Has160Test()).test();
4242
(new Md2Test()).test();
@@ -61,6 +61,7 @@ import ArrayBufferTest from "./encoder/ArrayBufferTest";
6161
(new Whirlpool0Test()).test();
6262
(new WhirlpoolTTest()).test();
6363
(new WhirlpoolTest()).test();
64+
(new Sm3Test()).test();
6465

6566
// HMAC tests
6667
(new HmacHas160Test()).test();

0 commit comments

Comments
 (0)