Skip to content

Commit 0a2f20b

Browse files
lukasjhancre8
andauthored
feat: add sha384, sha512 support (#282)
Signed-off-by: Lukas.J Han <[email protected]> Signed-off-by: Lukas J Han <[email protected]> Signed-off-by: Lukas.J.Han <[email protected]> Signed-off-by: Mirko Mollik <[email protected]> Co-authored-by: Mirko Mollik <[email protected]>
1 parent 1da5eba commit 0a2f20b

File tree

6 files changed

+161
-62
lines changed

6 files changed

+161
-62
lines changed

packages/hash/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"@sd-jwt/crypto-nodejs": "workspace:*"
4242
},
4343
"dependencies": {
44-
"@noble/hashes": "1.0.0",
44+
"@noble/hashes": "^1.8.0",
4545
"@sd-jwt/utils": "workspace:*"
4646
},
4747
"publishConfig": {

packages/hash/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export * from './sha256';
1+
export * from './sha';

packages/hash/src/sha.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import {
2+
sha256 as nobleSha256,
3+
sha384 as nobleSha384,
4+
sha512 as nobleSha512,
5+
} from '@noble/hashes/sha2.js';
6+
import { SDJWTException } from '@sd-jwt/utils';
7+
8+
export const sha256 = (text: string | ArrayBuffer): Uint8Array => {
9+
const uint8Array =
10+
typeof text === 'string' ? toUTF8Array(text) : new Uint8Array(text);
11+
const hashBytes = nobleSha256(uint8Array);
12+
return hashBytes;
13+
};
14+
15+
export const sha384 = (text: string | ArrayBuffer): Uint8Array => {
16+
const uint8Array =
17+
typeof text === 'string' ? toUTF8Array(text) : new Uint8Array(text);
18+
const hashBytes = nobleSha384(uint8Array);
19+
return hashBytes;
20+
};
21+
22+
export const sha512 = (text: string | ArrayBuffer): Uint8Array => {
23+
const uint8Array =
24+
typeof text === 'string' ? toUTF8Array(text) : new Uint8Array(text);
25+
const hashBytes = nobleSha512(uint8Array);
26+
return hashBytes;
27+
};
28+
29+
type HasherAlgorithm = 'sha256' | 'sha384' | 'sha512' | (string & {});
30+
31+
export const hasher = (
32+
data: string | ArrayBuffer,
33+
algorithm: HasherAlgorithm = 'sha256',
34+
) => {
35+
const msg =
36+
typeof data === 'string' ? toUTF8Array(data) : new Uint8Array(data);
37+
38+
const alg = toCryptoAlg(algorithm);
39+
40+
switch (alg) {
41+
case 'sha256':
42+
return sha256(msg);
43+
case 'sha384':
44+
return sha384(msg);
45+
case 'sha512':
46+
return sha512(msg);
47+
default:
48+
throw new SDJWTException(`Unsupported algorithm: ${algorithm}`);
49+
}
50+
};
51+
52+
const toCryptoAlg = (hashAlg: HasherAlgorithm): string =>
53+
// To cover sha-256, sha256, SHA-256, SHA256
54+
hashAlg
55+
.replace('-', '')
56+
.toLowerCase();
57+
58+
function toUTF8Array(str: string) {
59+
const utf8: Array<number> = [];
60+
for (let i = 0; i < str.length; i++) {
61+
let charcode = str.charCodeAt(i);
62+
if (charcode < 0x80) utf8.push(charcode);
63+
else if (charcode < 0x800) {
64+
utf8.push(0xc0 | (charcode >> 6), 0x80 | (charcode & 0x3f));
65+
} else if (charcode < 0xd800 || charcode >= 0xe000) {
66+
utf8.push(
67+
0xe0 | (charcode >> 12),
68+
0x80 | ((charcode >> 6) & 0x3f),
69+
0x80 | (charcode & 0x3f),
70+
);
71+
}
72+
// surrogate pair
73+
else {
74+
i++;
75+
// UTF-16 encodes 0x10000-0x10FFFF by
76+
// subtracting 0x10000 and splitting the
77+
// 20 bits of 0x0-0xFFFFF into two halves
78+
charcode =
79+
0x10000 + (((charcode & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
80+
utf8.push(
81+
0xf0 | (charcode >> 18),
82+
0x80 | ((charcode >> 12) & 0x3f),
83+
0x80 | ((charcode >> 6) & 0x3f),
84+
0x80 | (charcode & 0x3f),
85+
);
86+
}
87+
}
88+
return new Uint8Array(utf8);
89+
}

packages/hash/src/sha256.ts

Lines changed: 0 additions & 54 deletions
This file was deleted.

packages/hash/src/test/sha256.spec.ts renamed to packages/hash/src/test/sha.spec.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { digest } from '@sd-jwt/crypto-nodejs';
22
import { bytesToHex } from '@noble/hashes/utils';
33
import { hasher, sha256 } from '../index';
44
import { describe, expect, test } from 'vitest';
5+
import { createHash } from 'node:crypto';
56

6-
describe('SHA-256 tests', () => {
7+
describe('hashing tests', () => {
78
test('test#1', async () => {
89
const payload = 'test1';
910
const s1 = bytesToHex(await digest(payload));
@@ -123,4 +124,66 @@ describe('SHA-256 tests', () => {
123124
expect(e).toBeInstanceOf(Error);
124125
}
125126
});
127+
128+
describe('Hash', () => {
129+
(process.env.npm_lifecycle_event === 'test:browser' ? test.skip : test)(
130+
'sha256 - string',
131+
() => {
132+
const data = 'test';
133+
const hashdata = hasher(data, 'sha-256');
134+
const hashdata2 = createHash('sha256').update(data).digest();
135+
expect(bytesToHex(hashdata)).toEqual(bytesToHex(hashdata2));
136+
},
137+
);
138+
139+
(process.env.npm_lifecycle_event === 'test:browser' ? test.skip : test)(
140+
'sha256 - arraybuffer',
141+
() => {
142+
const data = new TextEncoder().encode('test');
143+
const hashdata = hasher(data, 'sha-256');
144+
const hashdata2 = createHash('sha256').update(data).digest();
145+
expect(bytesToHex(hashdata)).toEqual(bytesToHex(hashdata2));
146+
},
147+
);
148+
149+
(process.env.npm_lifecycle_event === 'test:browser' ? test.skip : test)(
150+
'sha-384 - string',
151+
() => {
152+
const data = 'test';
153+
const hashdata = hasher(data, 'sha-384');
154+
const hashdata2 = createHash('sha384').update(data).digest();
155+
expect(bytesToHex(hashdata)).toEqual(bytesToHex(hashdata2));
156+
},
157+
);
158+
159+
(process.env.npm_lifecycle_event === 'test:browser' ? test.skip : test)(
160+
'sha-384 - arraybuffer',
161+
() => {
162+
const data = new TextEncoder().encode('test');
163+
const hashdata = hasher(data, 'sha-384');
164+
const hashdata2 = createHash('sha384').update(data).digest();
165+
expect(bytesToHex(hashdata)).toEqual(bytesToHex(hashdata2));
166+
},
167+
);
168+
169+
(process.env.npm_lifecycle_event === 'test:browser' ? test.skip : test)(
170+
'sha-512 - string',
171+
() => {
172+
const data = 'test';
173+
const hashdata = hasher(data, 'sha-512');
174+
const hashdata2 = createHash('sha512').update(data).digest();
175+
expect(bytesToHex(hashdata)).toEqual(bytesToHex(hashdata2));
176+
},
177+
);
178+
179+
(process.env.npm_lifecycle_event === 'test:browser' ? test.skip : test)(
180+
'sha-512 - arraybuffer',
181+
() => {
182+
const data = new TextEncoder().encode('test');
183+
const hashdata = hasher(data, 'sha-512');
184+
const hashdata2 = createHash('sha512').update(data).digest();
185+
expect(bytesToHex(hashdata)).toEqual(bytesToHex(hashdata2));
186+
},
187+
);
188+
});
126189
});

pnpm-lock.yaml

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)