leviathan-crypto 1.4.0 → 2.0.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/CLAUDE.md +129 -94
- package/README.md +166 -223
- package/SECURITY.md +85 -45
- package/dist/chacha20/cipher-suite.d.ts +4 -0
- package/dist/chacha20/cipher-suite.js +78 -0
- package/dist/chacha20/embedded.d.ts +1 -0
- package/dist/chacha20/embedded.js +27 -0
- package/dist/chacha20/index.d.ts +20 -27
- package/dist/chacha20/index.js +40 -59
- package/dist/chacha20/ops.d.ts +1 -1
- package/dist/chacha20/ops.js +19 -18
- package/dist/chacha20/pool-worker.js +77 -0
- package/dist/ct-wasm.d.ts +1 -0
- package/dist/ct-wasm.js +3 -0
- package/dist/ct.wasm +0 -0
- package/dist/docs/aead.md +320 -0
- package/dist/docs/architecture.md +419 -285
- package/dist/docs/argon2id.md +42 -30
- package/dist/docs/chacha20.md +192 -266
- package/dist/docs/exports.md +241 -0
- package/dist/docs/fortuna.md +60 -69
- package/dist/docs/init.md +172 -178
- package/dist/docs/loader.md +87 -142
- package/dist/docs/serpent.md +134 -583
- package/dist/docs/sha2.md +91 -103
- package/dist/docs/sha3.md +70 -36
- package/dist/docs/types.md +93 -16
- package/dist/docs/utils.md +109 -32
- package/dist/embedded/kyber.d.ts +1 -0
- package/dist/embedded/kyber.js +3 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.js +38 -0
- package/dist/fortuna.d.ts +0 -6
- package/dist/fortuna.js +5 -5
- package/dist/index.d.ts +25 -9
- package/dist/index.js +36 -7
- package/dist/init.d.ts +3 -7
- package/dist/init.js +18 -35
- package/dist/keccak/embedded.d.ts +1 -0
- package/dist/keccak/embedded.js +27 -0
- package/dist/keccak/index.d.ts +4 -0
- package/dist/keccak/index.js +31 -0
- package/dist/kyber/embedded.d.ts +1 -0
- package/dist/kyber/embedded.js +27 -0
- package/dist/kyber/indcpa.d.ts +49 -0
- package/dist/kyber/indcpa.js +352 -0
- package/dist/kyber/index.d.ts +38 -0
- package/dist/kyber/index.js +150 -0
- package/dist/kyber/kem.d.ts +21 -0
- package/dist/kyber/kem.js +160 -0
- package/dist/kyber/params.d.ts +14 -0
- package/dist/kyber/params.js +37 -0
- package/dist/kyber/suite.d.ts +13 -0
- package/dist/kyber/suite.js +93 -0
- package/dist/kyber/types.d.ts +98 -0
- package/dist/kyber/types.js +25 -0
- package/dist/kyber/validate.d.ts +19 -0
- package/dist/kyber/validate.js +68 -0
- package/dist/kyber.wasm +0 -0
- package/dist/loader.d.ts +15 -6
- package/dist/loader.js +65 -21
- package/dist/serpent/cipher-suite.d.ts +4 -0
- package/dist/serpent/cipher-suite.js +121 -0
- package/dist/serpent/embedded.d.ts +1 -0
- package/dist/serpent/embedded.js +27 -0
- package/dist/serpent/index.d.ts +6 -37
- package/dist/serpent/index.js +9 -118
- package/dist/serpent/pool-worker.d.ts +1 -0
- package/dist/serpent/pool-worker.js +202 -0
- package/dist/serpent/serpent-cbc.d.ts +30 -0
- package/dist/serpent/serpent-cbc.js +136 -0
- package/dist/sha2/embedded.d.ts +1 -0
- package/dist/sha2/embedded.js +27 -0
- package/dist/sha2/hkdf.js +6 -2
- package/dist/sha2/index.d.ts +3 -2
- package/dist/sha2/index.js +3 -4
- package/dist/sha3/embedded.d.ts +1 -0
- package/dist/sha3/embedded.js +27 -0
- package/dist/sha3/index.d.ts +3 -2
- package/dist/sha3/index.js +3 -4
- package/dist/stream/constants.d.ts +6 -0
- package/dist/stream/constants.js +30 -0
- package/dist/stream/header.d.ts +9 -0
- package/dist/stream/header.js +77 -0
- package/dist/stream/index.d.ts +7 -0
- package/dist/stream/index.js +27 -0
- package/dist/stream/open-stream.d.ts +21 -0
- package/dist/stream/open-stream.js +146 -0
- package/dist/stream/seal-stream-pool.d.ts +38 -0
- package/dist/stream/seal-stream-pool.js +391 -0
- package/dist/stream/seal-stream.d.ts +20 -0
- package/dist/stream/seal-stream.js +142 -0
- package/dist/stream/seal.d.ts +9 -0
- package/dist/stream/seal.js +75 -0
- package/dist/stream/types.d.ts +24 -0
- package/dist/stream/types.js +26 -0
- package/dist/utils.d.ts +7 -2
- package/dist/utils.js +49 -3
- package/dist/wasm-source.d.ts +12 -0
- package/dist/wasm-source.js +26 -0
- package/package.json +13 -5
- package/dist/chacha20/pool.d.ts +0 -52
- package/dist/chacha20/pool.js +0 -178
- package/dist/chacha20/pool.worker.js +0 -37
- package/dist/chacha20/stream-sealer.d.ts +0 -49
- package/dist/chacha20/stream-sealer.js +0 -327
- package/dist/docs/chacha20_pool.md +0 -309
- package/dist/docs/wasm.md +0 -194
- package/dist/serpent/seal.d.ts +0 -8
- package/dist/serpent/seal.js +0 -72
- package/dist/serpent/stream-pool.d.ts +0 -48
- package/dist/serpent/stream-pool.js +0 -275
- package/dist/serpent/stream-sealer.d.ts +0 -55
- package/dist/serpent/stream-sealer.js +0 -342
- package/dist/serpent/stream.d.ts +0 -28
- package/dist/serpent/stream.js +0 -205
- package/dist/serpent/stream.worker.d.ts +0 -32
- package/dist/serpent/stream.worker.js +0 -117
- /package/dist/chacha20/{pool.worker.d.ts → pool-worker.d.ts} +0 -0
package/dist/docs/sha2.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# SHA-2
|
|
1
|
+
# SHA-2 TypeScript API
|
|
2
2
|
|
|
3
3
|
> [!NOTE]
|
|
4
4
|
> Cryptographic hashing and message authentication using SHA-256, SHA-384,
|
|
@@ -6,42 +6,49 @@
|
|
|
6
6
|
>
|
|
7
7
|
> See [SHA-2 implementation audit](./sha2_audit.md), [HMAC audit](./hmac_audit.md), and [HKDF audit](./hkdf_audit.md) for algorithm correctness verifications.
|
|
8
8
|
|
|
9
|
+
> ### Table of Contents
|
|
10
|
+
> - [Overview](#overview)
|
|
11
|
+
> - [Security Notes](#security-notes)
|
|
12
|
+
> - [Module Init](#module-init)
|
|
13
|
+
> - [API Reference](#api-reference)
|
|
14
|
+
> - [Usage Examples](#usage-examples)
|
|
15
|
+
> - [Error Conditions](#error-conditions)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
9
19
|
## Overview
|
|
10
20
|
|
|
11
21
|
SHA-2 is a family of cryptographic hash functions standardized in
|
|
12
22
|
[FIPS 180-4](https://csrc.nist.gov/publications/detail/fips/180/4/final).
|
|
13
|
-
A hash function takes an input of any size
|
|
14
|
-
byte -- and produces a fixed-size output called a **digest** (sometimes called
|
|
23
|
+
A hash function takes an input of any size (a password, a file, a single byte) and produces a fixed-size output called a **digest** (sometimes called
|
|
15
24
|
a "fingerprint" or "hash"). Even the smallest change to the input produces a
|
|
16
25
|
completely different digest. This makes hash functions useful for verifying that
|
|
17
26
|
data has not been tampered with.
|
|
18
27
|
|
|
19
28
|
leviathan-crypto provides three SHA-2 variants:
|
|
20
29
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
Useful when you need a digest longer than 256 bits but shorter than 512 bits,
|
|
27
|
-
or when a protocol specifies it (e.g. TLS cipher suites).
|
|
30
|
+
**SHA-256.** 32-byte (256-bit) digest. The most widely used variant. Use this unless you have a specific reason to choose another.
|
|
31
|
+
|
|
32
|
+
**SHA-512.** 64-byte (512-bit) digest. Higher security margin. Faster than SHA-256 on 64-bit platforms.
|
|
33
|
+
|
|
34
|
+
**SHA-384.** 48-byte (384-bit) digest. A truncated variant of SHA-512. Useful when you need a digest longer than 256 bits but shorter than 512 bits, or when a protocol specifies it (e.g. TLS cipher suites).
|
|
28
35
|
|
|
29
36
|
**HMAC** (Hash-based Message Authentication Code, [RFC 2104](https://www.rfc-editor.org/rfc/rfc2104))
|
|
30
37
|
combines a secret key with a hash function to produce a **tag** that proves both
|
|
31
38
|
the integrity and the authenticity of a message. Anyone can compute a plain SHA-256
|
|
32
|
-
hash of a message
|
|
33
|
-
|
|
34
|
-
someone who knows the key, and that it was not modified in transit.
|
|
39
|
+
hash of a message. But only someone who holds the secret key can compute the correct HMAC tag. The recipient can then verify that the message was sent by
|
|
40
|
+
someone who knows the key and that it was not modified in transit.
|
|
35
41
|
|
|
36
42
|
leviathan-crypto provides three HMAC variants corresponding to each hash:
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
**HMAC_SHA256.** 32-byte tag.
|
|
45
|
+
|
|
46
|
+
**HMAC_SHA512.** 64-byte tag.
|
|
47
|
+
|
|
48
|
+
**HMAC_SHA384.** 48-byte tag.
|
|
41
49
|
|
|
42
50
|
All computation runs in WebAssembly. The TypeScript classes handle input
|
|
43
|
-
validation and the JS/WASM boundary
|
|
44
|
-
algorithms directly.
|
|
51
|
+
validation and the JS/WASM boundary. They never implement cryptographic algorithms directly.
|
|
45
52
|
|
|
46
53
|
---
|
|
47
54
|
|
|
@@ -70,7 +77,7 @@ encryption with leviathan primitives.
|
|
|
70
77
|
Never construct a MAC by concatenating a secret and a message and hashing them:
|
|
71
78
|
|
|
72
79
|
```typescript
|
|
73
|
-
// DANGEROUS
|
|
80
|
+
// DANGEROUS: DO NOT DO THIS
|
|
74
81
|
const bad = sha256.hash(concat(secret, message))
|
|
75
82
|
```
|
|
76
83
|
|
|
@@ -94,8 +101,7 @@ HMAC keys should be **at least as long as the hash output**:
|
|
|
94
101
|
Keys shorter than this are technically valid (they will be zero-padded
|
|
95
102
|
internally) but provide less security than the hash function offers. Keys
|
|
96
103
|
longer than the hash block size (64 bytes for SHA-256, 128 bytes for
|
|
97
|
-
SHA-384/SHA-512) are pre-hashed automatically per RFC 2104 section 3
|
|
98
|
-
handled for you, but there is no benefit to using very long keys.
|
|
104
|
+
SHA-384/SHA-512) are pre-hashed automatically per RFC 2104 section 3. There is no benefit to using very long keys.
|
|
99
105
|
|
|
100
106
|
### Always use constant-time comparison for HMAC verification
|
|
101
107
|
|
|
@@ -121,24 +127,25 @@ with a hash or HMAC instance.
|
|
|
121
127
|
Each module subpath exports its own init function for consumers who want
|
|
122
128
|
tree-shakeable imports.
|
|
123
129
|
|
|
124
|
-
### `sha2Init(
|
|
130
|
+
### `sha2Init(source)`
|
|
125
131
|
|
|
126
132
|
Initializes only the sha2 WASM binary. Equivalent to calling the
|
|
127
|
-
root `init(
|
|
133
|
+
root `init({ sha2: source })` but without pulling the other three
|
|
128
134
|
modules into the bundle.
|
|
129
135
|
|
|
130
136
|
**Signature:**
|
|
131
137
|
|
|
132
138
|
```typescript
|
|
133
|
-
async function sha2Init(
|
|
139
|
+
async function sha2Init(source: WasmSource): Promise<void>
|
|
134
140
|
```
|
|
135
141
|
|
|
136
142
|
**Usage:**
|
|
137
143
|
|
|
138
144
|
```typescript
|
|
139
145
|
import { sha2Init, SHA256 } from 'leviathan-crypto/sha2'
|
|
146
|
+
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
140
147
|
|
|
141
|
-
await sha2Init()
|
|
148
|
+
await sha2Init(sha2Wasm)
|
|
142
149
|
const sha = new SHA256()
|
|
143
150
|
```
|
|
144
151
|
|
|
@@ -146,7 +153,7 @@ const sha = new SHA256()
|
|
|
146
153
|
|
|
147
154
|
## API Reference
|
|
148
155
|
|
|
149
|
-
All classes require `init(
|
|
156
|
+
All classes require `init({ sha2: sha2Wasm })` or the subpath `sha2Init(sha2Wasm)` to be called first.
|
|
150
157
|
Constructing any SHA-2 class before initialization throws an error.
|
|
151
158
|
|
|
152
159
|
### SHA256
|
|
@@ -161,16 +168,11 @@ class SHA256 {
|
|
|
161
168
|
}
|
|
162
169
|
```
|
|
163
170
|
|
|
164
|
-
**`constructor()`**
|
|
165
|
-
has not been called.
|
|
171
|
+
**`constructor()`** Creates a new SHA256 instance. Throws if `init({ sha2: sha2Wasm })` has not been called.
|
|
166
172
|
|
|
167
|
-
**`hash(msg: Uint8Array): Uint8Array`**
|
|
168
|
-
a 32-byte `Uint8Array` digest. The message can be any length (including empty).
|
|
169
|
-
Large messages are internally chunked and streamed through the WASM hash function,
|
|
170
|
-
so memory usage stays constant regardless of input size.
|
|
173
|
+
**`hash(msg: Uint8Array): Uint8Array`** Hashes the entire message and returns a 32-byte `Uint8Array` digest. The message can be any length including empty. Large messages are internally chunked and streamed through the WASM hash function, so memory usage stays constant regardless of input size.
|
|
171
174
|
|
|
172
|
-
**`dispose(): void`**
|
|
173
|
-
buffer, output buffer). Call this when you are done with the instance.
|
|
175
|
+
**`dispose(): void`** Wipes all internal WASM buffers (hash state, input buffer, output buffer). Call this when you are done with the instance.
|
|
174
176
|
|
|
175
177
|
---
|
|
176
178
|
|
|
@@ -186,11 +188,11 @@ class SHA512 {
|
|
|
186
188
|
}
|
|
187
189
|
```
|
|
188
190
|
|
|
189
|
-
**`constructor()`**
|
|
191
|
+
**`constructor()`** Creates a new SHA512 instance. Throws if not initialized.
|
|
190
192
|
|
|
191
|
-
**`hash(msg: Uint8Array): Uint8Array`**
|
|
193
|
+
**`hash(msg: Uint8Array): Uint8Array`** Returns a 64-byte digest.
|
|
192
194
|
|
|
193
|
-
**`dispose(): void`**
|
|
195
|
+
**`dispose(): void`** Wipes all internal WASM buffers.
|
|
194
196
|
|
|
195
197
|
---
|
|
196
198
|
|
|
@@ -207,11 +209,11 @@ class SHA384 {
|
|
|
207
209
|
}
|
|
208
210
|
```
|
|
209
211
|
|
|
210
|
-
**`constructor()`**
|
|
212
|
+
**`constructor()`** Creates a new SHA384 instance. Throws if not initialized.
|
|
211
213
|
|
|
212
|
-
**`hash(msg: Uint8Array): Uint8Array`**
|
|
214
|
+
**`hash(msg: Uint8Array): Uint8Array`** Returns a 48-byte digest.
|
|
213
215
|
|
|
214
|
-
**`dispose(): void`**
|
|
216
|
+
**`dispose(): void`** Wipes all internal WASM buffers.
|
|
215
217
|
|
|
216
218
|
---
|
|
217
219
|
|
|
@@ -227,15 +229,11 @@ class HMAC_SHA256 {
|
|
|
227
229
|
}
|
|
228
230
|
```
|
|
229
231
|
|
|
230
|
-
**`constructor()`**
|
|
231
|
-
initialized.
|
|
232
|
+
**`constructor()`** Creates a new HMAC_SHA256 instance. Throws if not initialized.
|
|
232
233
|
|
|
233
|
-
**`hash(key: Uint8Array, msg: Uint8Array): Uint8Array`**
|
|
234
|
-
HMAC-SHA256 tag for the given message using the given key. Returns a 32-byte
|
|
235
|
-
`Uint8Array`. Keys longer than 64 bytes are automatically pre-hashed with
|
|
236
|
-
SHA-256 per RFC 2104 section 3.
|
|
234
|
+
**`hash(key: Uint8Array, msg: Uint8Array): Uint8Array`** Computes the HMAC-SHA256 tag for the given message using the given key. Returns a 32-byte `Uint8Array`. Keys longer than 64 bytes are automatically pre-hashed with SHA-256 per RFC 2104 section 3.
|
|
237
235
|
|
|
238
|
-
**`dispose(): void`**
|
|
236
|
+
**`dispose(): void`** Wipes all internal WASM buffers, including key material.
|
|
239
237
|
|
|
240
238
|
---
|
|
241
239
|
|
|
@@ -251,13 +249,11 @@ class HMAC_SHA512 {
|
|
|
251
249
|
}
|
|
252
250
|
```
|
|
253
251
|
|
|
254
|
-
**`constructor()`**
|
|
255
|
-
initialized.
|
|
252
|
+
**`constructor()`** Creates a new HMAC_SHA512 instance. Throws if not initialized.
|
|
256
253
|
|
|
257
|
-
**`hash(key: Uint8Array, msg: Uint8Array): Uint8Array`**
|
|
258
|
-
HMAC tag. Keys longer than 128 bytes are pre-hashed with SHA-512.
|
|
254
|
+
**`hash(key: Uint8Array, msg: Uint8Array): Uint8Array`** Returns a 64-byte HMAC tag. Keys longer than 128 bytes are pre-hashed with SHA-512.
|
|
259
255
|
|
|
260
|
-
**`dispose(): void`**
|
|
256
|
+
**`dispose(): void`** Wipes all internal WASM buffers.
|
|
261
257
|
|
|
262
258
|
---
|
|
263
259
|
|
|
@@ -273,13 +269,11 @@ class HMAC_SHA384 {
|
|
|
273
269
|
}
|
|
274
270
|
```
|
|
275
271
|
|
|
276
|
-
**`constructor()`**
|
|
277
|
-
initialized.
|
|
272
|
+
**`constructor()`** Creates a new HMAC_SHA384 instance. Throws if not initialized.
|
|
278
273
|
|
|
279
|
-
**`hash(key: Uint8Array, msg: Uint8Array): Uint8Array`**
|
|
280
|
-
HMAC tag. Keys longer than 128 bytes are pre-hashed with SHA-384.
|
|
274
|
+
**`hash(key: Uint8Array, msg: Uint8Array): Uint8Array`** Returns a 48-byte HMAC tag. Keys longer than 128 bytes are pre-hashed with SHA-384.
|
|
281
275
|
|
|
282
|
-
**`dispose(): void`**
|
|
276
|
+
**`dispose(): void`** Wipes all internal WASM buffers.
|
|
283
277
|
|
|
284
278
|
---
|
|
285
279
|
|
|
@@ -302,26 +296,15 @@ class HKDF_SHA256 {
|
|
|
302
296
|
}
|
|
303
297
|
```
|
|
304
298
|
|
|
305
|
-
**`constructor()`**
|
|
306
|
-
`init(['sha2'])` has not been called.
|
|
299
|
+
**`constructor()`** Creates a new HKDF_SHA256 instance. Throws if `init({ sha2: sha2Wasm })` has not been called.
|
|
307
300
|
|
|
308
|
-
**`extract(salt, ikm): Uint8Array`**
|
|
309
|
-
`PRK = HMAC-SHA256(salt, IKM)`. Returns a 32-byte pseudorandom key. If `salt`
|
|
310
|
-
is `null` or empty, defaults to 32 zero bytes per RFC section 2.2.
|
|
301
|
+
**`extract(salt, ikm): Uint8Array`** RFC 5869 section 2.2. Computes `PRK = HMAC-SHA256(salt, IKM)`. Returns a 32-byte pseudorandom key. If `salt` is `null` or empty, defaults to 32 zero bytes per RFC section 2.2.
|
|
311
302
|
|
|
312
|
-
**`expand(prk, info, length): Uint8Array`**
|
|
313
|
-
`length` bytes of output keying material from a 32-byte PRK. `info` provides
|
|
314
|
-
application-specific context (can be empty). `length` must be between 1 and
|
|
315
|
-
8160 (255 x 32). Throws `RangeError` if `prk` is not exactly 32 bytes or if
|
|
316
|
-
`length` is out of range.
|
|
303
|
+
**`expand(prk, info, length): Uint8Array`** RFC 5869 section 2.3. Derives `length` bytes of output keying material from a 32-byte PRK. `info` provides application-specific context (can be empty). `length` must be between 1 and 8160 (255 x 32). Throws `RangeError` if `prk` is not exactly 32 bytes or if `length` is out of range.
|
|
317
304
|
|
|
318
|
-
**`derive(ikm, salt, info, length): Uint8Array`**
|
|
319
|
-
`extract(salt, ikm)` then `expand(prk, info, length)`. This is the correct
|
|
320
|
-
path for most callers. `extract()` and `expand()` are exposed separately for
|
|
321
|
-
advanced use cases such as key separation and ratchets -- callers who reach for
|
|
322
|
-
them should know why.
|
|
305
|
+
**`derive(ikm, salt, info, length): Uint8Array`** One-shot: calls `extract(salt, ikm)` then `expand(prk, info, length)`. This is the correct path for most callers. `extract()` and `expand()` are exposed separately for advanced use cases such as key separation and ratchets. Callers who reach for them should know why.
|
|
323
306
|
|
|
324
|
-
**`dispose(): void`**
|
|
307
|
+
**`dispose(): void`** Releases the internal HMAC instance.
|
|
325
308
|
|
|
326
309
|
---
|
|
327
310
|
|
|
@@ -340,11 +323,9 @@ class HKDF_SHA512 {
|
|
|
340
323
|
}
|
|
341
324
|
```
|
|
342
325
|
|
|
343
|
-
**`extract(salt, ikm)`**
|
|
344
|
-
bytes.
|
|
326
|
+
**`extract(salt, ikm)`** If `salt` is `null` or empty, defaults to 64 zero bytes.
|
|
345
327
|
|
|
346
|
-
**`expand(prk, info, length)`**
|
|
347
|
-
be between 1 and 16320. Throws `RangeError` otherwise.
|
|
328
|
+
**`expand(prk, info, length)`** PRK must be exactly 64 bytes. `length` must be between 1 and 16320. Throws `RangeError` otherwise.
|
|
348
329
|
|
|
349
330
|
> [!NOTE]
|
|
350
331
|
> HKDF is a pure TypeScript composition over the WASM-backed HMAC classes.
|
|
@@ -355,8 +336,9 @@ be between 1 and 16320. Throws `RangeError` otherwise.
|
|
|
355
336
|
|
|
356
337
|
```typescript
|
|
357
338
|
import { init, HKDF_SHA256, bytesToHex } from 'leviathan-crypto'
|
|
339
|
+
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
358
340
|
|
|
359
|
-
await init(
|
|
341
|
+
await init({ sha2: sha2Wasm })
|
|
360
342
|
|
|
361
343
|
const hkdf = new HKDF_SHA256()
|
|
362
344
|
const ikm = new Uint8Array(32) // your input keying material
|
|
@@ -375,13 +357,14 @@ hkdf.dispose()
|
|
|
375
357
|
|
|
376
358
|
### Example 1: Hash a message with SHA-256
|
|
377
359
|
|
|
378
|
-
|
|
360
|
+
Hash a string and get a hex-encoded digest.
|
|
379
361
|
|
|
380
362
|
```typescript
|
|
381
363
|
import { init, SHA256, bytesToHex, utf8ToBytes } from 'leviathan-crypto'
|
|
364
|
+
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
382
365
|
|
|
383
366
|
// Step 1: Initialize the SHA-2 WASM module (do this once at app startup)
|
|
384
|
-
await init(
|
|
367
|
+
await init({ sha2: sha2Wasm })
|
|
385
368
|
|
|
386
369
|
// Step 2: Create a SHA256 instance
|
|
387
370
|
const sha = new SHA256()
|
|
@@ -395,7 +378,7 @@ const digest = sha.hash(message)
|
|
|
395
378
|
console.log(bytesToHex(digest))
|
|
396
379
|
// => "315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3"
|
|
397
380
|
|
|
398
|
-
// Step 5: Clean up
|
|
381
|
+
// Step 5: Clean up, wipes hash state from WASM memory
|
|
399
382
|
sha.dispose()
|
|
400
383
|
```
|
|
401
384
|
|
|
@@ -407,8 +390,9 @@ response.
|
|
|
407
390
|
|
|
408
391
|
```typescript
|
|
409
392
|
import { init, SHA256, bytesToHex } from 'leviathan-crypto'
|
|
393
|
+
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
410
394
|
|
|
411
|
-
await init(
|
|
395
|
+
await init({ sha2: sha2Wasm })
|
|
412
396
|
|
|
413
397
|
// Suppose you have file contents as an ArrayBuffer (from FileReader, fetch, etc.)
|
|
414
398
|
const response = await fetch('https://example.com/file.bin')
|
|
@@ -421,8 +405,7 @@ console.log('SHA-256:', bytesToHex(digest))
|
|
|
421
405
|
sha.dispose()
|
|
422
406
|
```
|
|
423
407
|
|
|
424
|
-
The library handles large inputs automatically
|
|
425
|
-
the WASM hash function in chunks, so you do not need to worry about memory.
|
|
408
|
+
The library handles large inputs automatically. It streams the data through the WASM hash function in chunks, so you do not need to worry about memory.
|
|
426
409
|
|
|
427
410
|
### Example 3: Using SHA-512 or SHA-384
|
|
428
411
|
|
|
@@ -430,8 +413,9 @@ The API is identical for all three hash variants. Only the output size differs.
|
|
|
430
413
|
|
|
431
414
|
```typescript
|
|
432
415
|
import { init, SHA256, SHA384, SHA512, bytesToHex, utf8ToBytes } from 'leviathan-crypto'
|
|
416
|
+
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
433
417
|
|
|
434
|
-
await init(
|
|
418
|
+
await init({ sha2: sha2Wasm })
|
|
435
419
|
|
|
436
420
|
const msg = utf8ToBytes('Same message, different hashes')
|
|
437
421
|
|
|
@@ -451,16 +435,17 @@ sha512.dispose()
|
|
|
451
435
|
### Example 4: Generate and verify an HMAC
|
|
452
436
|
|
|
453
437
|
Use HMAC when you need to prove that a message was created by someone who holds
|
|
454
|
-
a secret key.
|
|
455
|
-
|
|
438
|
+
a secret key. One side generates a tag, the other side recomputes the tag with
|
|
439
|
+
the same key and checks that they match.
|
|
456
440
|
|
|
457
441
|
```typescript
|
|
458
442
|
import {
|
|
459
443
|
init, HMAC_SHA256, constantTimeEqual, randomBytes,
|
|
460
444
|
bytesToHex, utf8ToBytes
|
|
461
445
|
} from 'leviathan-crypto'
|
|
446
|
+
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
462
447
|
|
|
463
|
-
await init(
|
|
448
|
+
await init({ sha2: sha2Wasm })
|
|
464
449
|
|
|
465
450
|
// Generate a random 32-byte key (do this once, store it securely)
|
|
466
451
|
const key = randomBytes(32)
|
|
@@ -480,7 +465,7 @@ const recomputed = hmac.hash(key, message)
|
|
|
480
465
|
|
|
481
466
|
// Use constant-time comparison to check the tags
|
|
482
467
|
if (constantTimeEqual(tag, recomputed)) {
|
|
483
|
-
console.log('Message is authentic
|
|
468
|
+
console.log('Message is authentic. It was not tampered with.')
|
|
484
469
|
} else {
|
|
485
470
|
console.log('WARNING: message has been modified or key is wrong')
|
|
486
471
|
}
|
|
@@ -488,31 +473,32 @@ if (constantTimeEqual(tag, recomputed)) {
|
|
|
488
473
|
hmac.dispose()
|
|
489
474
|
```
|
|
490
475
|
|
|
491
|
-
### Example 5: HMAC verification
|
|
476
|
+
### Example 5: HMAC verification — the right way
|
|
492
477
|
|
|
493
|
-
|
|
494
|
-
|
|
478
|
+
The difference between these two approaches is the difference between a secure
|
|
479
|
+
system and a broken one.
|
|
495
480
|
|
|
496
481
|
```typescript
|
|
497
482
|
import { init, HMAC_SHA256, constantTimeEqual, bytesToHex } from 'leviathan-crypto'
|
|
483
|
+
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
498
484
|
|
|
499
|
-
await init(
|
|
485
|
+
await init({ sha2: sha2Wasm })
|
|
500
486
|
const hmac = new HMAC_SHA256()
|
|
501
487
|
|
|
502
488
|
// Suppose you received a message with a tag and you recomputed the expected tag:
|
|
503
489
|
const receivedTag = hmac.hash(key, message)
|
|
504
490
|
const expectedTag = hmac.hash(key, message)
|
|
505
491
|
|
|
506
|
-
// WRONG
|
|
492
|
+
// WRONG: timing attack vulnerable
|
|
507
493
|
// JavaScript's === operator compares byte-by-byte and returns false as soon as
|
|
508
494
|
// it finds a mismatch. An attacker can measure the response time to figure out
|
|
509
495
|
// how many leading bytes of the tag are correct, then forge a valid tag one
|
|
510
496
|
// byte at a time.
|
|
511
497
|
if (bytesToHex(receivedTag) === bytesToHex(expectedTag)) {
|
|
512
|
-
// This
|
|
498
|
+
// This works but is insecure
|
|
513
499
|
}
|
|
514
500
|
|
|
515
|
-
// RIGHT
|
|
501
|
+
// RIGHT: constant-time comparison
|
|
516
502
|
// constantTimeEqual always examines every byte, regardless of where the first
|
|
517
503
|
// difference is. The comparison takes the same amount of time whether zero
|
|
518
504
|
// bytes match or all bytes match.
|
|
@@ -529,8 +515,9 @@ The pattern is identical to HMAC-SHA256. Use a 64-byte key for full security.
|
|
|
529
515
|
|
|
530
516
|
```typescript
|
|
531
517
|
import { init, HMAC_SHA512, constantTimeEqual, randomBytes, utf8ToBytes } from 'leviathan-crypto'
|
|
518
|
+
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
532
519
|
|
|
533
|
-
await init(
|
|
520
|
+
await init({ sha2: sha2Wasm })
|
|
534
521
|
|
|
535
522
|
// 64-byte key for HMAC-SHA512
|
|
536
523
|
const key = randomBytes(64)
|
|
@@ -552,8 +539,9 @@ SHA-2 is well-defined for empty inputs. This can be useful as a sanity check.
|
|
|
552
539
|
|
|
553
540
|
```typescript
|
|
554
541
|
import { init, SHA256, bytesToHex } from 'leviathan-crypto'
|
|
542
|
+
import { sha2Wasm } from 'leviathan-crypto/sha2/embedded'
|
|
555
543
|
|
|
556
|
-
await init(
|
|
544
|
+
await init({ sha2: sha2Wasm })
|
|
557
545
|
|
|
558
546
|
const sha = new SHA256()
|
|
559
547
|
const digest = sha.hash(new Uint8Array(0))
|
|
@@ -570,14 +558,14 @@ sha.dispose()
|
|
|
570
558
|
|
|
571
559
|
### Module not initialized
|
|
572
560
|
|
|
573
|
-
If you construct any SHA-2 class before calling `init(
|
|
561
|
+
If you construct any SHA-2 class before calling `init({ sha2: sha2Wasm })`, the
|
|
574
562
|
constructor throws immediately:
|
|
575
563
|
|
|
576
564
|
```
|
|
577
|
-
Error: leviathan-crypto: call init(
|
|
565
|
+
Error: leviathan-crypto: call init({ sha2: ... }) before using this class
|
|
578
566
|
```
|
|
579
567
|
|
|
580
|
-
**Fix:** Call `await init(
|
|
568
|
+
**Fix:** Call `await init({ sha2: sha2Wasm })` at application startup, before creating any
|
|
581
569
|
SHA-2 instances.
|
|
582
570
|
|
|
583
571
|
```typescript
|
|
@@ -585,7 +573,7 @@ SHA-2 instances.
|
|
|
585
573
|
const sha = new SHA256() // Error!
|
|
586
574
|
|
|
587
575
|
// Do this instead:
|
|
588
|
-
await init(
|
|
576
|
+
await init({ sha2: sha2Wasm })
|
|
589
577
|
const sha = new SHA256() // OK
|
|
590
578
|
```
|
|
591
579
|
|
|
@@ -616,7 +604,7 @@ SHA-2 is well-defined for zero-length messages and will return the correct diges
|
|
|
616
604
|
> - [architecture](./architecture.md) — architecture overview, module relationships, buffer layouts, and build pipeline
|
|
617
605
|
> - [asm_sha2](./asm_sha2.md) — WASM implementation details (AssemblyScript buffer layout, compression functions)
|
|
618
606
|
> - [sha3](./sha3.md) — alternative: SHA-3 family (immune to length extension attacks)
|
|
619
|
-
> - [serpent](./serpent.md) —
|
|
607
|
+
> - [serpent](./serpent.md) — `SerpentCipher` uses HMAC-SHA256 and HKDF internally via `Seal` and `SealStream`
|
|
620
608
|
> - [argon2id](./argon2id.md) — Argon2id password hashing; HKDF expands Argon2id root keys
|
|
621
609
|
> - [fortuna](./fortuna.md) — Fortuna CSPRNG uses SHA-256 for entropy accumulation
|
|
622
610
|
> - [utils](./utils.md) — `constantTimeEqual`, `bytesToHex`, `utf8ToBytes`, `randomBytes`
|