simple-hpke 0.0.11 → 0.1.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 +26 -25
- package/dist/constants.cjs +61 -41
- package/dist/constants.cjs.map +3 -3
- package/dist/constants.js +61 -41
- package/dist/constants.js.map +3 -3
- package/dist/constants.min.cjs +1 -1
- package/dist/constants.min.cjs.map +3 -3
- package/dist/constants.min.js +1 -1
- package/dist/constants.min.js.map +3 -3
- package/dist/index.cjs +62 -41
- package/dist/index.cjs.map +3 -3
- package/dist/index.d.ts +46 -41
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +62 -41
- package/dist/index.js.map +3 -3
- package/dist/index.min.cjs +1 -1
- package/dist/index.min.cjs.map +3 -3
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +3 -3
- package/dist/meta.json +15 -14
- package/dist/util.cjs +61 -41
- package/dist/util.cjs.map +3 -3
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +61 -41
- package/dist/util.js.map +3 -3
- package/dist/util.min.cjs +1 -1
- package/dist/util.min.cjs.map +3 -3
- package/dist/util.min.js +1 -1
- package/dist/util.min.js.map +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Simple HPKE
|
|
2
|
-
[](https://github.com/vanishing-page/simple-hpke/actions/workflows/nodejs.yml)
|
|
3
3
|
[](README.md)
|
|
4
4
|
[](README.md)
|
|
5
5
|
[](https://semver.org/)
|
|
@@ -54,7 +54,7 @@ Wrap an AES key, or encrypt a message.
|
|
|
54
54
|
Encrypt an AES key to yourself, then recover it later.
|
|
55
55
|
|
|
56
56
|
```ts
|
|
57
|
-
import { seal, open } from 'simple-hpke'
|
|
57
|
+
import { create, seal, open } from 'simple-hpke'
|
|
58
58
|
|
|
59
59
|
// An X25519 keypair. (asymmetric keypair).
|
|
60
60
|
// The private key can be non-extractable.
|
|
@@ -65,8 +65,8 @@ const keypair = await crypto.subtle.generateKey(
|
|
|
65
65
|
['deriveBits']
|
|
66
66
|
)
|
|
67
67
|
|
|
68
|
-
//
|
|
69
|
-
const {
|
|
68
|
+
// Create a new AES key and encrypt it to your public key.
|
|
69
|
+
const { enc, key } = await create(keypair)
|
|
70
70
|
|
|
71
71
|
// Or wrap an existing AES key. The supplied AES key must be extractable.
|
|
72
72
|
const aesKey = await crypto.subtle.generateKey(
|
|
@@ -75,11 +75,11 @@ const aesKey = await crypto.subtle.generateKey(
|
|
|
75
75
|
['encrypt', 'decrypt']
|
|
76
76
|
)
|
|
77
77
|
|
|
78
|
-
//
|
|
79
|
-
const {
|
|
78
|
+
// Wrap an existing key.
|
|
79
|
+
const { enc: sealedKey } = await seal(keypair, aesKey)
|
|
80
80
|
|
|
81
81
|
// Later, recover the same key with your private key.
|
|
82
|
-
const recoveredKey = await open(keypair,
|
|
82
|
+
const recoveredKey = await open(keypair, sealedKey)
|
|
83
83
|
|
|
84
84
|
// `recoveredKey` is equal to `aesKey`
|
|
85
85
|
```
|
|
@@ -98,7 +98,7 @@ along with the IV. The recipient uses their private key to open the AES key and
|
|
|
98
98
|
decrypt the message.
|
|
99
99
|
|
|
100
100
|
```ts
|
|
101
|
-
import {
|
|
101
|
+
import { create, open } from 'simple-hpke'
|
|
102
102
|
|
|
103
103
|
const recipient = await crypto.subtle.generateKey(
|
|
104
104
|
{ name: 'X25519' },
|
|
@@ -106,31 +106,31 @@ const recipient = await crypto.subtle.generateKey(
|
|
|
106
106
|
['deriveBits']
|
|
107
107
|
)
|
|
108
108
|
|
|
109
|
-
// Create
|
|
110
|
-
const {
|
|
109
|
+
// Create a fresh AES-GCM key, and encrypt a message with it.
|
|
110
|
+
const { enc, key } = await create(recipient)
|
|
111
111
|
const iv = crypto.getRandomValues(new Uint8Array(12))
|
|
112
|
-
const
|
|
112
|
+
const ciphertextBuffer = await crypto.subtle.encrypt(
|
|
113
113
|
{ name: 'AES-GCM', iv },
|
|
114
114
|
key,
|
|
115
115
|
new TextEncoder().encode('attack at dawn')
|
|
116
116
|
)
|
|
117
117
|
|
|
118
|
-
// Send `
|
|
118
|
+
// Send `enc`, `iv`, and `ciphertext` together. `enc` is a fixed
|
|
119
119
|
// 80 bytes for this suite, and the AES-GCM IV is 12 bytes, so the recipient
|
|
120
120
|
// can slice the payload back apart at known offsets.
|
|
121
|
-
const ciphertext = new Uint8Array(
|
|
122
|
-
const message = new Uint8Array(
|
|
123
|
-
message.set(
|
|
124
|
-
message.set(iv,
|
|
125
|
-
message.set(ciphertext,
|
|
121
|
+
const ciphertext = new Uint8Array(ciphertextBuffer)
|
|
122
|
+
const message = new Uint8Array(enc.length + iv.length + ciphertext.length)
|
|
123
|
+
message.set(enc, 0)
|
|
124
|
+
message.set(iv, enc.length)
|
|
125
|
+
message.set(ciphertext, enc.length + iv.length)
|
|
126
126
|
|
|
127
127
|
// On the other side, split the payload back into its parts.
|
|
128
|
-
const
|
|
128
|
+
const enc2 = message.subarray(0, 80)
|
|
129
129
|
const iv2 = message.subarray(80, 80 + 12)
|
|
130
130
|
const ciphertext2 = message.subarray(80 + 12)
|
|
131
131
|
|
|
132
132
|
// Recover the key, then decrypt the message.
|
|
133
|
-
const recovered = await open(recipient,
|
|
133
|
+
const recovered = await open(recipient, enc2)
|
|
134
134
|
const plaintext = await crypto.subtle.decrypt(
|
|
135
135
|
{ name: 'AES-GCM', iv: iv2 },
|
|
136
136
|
recovered,
|
|
@@ -146,7 +146,8 @@ So that was a lot of code to encrypt and decrypt a message...
|
|
|
146
146
|
This package exposes functions `encrypt` and `decrypt` which do the same thing.
|
|
147
147
|
|
|
148
148
|
`encrypt` seals an AES key to the recipient, encrypts the message under
|
|
149
|
-
that key, and returns a single envelope:
|
|
149
|
+
that key, and returns a single envelope:
|
|
150
|
+
`wrappedLen + wrapped + iv + ciphertext`
|
|
150
151
|
(a 2-byte length prefix, the wrapped key, the 12-byte AES-GCM IV, and the
|
|
151
152
|
cipher text). `decrypt` reverses it, returning the plaintext bytes.
|
|
152
153
|
|
|
@@ -156,8 +157,8 @@ cipher text). `decrypt` reverses it, returning the plaintext bytes.
|
|
|
156
157
|
> [`decrypt.fromString`](#decryptfromstring) below for a convenient way
|
|
157
158
|
> to decrypt from a string.
|
|
158
159
|
>
|
|
159
|
-
> See [`encrypt.asString`](#
|
|
160
|
-
>
|
|
160
|
+
> See [`encrypt.asString`](#encryptasstring) for encrypting and encoding to a
|
|
161
|
+
> string.
|
|
161
162
|
>
|
|
162
163
|
|
|
163
164
|
|
|
@@ -249,7 +250,7 @@ bytes to `decrypt` / `decrypt.asString`.
|
|
|
249
250
|
|
|
250
251
|
##### `decrypt`
|
|
251
252
|
|
|
252
|
-
|
|
253
|
+
Decrypt the given data, return a `Uint8Array`.
|
|
253
254
|
|
|
254
255
|
```ts
|
|
255
256
|
async function decrypt (
|
|
@@ -261,7 +262,7 @@ async function decrypt (
|
|
|
261
262
|
|
|
262
263
|
##### `decrypt.asString`
|
|
263
264
|
|
|
264
|
-
Take a `Uint8Array`, return a
|
|
265
|
+
Take a `Uint8Array`, return a string.
|
|
265
266
|
|
|
266
267
|
```ts
|
|
267
268
|
decrypt.asString = async function decryptToString (
|
|
@@ -293,7 +294,7 @@ This exposes ESM and common JS via
|
|
|
293
294
|
|
|
294
295
|
### ESM
|
|
295
296
|
```js
|
|
296
|
-
import { seal, open, encrypt, decrypt } from 'simple-hpke'
|
|
297
|
+
import { create, seal, open, encrypt, decrypt } from 'simple-hpke'
|
|
297
298
|
```
|
|
298
299
|
|
|
299
300
|
### Common JS
|
package/dist/constants.cjs
CHANGED
|
@@ -1317,51 +1317,19 @@ __name(toString2, "toString");
|
|
|
1317
1317
|
|
|
1318
1318
|
// src/index.ts
|
|
1319
1319
|
var subtle = globalThis.crypto.subtle;
|
|
1320
|
+
async function create2(recipient, opts) {
|
|
1321
|
+
const { wrapped, key } = await encryptKey(recipient, null, opts);
|
|
1322
|
+
return { enc: wrapped, key };
|
|
1323
|
+
}
|
|
1324
|
+
__name(create2, "create");
|
|
1320
1325
|
async function seal(recipient, aesKey, opts) {
|
|
1321
|
-
const
|
|
1322
|
-
|
|
1323
|
-
if (aesKey instanceof Uint8Array) {
|
|
1324
|
-
validateRawKeyBytes(aesKey);
|
|
1325
|
-
keyBytes = aesKey;
|
|
1326
|
-
} else if (aesKey) {
|
|
1327
|
-
keyBytes = await exportAesKeyBytes(aesKey);
|
|
1328
|
-
validateRawKeyBytes(keyBytes);
|
|
1329
|
-
} else {
|
|
1330
|
-
const keysize = opts?.keysize ?? 256;
|
|
1331
|
-
validateKeysize(keysize);
|
|
1332
|
-
keyBytes = globalThis.crypto.getRandomValues(
|
|
1333
|
-
new Uint8Array(keysize / 8)
|
|
1334
|
-
);
|
|
1335
|
-
}
|
|
1336
|
-
const publicKey = await resolveRecipientPublicKey(recipient);
|
|
1337
|
-
const { sharedSecret, enc } = await encap(publicKey);
|
|
1338
|
-
const { key, baseNonce } = await keySchedule(sharedSecret, info);
|
|
1339
|
-
const ciphertext = await aeadSeal(key, baseNonce, keyBytes);
|
|
1340
|
-
const wrapped = concat2(enc, ciphertext);
|
|
1341
|
-
const aesGcmKey = await importAesKey(keyBytes);
|
|
1342
|
-
return { wrapped, key: aesGcmKey };
|
|
1326
|
+
const { wrapped, key } = await encryptKey(recipient, aesKey, opts);
|
|
1327
|
+
return { enc: wrapped, key };
|
|
1343
1328
|
}
|
|
1344
1329
|
__name(seal, "seal");
|
|
1345
1330
|
var open = Object.assign(openBytes, {
|
|
1346
1331
|
raw: openRawBytes
|
|
1347
1332
|
});
|
|
1348
|
-
async function openBytes(keypair, wrapped, opts) {
|
|
1349
|
-
const keyBytes = await openRawBytes(keypair, wrapped, opts);
|
|
1350
|
-
return importAesKey(keyBytes);
|
|
1351
|
-
}
|
|
1352
|
-
__name(openBytes, "openBytes");
|
|
1353
|
-
async function openRawBytes(keypair, wrapped, opts) {
|
|
1354
|
-
if (wrapped.byteLength < ENC_LENGTH + AEAD_TAG_LENGTH) {
|
|
1355
|
-
throw new Error("malformed envelope: too short");
|
|
1356
|
-
}
|
|
1357
|
-
const info = normalizeInfo(opts?.info);
|
|
1358
|
-
const enc = wrapped.slice(0, ENC_LENGTH);
|
|
1359
|
-
const ciphertext = wrapped.slice(ENC_LENGTH);
|
|
1360
|
-
const sharedSecret = await decap(enc, keypair);
|
|
1361
|
-
const { key, baseNonce } = await keySchedule(sharedSecret, info);
|
|
1362
|
-
return aeadOpen(key, baseNonce, ciphertext);
|
|
1363
|
-
}
|
|
1364
|
-
__name(openRawBytes, "openRawBytes");
|
|
1365
1333
|
var encrypt = Object.assign(encryptBytes, {
|
|
1366
1334
|
asString: encryptToString
|
|
1367
1335
|
});
|
|
@@ -1500,6 +1468,48 @@ async function keySchedule(sharedSecret, info) {
|
|
|
1500
1468
|
return { key, baseNonce };
|
|
1501
1469
|
}
|
|
1502
1470
|
__name(keySchedule, "keySchedule");
|
|
1471
|
+
async function openRawBytes(keypair, wrapped, opts) {
|
|
1472
|
+
if (wrapped.byteLength < ENC_LENGTH + AEAD_TAG_LENGTH) {
|
|
1473
|
+
throw new Error("malformed envelope: too short");
|
|
1474
|
+
}
|
|
1475
|
+
const info = normalizeInfo(opts?.info);
|
|
1476
|
+
const enc = wrapped.slice(0, ENC_LENGTH);
|
|
1477
|
+
const ciphertext = wrapped.slice(ENC_LENGTH);
|
|
1478
|
+
const sharedSecret = await decap(enc, keypair);
|
|
1479
|
+
const { key, baseNonce } = await keySchedule(sharedSecret, info);
|
|
1480
|
+
return aeadOpen(key, baseNonce, ciphertext);
|
|
1481
|
+
}
|
|
1482
|
+
__name(openRawBytes, "openRawBytes");
|
|
1483
|
+
async function openBytes(keypair, wrapped, opts) {
|
|
1484
|
+
const keyBytes = await openRawBytes(keypair, wrapped, opts);
|
|
1485
|
+
return importAesKey(keyBytes);
|
|
1486
|
+
}
|
|
1487
|
+
__name(openBytes, "openBytes");
|
|
1488
|
+
async function encryptKey(recipient, aesKey, opts) {
|
|
1489
|
+
const info = normalizeInfo(opts?.info);
|
|
1490
|
+
let keyBytes;
|
|
1491
|
+
if (aesKey instanceof Uint8Array) {
|
|
1492
|
+
validateRawKeyBytes(aesKey);
|
|
1493
|
+
keyBytes = aesKey;
|
|
1494
|
+
} else if (aesKey) {
|
|
1495
|
+
keyBytes = await exportAesKeyBytes(aesKey);
|
|
1496
|
+
validateRawKeyBytes(keyBytes);
|
|
1497
|
+
} else {
|
|
1498
|
+
const keysize = opts?.size ?? 256;
|
|
1499
|
+
validateKeysize(keysize);
|
|
1500
|
+
keyBytes = globalThis.crypto.getRandomValues(
|
|
1501
|
+
new Uint8Array(keysize / 8)
|
|
1502
|
+
);
|
|
1503
|
+
}
|
|
1504
|
+
const publicKey = await resolveRecipientPublicKey(recipient);
|
|
1505
|
+
const { sharedSecret, enc } = await encap(publicKey);
|
|
1506
|
+
const { key, baseNonce } = await keySchedule(sharedSecret, info);
|
|
1507
|
+
const ciphertext = await aeadSeal(key, baseNonce, keyBytes);
|
|
1508
|
+
const wrapped = concat2(enc, ciphertext);
|
|
1509
|
+
const aesGcmKey = await importAesKey(keyBytes);
|
|
1510
|
+
return { wrapped, key: aesGcmKey };
|
|
1511
|
+
}
|
|
1512
|
+
__name(encryptKey, "encryptKey");
|
|
1503
1513
|
|
|
1504
1514
|
// src/util.ts
|
|
1505
1515
|
var subtle2 = globalThis.crypto.subtle;
|
|
@@ -1537,14 +1547,24 @@ async function aeadOpen(key, nonce, ciphertext) {
|
|
|
1537
1547
|
__name(aeadOpen, "aeadOpen");
|
|
1538
1548
|
async function encryptBytes(recipient, message, aesKey, opts) {
|
|
1539
1549
|
const plaintext = typeof message === "string" ? new TextEncoder().encode(message) : message;
|
|
1540
|
-
|
|
1550
|
+
let enc;
|
|
1551
|
+
let key;
|
|
1552
|
+
if (!aesKey) {
|
|
1553
|
+
const keys = await create2(recipient, opts);
|
|
1554
|
+
enc = keys.enc;
|
|
1555
|
+
key = keys.key;
|
|
1556
|
+
} else {
|
|
1557
|
+
const keys = await seal(recipient, aesKey, opts);
|
|
1558
|
+
enc = keys.enc;
|
|
1559
|
+
key = keys.key;
|
|
1560
|
+
}
|
|
1541
1561
|
const iv = globalThis.crypto.getRandomValues(new Uint8Array(NN));
|
|
1542
1562
|
const ct = new Uint8Array(await subtle2.encrypt(
|
|
1543
1563
|
{ name: "AES-GCM", iv },
|
|
1544
1564
|
key,
|
|
1545
1565
|
plaintext
|
|
1546
1566
|
));
|
|
1547
|
-
return concat2(i2osp(
|
|
1567
|
+
return concat2(i2osp(enc.length, WRAPPED_LEN_PREFIX), enc, iv, ct);
|
|
1548
1568
|
}
|
|
1549
1569
|
__name(encryptBytes, "encryptBytes");
|
|
1550
1570
|
async function encryptToString(recipient, message, aesKey, opts) {
|