raz-encrypt 0.1.0 → 0.3.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/README.md +2 -15
- package/dist/crypto/frameCipher.d.ts +13 -4
- package/dist/crypto/frameCipher.js +15 -6
- package/dist/crypto/frameCipher.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -145,19 +145,6 @@ const combinedResult = await verify(
|
|
|
145
145
|
|
|
146
146
|
**So, to directly answer "do I need to run verify first?"** — no. `verify()` is only relevant when Alice actually signed something; `decryptFrom()` is only relevant when Alice actually encrypted something. Run whichever one matches what you received, in either order, or both if she did both.
|
|
147
147
|
|
|
148
|
-
## Hands-on demo
|
|
149
|
-
|
|
150
|
-
The `demo/` folder is a runnable version of the quickstart above, playing Alice and Bob as two separate local identities:
|
|
151
|
-
|
|
152
|
-
```bash
|
|
153
|
-
npm run demo:setup # creates both identities, swaps public cards, records trust
|
|
154
|
-
npm run demo:sign # Alice signs demo/sample-document.json
|
|
155
|
-
npm run demo:verify # Bob verifies it -> { valid: true }
|
|
156
|
-
npm run demo:tamper # Bob verifies a modified copy -> { valid: false, reason: "hash-mismatch" }
|
|
157
|
-
npm run demo:verify # run again fresh -> proves identity/trust really persisted to disk, not just in memory
|
|
158
|
-
npm run demo:clean # removes the generated local stores/cards so you can start over
|
|
159
|
-
```
|
|
160
|
-
|
|
161
148
|
## API
|
|
162
149
|
|
|
163
150
|
| Function | What it does |
|
|
@@ -191,11 +178,11 @@ If one layer is ever broken — classical by a quantum computer, or the newer po
|
|
|
191
178
|
|
|
192
179
|
**Bottom line:** this is genuinely strong, real-world-grade cryptography built from carefully audited, widely used components (the [`@noble/*`](https://github.com/paulmillr/noble-post-quantum) libraries) — not a marketing label. It just isn't a government-certified module, and true safety also depends on this specific implementation being built and tested carefully, which is exactly what the test suite's "try to break it" cases (tampered documents, corrupted individual signatures) are for.
|
|
193
180
|
|
|
194
|
-
**What `sign`/`verify` guarantees, precisely.** Every field that affects a verification outcome — the document hash
|
|
181
|
+
**What `sign`/`verify` guarantees, precisely.** Every field that affects a verification outcome — the document hash _and_ the optional `expiresAt` — is part of the signed payload, not just `documentHash` alone. That matters because certificates travel over the exact same untrusted channels (email, chat) this library assumes for everything else: without that, an intermediary with no secret key at all could delete or rewrite `expiresAt` in transit and flip a `verify()` result, which would quietly defeat the whole point of setting an expiry.
|
|
195
182
|
|
|
196
183
|
**Two honest limitations, so you can decide if they matter for your use case:**
|
|
197
184
|
|
|
198
|
-
- **No forward secrecy.** `encryptFor`/`decryptFrom` derive their key from
|
|
185
|
+
- **No forward secrecy.** `encryptFor`/`decryptFrom` derive their key from _long-term_ keys on both sides: a static ECDH-P384 exchange (deterministic for a given pair of identities) combined with an ML-KEM-1024 encapsulation whose secret still requires the recipient's long-term KEM secret key to decapsulate. Both of those secret keys live in the same `identity.enc.json`. This means that if someone ever recovers your passphrase and that file, they can decrypt **every box anyone ever sent you**, not just future ones — there's no per-message ephemeral key rotation (the kind Signal-style ratcheting provides). Rotate identities (new `storageDir`, `trustParty()` again) if you need to bound the blast radius of a future key compromise.
|
|
199
186
|
- **No replay protection.** An `EncryptedBox` decrypts identically no matter how many times, or how much later, it's replayed to the same recipient — there's no session id, counter, or timestamp binding it to a single delivery. Fine for "send this document once"; if your use case cares about replay (e.g. treating receipt of a box as a one-time authorization), add your own nonce/timestamp inside the encrypted payload and check it on the receiving end.
|
|
200
187
|
|
|
201
188
|
## What this library deliberately does _not_ do (v1)
|
|
@@ -13,11 +13,20 @@ export interface EncryptedFrame {
|
|
|
13
13
|
* `counter`. The caller owns counter bookkeeping: increment it once per
|
|
14
14
|
* frame sent under this key, one counter sequence per direction, and never
|
|
15
15
|
* reuse a value.
|
|
16
|
+
*
|
|
17
|
+
* `aad` is optional Associated Authenticated Data: extra context (e.g.
|
|
18
|
+
* direction, frame/message type) bound into the AEAD tag but not encrypted.
|
|
19
|
+
* It is never transmitted as part of EncryptedFrame — the receiver must
|
|
20
|
+
* reconstruct the identical bytes out of band (from the frame's own
|
|
21
|
+
* metadata, e.g. its direction and type) and pass them to decryptFrame.
|
|
22
|
+
* Omitting it behaves exactly as before this parameter existed.
|
|
16
23
|
*/
|
|
17
|
-
export declare function encryptFrame(key: Uint8Array, counter: number, plaintext: Uint8Array): EncryptedFrame;
|
|
24
|
+
export declare function encryptFrame(key: Uint8Array, counter: number, plaintext: Uint8Array, aad?: Uint8Array): EncryptedFrame;
|
|
18
25
|
/**
|
|
19
26
|
* Decrypts a frame produced by encryptFrame. Fails closed — throws
|
|
20
|
-
* FrameDecryptionError — on a tampered ciphertext, a mismatched counter,
|
|
21
|
-
*
|
|
27
|
+
* FrameDecryptionError — on a tampered ciphertext, a mismatched counter, the
|
|
28
|
+
* wrong directional key, or an `aad` that doesn't match what the sender used
|
|
29
|
+
* (including one side supplying `aad` and the other omitting it), rather
|
|
30
|
+
* than ever returning corrupted plaintext.
|
|
22
31
|
*/
|
|
23
|
-
export declare function decryptFrame(key: Uint8Array, frame: EncryptedFrame): Uint8Array;
|
|
32
|
+
export declare function decryptFrame(key: Uint8Array, frame: EncryptedFrame, aad?: Uint8Array): Uint8Array;
|
|
@@ -37,19 +37,28 @@ function counterToNonce(counter) {
|
|
|
37
37
|
* `counter`. The caller owns counter bookkeeping: increment it once per
|
|
38
38
|
* frame sent under this key, one counter sequence per direction, and never
|
|
39
39
|
* reuse a value.
|
|
40
|
+
*
|
|
41
|
+
* `aad` is optional Associated Authenticated Data: extra context (e.g.
|
|
42
|
+
* direction, frame/message type) bound into the AEAD tag but not encrypted.
|
|
43
|
+
* It is never transmitted as part of EncryptedFrame — the receiver must
|
|
44
|
+
* reconstruct the identical bytes out of band (from the frame's own
|
|
45
|
+
* metadata, e.g. its direction and type) and pass them to decryptFrame.
|
|
46
|
+
* Omitting it behaves exactly as before this parameter existed.
|
|
40
47
|
*/
|
|
41
|
-
export function encryptFrame(key, counter, plaintext) {
|
|
42
|
-
const ciphertext = gcm(key, counterToNonce(counter)).encrypt(plaintext);
|
|
48
|
+
export function encryptFrame(key, counter, plaintext, aad) {
|
|
49
|
+
const ciphertext = gcm(key, counterToNonce(counter), aad).encrypt(plaintext);
|
|
43
50
|
return { counter, ciphertext: bytesToHex(ciphertext) };
|
|
44
51
|
}
|
|
45
52
|
/**
|
|
46
53
|
* Decrypts a frame produced by encryptFrame. Fails closed — throws
|
|
47
|
-
* FrameDecryptionError — on a tampered ciphertext, a mismatched counter,
|
|
48
|
-
*
|
|
54
|
+
* FrameDecryptionError — on a tampered ciphertext, a mismatched counter, the
|
|
55
|
+
* wrong directional key, or an `aad` that doesn't match what the sender used
|
|
56
|
+
* (including one side supplying `aad` and the other omitting it), rather
|
|
57
|
+
* than ever returning corrupted plaintext.
|
|
49
58
|
*/
|
|
50
|
-
export function decryptFrame(key, frame) {
|
|
59
|
+
export function decryptFrame(key, frame, aad) {
|
|
51
60
|
try {
|
|
52
|
-
return gcm(key, counterToNonce(frame.counter)).decrypt(hexToBytes(frame.ciphertext));
|
|
61
|
+
return gcm(key, counterToNonce(frame.counter), aad).decrypt(hexToBytes(frame.ciphertext));
|
|
53
62
|
}
|
|
54
63
|
catch {
|
|
55
64
|
throw new FrameDecryptionError();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frameCipher.js","sourceRoot":"","sources":["../../src/crypto/frameCipher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEhE,yEAAyE;AACzE,6EAA6E;AAC7E,2EAA2E;AAC3E,4EAA4E;AAC5E,yEAAyE;AACzE,4EAA4E;AAC5E,4EAA4E;AAC5E,4EAA4E;AAC5E,wEAAwE;AACxE,gBAAgB;AAEhB,MAAM,YAAY,GAAG,EAAE,CAAC;AACxB,MAAM,aAAa,GAAG,CAAC,CAAC;AAExB,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC7C;QACE,KAAK,CAAC,+FAA+F,CAAC,CAAC;QACvG,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AASD,0IAA0I;AAC1I,SAAS,cAAc,CAAC,OAAe;IACrC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,qDAAqD,OAAO,GAAG,CAAC,CAAC;IACnF,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC;IAC3C,IAAI,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC5B,KAAK,IAAI,CAAC,GAAG,YAAY,GAAG,CAAC,EAAE,CAAC,IAAI,YAAY,GAAG,aAAa,EAAE,CAAC,EAAE,EAAE,CAAC;QACtE,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC;QACjC,KAAK,KAAK,EAAE,CAAC;IACf,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"frameCipher.js","sourceRoot":"","sources":["../../src/crypto/frameCipher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEhE,yEAAyE;AACzE,6EAA6E;AAC7E,2EAA2E;AAC3E,4EAA4E;AAC5E,yEAAyE;AACzE,4EAA4E;AAC5E,4EAA4E;AAC5E,4EAA4E;AAC5E,wEAAwE;AACxE,gBAAgB;AAEhB,MAAM,YAAY,GAAG,EAAE,CAAC;AACxB,MAAM,aAAa,GAAG,CAAC,CAAC;AAExB,MAAM,OAAO,oBAAqB,SAAQ,KAAK;IAC7C;QACE,KAAK,CAAC,+FAA+F,CAAC,CAAC;QACvG,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AASD,0IAA0I;AAC1I,SAAS,cAAc,CAAC,OAAe;IACrC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,qDAAqD,OAAO,GAAG,CAAC,CAAC;IACnF,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC;IAC3C,IAAI,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC5B,KAAK,IAAI,CAAC,GAAG,YAAY,GAAG,CAAC,EAAE,CAAC,IAAI,YAAY,GAAG,aAAa,EAAE,CAAC,EAAE,EAAE,CAAC;QACtE,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC;QACjC,KAAK,KAAK,EAAE,CAAC;IACf,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,YAAY,CAAC,GAAe,EAAE,OAAe,EAAE,SAAqB,EAAE,GAAgB;IACpG,MAAM,UAAU,GAAG,GAAG,CAAC,GAAG,EAAE,cAAc,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC7E,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;AACzD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,GAAe,EAAE,KAAqB,EAAE,GAAgB;IACnF,IAAI,CAAC;QACH,OAAO,GAAG,CAAC,GAAG,EAAE,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC;IAC5F,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,oBAAoB,EAAE,CAAC;IACnC,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED