Skip to content

Commit 9dd062b

Browse files
committed
common: add sha2 and hmac.
1 parent 9ecd0ea commit 9dd062b

File tree

6 files changed

+1482
-0
lines changed

6 files changed

+1482
-0
lines changed

packages/common/mod.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,6 @@ export {
4848
loadSubtleCrypto,
4949
xor,
5050
} from "./src/utils/misc.ts";
51+
52+
export { hmac } from "./src/hash/hmac.ts";
53+
export { sha256, sha384, sha512 } from "./src/hash/sha2.ts";

packages/common/src/hash/hmac.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// deno-lint-ignore-file no-explicit-any
2+
/**
3+
* This file is based on noble-hashes (https://github.com/paulmillr/noble-hashes).
4+
*
5+
* noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com)
6+
*
7+
* The original file is located at:
8+
* https://github.com/paulmillr/noble-hashes/blob/2e0c00e1aa134082ba1380bf3afb8b1641f60fed/src/hmac.ts
9+
*/
10+
/**
11+
* HMAC: RFC2104 message authentication code.
12+
* @module
13+
*/
14+
import {
15+
abytes,
16+
aexists,
17+
ahash,
18+
type CHash,
19+
clean,
20+
type Hash,
21+
} from "../utils/noble.ts";
22+
23+
export class _HMAC<T extends Hash<T>> implements Hash<_HMAC<T>> {
24+
oHash: T;
25+
iHash: T;
26+
blockLen: number;
27+
outputLen: number;
28+
private finished = false;
29+
private destroyed = false;
30+
31+
constructor(hash: CHash, key: Uint8Array) {
32+
ahash(hash);
33+
abytes(key, undefined, "key");
34+
this.iHash = hash.create() as T;
35+
if (typeof this.iHash.update !== "function") {
36+
throw new Error("Expected instance of class which extends utils.Hash");
37+
}
38+
this.blockLen = this.iHash.blockLen;
39+
this.outputLen = this.iHash.outputLen;
40+
const blockLen = this.blockLen;
41+
const pad = new Uint8Array(blockLen);
42+
// blockLen can be bigger than outputLen
43+
pad.set(key.length > blockLen ? hash.create().update(key).digest() : key);
44+
for (let i = 0; i < pad.length; i++) pad[i] ^= 0x36;
45+
this.iHash.update(pad);
46+
// By doing update (processing of first block) of outer hash here we can re-use it between multiple calls via clone
47+
this.oHash = hash.create() as T;
48+
// Undo internal XOR && apply outer XOR
49+
for (let i = 0; i < pad.length; i++) pad[i] ^= 0x36 ^ 0x5c;
50+
this.oHash.update(pad);
51+
clean(pad);
52+
}
53+
update(buf: Uint8Array): this {
54+
aexists(this);
55+
this.iHash.update(buf);
56+
return this;
57+
}
58+
digestInto(out: Uint8Array): void {
59+
aexists(this);
60+
abytes(out, this.outputLen, "output");
61+
this.finished = true;
62+
this.iHash.digestInto(out);
63+
this.oHash.update(out);
64+
this.oHash.digestInto(out);
65+
this.destroy();
66+
}
67+
digest(): Uint8Array {
68+
const out = new Uint8Array(this.oHash.outputLen);
69+
this.digestInto(out);
70+
return out;
71+
}
72+
_cloneInto(to?: _HMAC<T>): _HMAC<T> {
73+
// Create new instance without calling constructor since key already in state and we don't know it.
74+
to ||= Object.create(Object.getPrototypeOf(this), {});
75+
const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this;
76+
to = to as this;
77+
to.finished = finished;
78+
to.destroyed = destroyed;
79+
to.blockLen = blockLen;
80+
to.outputLen = outputLen;
81+
to.oHash = oHash._cloneInto(to.oHash);
82+
to.iHash = iHash._cloneInto(to.iHash);
83+
return to;
84+
}
85+
clone(): _HMAC<T> {
86+
return this._cloneInto();
87+
}
88+
destroy(): void {
89+
this.destroyed = true;
90+
this.oHash.destroy();
91+
this.iHash.destroy();
92+
}
93+
}
94+
95+
/**
96+
* HMAC: RFC2104 message authentication code.
97+
* @param hash - function that would be used e.g. sha256
98+
* @param key - message key
99+
* @param message - message data
100+
* @example
101+
* import { hmac } from '@noble/hashes/hmac';
102+
* import { sha256 } from '@noble/hashes/sha2';
103+
* const mac1 = hmac(sha256, 'key', 'message');
104+
*/
105+
export const hmac: {
106+
(hash: CHash, key: Uint8Array, message: Uint8Array): Uint8Array;
107+
create(hash: CHash, key: Uint8Array): _HMAC<any>;
108+
} = (hash: CHash, key: Uint8Array, message: Uint8Array): Uint8Array =>
109+
new _HMAC<any>(hash, key).update(message).digest();
110+
hmac.create = (hash: CHash, key: Uint8Array) => new _HMAC<any>(hash, key);

packages/common/src/hash/md.ts

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
// deno-lint-ignore-file no-explicit-any
2+
/**
3+
* This file is based on noble-hashes (https://github.com/paulmillr/noble-hashes).
4+
*
5+
* noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com)
6+
*
7+
* The original file is located at:
8+
* https://github.com/paulmillr/noble-hashes/blob/2e0c00e1aa134082ba1380bf3afb8b1641f60fed/src/_md.ts
9+
*/
10+
/**
11+
* Internal Merkle-Damgard hash utils.
12+
* @module
13+
*/
14+
import {
15+
abytes,
16+
aexists,
17+
aoutput,
18+
clean,
19+
createView,
20+
type Hash,
21+
} from "../utils/noble.ts";
22+
23+
/** Choice: a ? b : c */
24+
export function Chi(a: number, b: number, c: number): number {
25+
return (a & b) ^ (~a & c);
26+
}
27+
28+
/** Majority function, true if any two inputs is true. */
29+
export function Maj(a: number, b: number, c: number): number {
30+
return (a & b) ^ (a & c) ^ (b & c);
31+
}
32+
33+
/**
34+
* Merkle-Damgard hash construction base class.
35+
* Could be used to create MD5, RIPEMD, SHA1, SHA2.
36+
*/
37+
export abstract class HashMD<T extends HashMD<T>> implements Hash<T> {
38+
protected abstract process(buf: DataView, offset: number): void;
39+
protected abstract get(): number[];
40+
protected abstract set(...args: number[]): void;
41+
abstract destroy(): void;
42+
protected abstract roundClean(): void;
43+
44+
readonly blockLen: number;
45+
readonly outputLen: number;
46+
readonly padOffset: number;
47+
readonly isLE: boolean;
48+
49+
// For partial updates less than block size
50+
protected buffer: Uint8Array;
51+
protected view: DataView;
52+
protected finished = false;
53+
protected length = 0;
54+
protected pos = 0;
55+
protected destroyed = false;
56+
57+
constructor(
58+
blockLen: number,
59+
outputLen: number,
60+
padOffset: number,
61+
isLE: boolean,
62+
) {
63+
this.blockLen = blockLen;
64+
this.outputLen = outputLen;
65+
this.padOffset = padOffset;
66+
this.isLE = isLE;
67+
this.buffer = new Uint8Array(blockLen);
68+
this.view = createView(this.buffer);
69+
}
70+
update(data: Uint8Array): this {
71+
aexists(this);
72+
abytes(data);
73+
const { view, buffer, blockLen } = this;
74+
const len = data.length;
75+
for (let pos = 0; pos < len;) {
76+
const take = Math.min(blockLen - this.pos, len - pos);
77+
// Fast path: we have at least one block in input, cast it to view and process
78+
if (take === blockLen) {
79+
const dataView = createView(data);
80+
for (; blockLen <= len - pos; pos += blockLen) {
81+
this.process(dataView, pos);
82+
}
83+
continue;
84+
}
85+
buffer.set(data.subarray(pos, pos + take), this.pos);
86+
this.pos += take;
87+
pos += take;
88+
if (this.pos === blockLen) {
89+
this.process(view, 0);
90+
this.pos = 0;
91+
}
92+
}
93+
this.length += data.length;
94+
this.roundClean();
95+
return this;
96+
}
97+
digestInto(out: Uint8Array): void {
98+
aexists(this);
99+
aoutput(out, this);
100+
this.finished = true;
101+
// Padding
102+
// We can avoid allocation of buffer for padding completely if it
103+
// was previously not allocated here. But it won't change performance.
104+
const { buffer, view, blockLen, isLE } = this;
105+
let { pos } = this;
106+
// append the bit '1' to the message
107+
buffer[pos++] = 0b10000000;
108+
clean(this.buffer.subarray(pos));
109+
// we have less than padOffset left in buffer, so we cannot put length in
110+
// current block, need process it and pad again
111+
if (this.padOffset > blockLen - pos) {
112+
this.process(view, 0);
113+
pos = 0;
114+
}
115+
// Pad until full block byte with zeros
116+
for (let i = pos; i < blockLen; i++) buffer[i] = 0;
117+
// Note: sha512 requires length to be 128bit integer, but length in JS will overflow before that
118+
// You need to write around 2 exabytes (u64_max / 8 / (1024**6)) for this to happen.
119+
// So we just write lowest 64 bits of that value.
120+
view.setBigUint64(blockLen - 8, BigInt(this.length * 8), isLE);
121+
this.process(view, 0);
122+
const oview = createView(out);
123+
const len = this.outputLen;
124+
// NOTE: we do division by 4 later, which must be fused in single op with modulo by JIT
125+
if (len % 4) throw new Error("_sha2: outputLen must be aligned to 32bit");
126+
const outLen = len / 4;
127+
const state = this.get();
128+
if (outLen > state.length) {
129+
throw new Error("_sha2: outputLen bigger than state");
130+
}
131+
for (let i = 0; i < outLen; i++) oview.setUint32(4 * i, state[i], isLE);
132+
}
133+
digest(): Uint8Array {
134+
const { buffer, outputLen } = this;
135+
this.digestInto(buffer);
136+
const res = buffer.slice(0, outputLen);
137+
this.destroy();
138+
return res;
139+
}
140+
_cloneInto(to?: T): T {
141+
to ||= new (this.constructor as any)() as T;
142+
to.set(...this.get());
143+
const { blockLen, buffer, length, finished, destroyed, pos } = this;
144+
to.destroyed = destroyed;
145+
to.finished = finished;
146+
to.length = length;
147+
to.pos = pos;
148+
if (length % blockLen) to.buffer.set(buffer);
149+
return to as unknown as any;
150+
}
151+
clone(): T {
152+
return this._cloneInto();
153+
}
154+
}
155+
156+
/**
157+
* Initial SHA-2 state: fractional parts of square roots of first 16 primes 2..53.
158+
* Check out `test/misc/sha2-gen-iv.js` for recomputation guide.
159+
*/
160+
161+
/** Initial SHA256 state. Bits 0..32 of frac part of sqrt of primes 2..19 */
162+
export const SHA256_IV: Uint32Array = /* @__PURE__ */ Uint32Array.from([
163+
0x6a09e667,
164+
0xbb67ae85,
165+
0x3c6ef372,
166+
0xa54ff53a,
167+
0x510e527f,
168+
0x9b05688c,
169+
0x1f83d9ab,
170+
0x5be0cd19,
171+
]);
172+
173+
/** Initial SHA384 state. Bits 0..64 of frac part of sqrt of primes 23..53 */
174+
export const SHA384_IV: Uint32Array = /* @__PURE__ */ Uint32Array.from([
175+
0xcbbb9d5d,
176+
0xc1059ed8,
177+
0x629a292a,
178+
0x367cd507,
179+
0x9159015a,
180+
0x3070dd17,
181+
0x152fecd8,
182+
0xf70e5939,
183+
0x67332667,
184+
0xffc00b31,
185+
0x8eb44a87,
186+
0x68581511,
187+
0xdb0c2e0d,
188+
0x64f98fa7,
189+
0x47b5481d,
190+
0xbefa4fa4,
191+
]);
192+
193+
/** Initial SHA512 state. Bits 0..64 of frac part of sqrt of primes 2..19 */
194+
export const SHA512_IV: Uint32Array = /* @__PURE__ */ Uint32Array.from([
195+
0x6a09e667,
196+
0xf3bcc908,
197+
0xbb67ae85,
198+
0x84caa73b,
199+
0x3c6ef372,
200+
0xfe94f82b,
201+
0xa54ff53a,
202+
0x5f1d36f1,
203+
0x510e527f,
204+
0xade682d1,
205+
0x9b05688c,
206+
0x2b3e6c1f,
207+
0x1f83d9ab,
208+
0xfb41bd6b,
209+
0x5be0cd19,
210+
0x137e2179,
211+
]);

0 commit comments

Comments
 (0)