1.2.7 • Published 3 months ago

hpke-js v1.2.7

Weekly downloads
-
License
MIT
Repository
github
Last release
3 months ago

deno doc Browser CI Node.js CI Deno CI Cloudflare Workers CI bun CI codecov

Documentation: deno.land | pages (only for the latest ver.)

For Node.js, you can install hpke-js via npm/yarn:

npm install @hpke/core
# if necessary...
npm install @hpke/dhkem-x25519
npm install @hpke/dhkem-x448
npm install @hpke/chacha20poly1305
# ...or you can use the v0.x-compatible all-in-one package below.
npm install hpke-js

Then, you can use it as follows:

import { AeadId, CipherSuite, KdfId, KemId } from "hpke-js";
// const { AeadId, CipherSuite, KdfId, KemId } = require("hpke-js");
// import {
//   Aes128Gcm, CipherSuite, DhkemP256HkdfSha256, HkdfSha256,
// } from "@hpke/core";

async function doHpke() {
  const suite = new CipherSuite({
    kem: KemId.DhkemP256HkdfSha256,
    kdf: KdfId.HkdfSha256,
    aead: AeadId.Aes128Gcm,
  });
  // When using "@hpke/core", specify the cryptographic algorithm instances
  // as follows, instead of identities above.
  // const suite = new CipherSuite({
  //   kem: new DhkemP256HkdfSha256(),
  //   kdf: new HkdfSha256(),
  //   aead: new Aes128Gcm(),
  // });

  // A recipient generates a key pair.
  const rkp = await suite.kem.generateKeyPair();

  // A sender encrypts a message with the recipient public key.
  const sender = await suite.createSenderContext({
    recipientPublicKey: rkp.publicKey,
  });
  const ct = await sender.seal(new TextEncoder().encode("Hello world!"));

  // The recipient decrypts it.
  const recipient = await suite.createRecipientContext({
    recipientKey: rkp.privateKey,
    enc: sender.enc,
  });
  const pt = await recipient.open(ct);

  // Hello world!
  console.log(new TextDecoder().decode(pt));
}

try {
  doHpke();
} catch (e) {
  console.log("failed:", e.message);
}

Index

Packages

The hpke-js includes the following packages.

namesincedescription
hpke-jsv0.1.0-The HPKE module supporting all of the ciphersuites defined in RFC9180, which consists of the following @hpke/{core, dhkem-x25519, dhkem-x448, chacha20poly1305} internally.
@hpke/corev1.0.0-The HPKE core module implemented using only Web Cryptography API. It does not support the X25519/X448-based KEMs and the ChaCha20/Poly1305 AEAD, but it has no external module dependencies. It's small in size and tree-shaking friendly. See /core/README.
@hpke/chacha20poly1305v1.0.0-The HPKE module extension for ChaCha20Poly1305 AEAD. See /x/chacha20poly1305/README.
@hpke/dhkem-x25519v1.0.0-The HPKE module extension for DHKEM(X25519, HKDF-SHA256). See /x/dhkem-x25519/README.
@hpke/dhkem-x448v1.0.0-The HPKE module extension for DHKEM(X448, HKDF-SHA512). See /x/dhkem-x448/README.
@hpke/hybridkem-x25519-kyber768v1.2.1-EXPERIMENTAL AND NOT STANDARDIZED The HPKE module extension for the hybrid post-quantum KEM currently named X25519Kyber768Draft00. See /x/hybridkem-x25519-kyber768/README.
@hpke/dhkem-secp256k1v1.0.0-EXPERIMENTAL AND NOT STANDARDIZED The HPKE module extension for DHKEM(secp256k1, HKDF-SHA256). See /x/dhkem-secp256k1/README.

Supported Features

HPKE Modes

BasePSKAuthAuthPSK

Key Encapsulation Machanisms (KEMs)

KEMsBrowserNode.jsDenoCloudflareWorkersbun
DHKEM (P-256, HKDF-SHA256)hpke-js@hpke/corehpke-js@hpke/corehpke-js@hpke/corehpke-js@hpke/corehpke-js@hpke/core
DHKEM (P-384, HKDF-SHA384)hpke-js@hpke/corehpke-js@hpke/corehpke-js@hpke/corehpke-js@hpke/corehpke-js@hpke/core
DHKEM (P-521, HKDF-SHA512)hpke-js@hpke/corehpke-js@hpke/corehpke-js@hpke/corehpke-js@hpke/core
DHKEM (X25519, HKDF-SHA256)hpke-js@hpke/dhkem-x25519hpke-js@hpke/dhkem-x25519hpke-js@hpke/dhkem-x25519hpke-js@hpke/dhkem-x25519hpke-js@hpke/dhkem-x25519
DHKEM (X448, HKDF-SHA512)hpke-js@hpke/dhkem-x448hpke-js@hpke/dhkem-x448hpke-js@hpke/dhkem-x448hpke-js@hpke/dhkem-x448hpke-js@hpke/dhkem-x448
Hybrid KEM (X25519, Kyber768)@hpke/hybridkem-x25519-kyber768@hpke/hybridkem-x25519-kyber768@hpke/hybridkem-x25519-kyber768@hpke/hybridkem-x25519-kyber768@hpke/hybridkem-x25519-kyber768
DHKEM (secp256k1, HKDF-SHA256)@hpke/dhkem-secp256k1@hpke/dhkem-secp256k1@hpke/dhkem-secp256k1@hpke/dhkem-secp256k1@hpke/dhkem-secp256k1

Key Derivation Functions (KDFs)

KDFsBrowserNode.jsDenoCloudflareWorkersbun
HKDF-SHA256hpke-js@hpke/core(*1)hpke-js@hpke/core(*1)hpke-js@hpke/core(*1)hpke-js@hpke/core(*1)hpke-js@hpke/core(*1)
HKDF-SHA384hpke-js@hpke/core(*1)hpke-js@hpke/core(*1)hpke-js@hpke/core(*1)hpke-js@hpke/core(*1)hpke-js@hpke/core(*1)
HKDF-SHA512hpke-js@hpke/core(*1)hpke-js@hpke/core(*1)hpke-js@hpke/core(*1)hpke-js@hpke/core(*1)hpke-js@hpke/core(*1)
  • (*1) The HKDF functions built in @hpke/core can derive keys of the same length as the hash size. If you want to derive keys longer than the hash size, use hpke-js.

Authenticated Encryption with Associated Data (AEAD) Functions

AEADsBrowserNode.jsDenoCloudflareWorkersbun
AES-128-GCMhpke-js@hpke/corehpke-js@hpke/corehpke-js@hpke/corehpke-js@hpke/corehpke-js@hpke/core
AES-256-GCMhpke-js@hpke/corehpke-js@hpke/corehpke-js@hpke/corehpke-js@hpke/corehpke-js@hpke/core
ChaCha20Poly1305hpke-js@hpke/chacha20poly1305hpke-js@hpke/chacha20poly1305hpke-js@hpke/chacha20poly1305hpke-js@hpke/chacha20poly1305hpke-js@hpke/chacha20poly1305
Export Onlyhpke-js@hpke/corehpke-js@hpke/corehpke-js@hpke/corehpke-js@hpke/corehpke-js@hpke/core

Supported Environments

  • Web Browser: Web Cryptography API supported browsers
    • Confirmed: Chrome, Firefox, Edge, Safari, Opera, Vivaldi, Brave
  • Node.js: 16.x, 17.x, 18.x, 19.x, 20.x
  • Deno: 1.x (1.25-)
  • Cloudflare Workers
  • bun: 0.x (0.6.0-), 1.x

Warnings and Restrictions

Installation

Node.js

Using npm:

npm install @hpke/core
# if necessary...
npm install @hpke/dhkem-x25519
npm install @hpke/dhkem-x448
npm install @hpke/chacha20poly1305
# ...or you can use the v0.x-compatible all-in-one package below.
npm install hpke-js

Using yarn:

yarn add @hpke/core
# if necessary...
yarn add @hpke/dhkem-x25519
yarn add @hpke/dhkem-x448
yarn add @hpke/chacha20poly1305
# ...or you can use the v0.x-compatible all-in-one package below.
yarn add hpke-js

Deno

Using deno.land:

// use a specific version
import * as hpke from "https://deno.land/x/hpke@1.2.7/mod.ts";
// import * as hpke from "https://deno.land/x/hpke@1.2.7/core/mod.ts";
// import * as hpke from "https://deno.land/x/hpke@1.2.7/x/dhkem-x25519/mod.ts";

// use the latest stable version
import * as hpke from "https://deno.land/x/hpke/mod.ts";
// import * as hpke from "https://deno.land/x/hpke/core/mod.ts";
// import * as hpke from "https://deno.land/x/hpke/x/dhkem-x25519/mod.ts";

Web Browsers

Followings are how to use the module with typical CDNs. Other CDNs can be used as well.

Using esm.sh:

<!-- use a specific version -->
<script type="module">
  import * as hpke from "https://esm.sh/hpke-js@1.2.7";
  // import * as hpke from "https://esm.sh/@hpke/core@1.2.7";
  // ...
</script>

<!-- use the latest stable version -->
<script type="module">
  import * as hpke from "https://esm.sh/hpke-js";
  // import * as hpke from "https://esm.sh/@hpke/core";
  // ...
</script>

Using unpkg:

<!-- use a specific version -->
<script type="module">
  import * as hpke from "https://unpkg.com/hpke-js@1.2.7/esm/mod.js";
  // import * as hpke from "https://unpkg.com/@hpke/core@1.2.7/esm/mod.js";
  // ...
</script>

Cloudflare Workers

git clone git@github.com:dajiaji/hpke-js.git
cd hpke-js
# for hpke-js
npm install -g esbuild
deno task dnt
deno task minify > $YOUR_SRC_PATH/hpke.js

# for @hpke/core
cd hpke-js/core
npm install -g esbuild
deno task dnt
deno task minify > $YOUR_SRC_PATH/hpke-core.js

# for @hpke/dhkem-x25519
cd hpke-js/x/dhkem-x25519
npm install -g esbuild
deno task dnt
deno task minify > $YOUR_SRC_PATH/hpke-dhkem-x25519.js

Usage

This section shows some typical usage examples.

Base mode

Node.js:

import { AeadId, CipherSuite, KdfId, KemId } from "hpke-js";
// const { AeadId, CipherSuite, KdfId, KemId } = require("hpke-js");
// import {
//   Aes128Gcm, CipherSuite, DhkemP256HkdfSha256, HkdfSha256,
// } from "@hpke/core";

async function doHpke() {
  const suite = new CipherSuite({
    kem: KemId.DhkemP256HkdfSha256,
    kdf: KdfId.HkdfSha256,
    aead: AeadId.Aes128Gcm,
  });

  // A recipient generates a key pair.
  const rkp = await suite.kem.generateKeyPair();

  // A sender encrypts a message with the recipient public key.
  const sender = await suite.createSenderContext({
    recipientPublicKey: rkp.publicKey,
  });
  const ct = await sender.seal(new TextEncoder().encode("Hello world!"));

  // The recipient decrypts it.
  const recipient = await suite.createRecipientContext({
    recipientKey: rkp.privateKey,
    enc: sender.enc,
  });
  const pt = await recipient.open(ct);

  // Hello world!
  console.log("decrypted: ", new TextDecoder().decode(pt));
}

try {
  doHpke();
} catch (e) {
  console.log("failed:", e.message);
}

Deno:

import { AeadId, CipherSuite, KdfId, KemId } from "https://deno.land/x/hpke@1.2.7/mod.ts";
// import {
//   Aes128Gcm, CipherSuite, HkdfSha256,
// } from "https://deno.land/x/hpke@1.2.7/core/mod.ts";
// import { DhkemX25519HkdfSha256 } from "https://deno.land/x/hpke@1.2.7/x/dhkem-x25519/mod.ts";

async function doHpke() {
  // setup
  const suite = new CipherSuite({
    // When using "https://deno.land/x/hpke/mod.ts" (not "/x/hpke/core/mod.ts"),
    // you can specify the identifier as follows:
    kem: KemId.DhkemX25519HkdfSha256,
    kdf: KdfId.HkdfSha256,
    aead: AeadId.Aes128Gcm,
  });
  //// When using hpke/core/mod.ts, specify the instances as follows:
  //const suite = new CipherSuite({
  //  kem: new DhkemX25519HkdfSha256(),
  //  kdf: new HkdfSha256(),
  //  aead: new Aes128Gcm(),
  //});

  const rkp = await suite.kem.generateKeyPair();

  const sender = await suite.createSenderContext({
    recipientPublicKey: rkp.publicKey,
  });

  // A JWK-formatted recipient public key can also be used.
  // const jwkPkR = {
  //   kty: "EC",
  //   crv: "P-256",
  //   kid: "P-256-01",
  //   x: "-eZXC6nV-xgthy8zZMCN8pcYSeE2XfWWqckA2fsxHPc",
  //   y: "BGU5soLgsu_y7GN2I3EPUXS9EZ7Sw0qif-V70JtInFI",
  //   key_ops: [],
  // };
  // const pkR = await suite.kem.importKey("jwk", jwkPkR, true);
  // const sender = await suite.createSenderContext({
  //   recipientPublicKey: pkR,
  // });

  // encrypt
  const ct = await sender.seal(new TextEncoder().encode("Hello world!"));

  const recipient = await suite.createRecipientContext({
    recipientKey: rkp.privateKey,
    enc: sender.enc,
  });

  // A JWK-formatted recipient private key can also be used.
  // const jwkSkR = {
  //   kty: "EC",
  //   crv: "P-256",
  //   kid: "P-256-01",
  //   x: "-eZXC6nV-xgthy8zZMCN8pcYSeE2XfWWqckA2fsxHPc",
  //   y: "BGU5soLgsu_y7GN2I3EPUXS9EZ7Sw0qif-V70JtInFI",
  //   d: "kwibx3gas6Kz1V2fyQHKSnr-ybflddSjN0eOnbmLmyo",
  //   key_ops: ["deriveBits"],
  // };
  // const skR = await suite.kem.importKey("jwk", jwkSkR, false);
  // const recipient = await suite.createRecipientContext({
  //   recipientKey: skR,
  //   enc: sender.enc,
  // });

  // decrypt
  const pt = await recipient.open(ct);

  // Hello world!
  console.log(new TextDecoder().decode(pt));
}

try {
  doHpke();
} catch (_err: unknown) {
  console.log("failed.");
}

Browsers:

<html>
  <head></head>
  <body>
    <script type="module">
      import { AeadId, CipherSuite, KdfId, KemId } from "https://esm.sh/hpke-js@1.2.7";
      // import {
      //   Aes128Gcm, CipherSuite, DhkemP256HkdfSha256, HkdfSha256,
      // } from "@hpke/core@1.2.7";

      globalThis.doHpke = async () => {
        try {
          const suite = new CipherSuite({
            kem: KemId.DhkemP256HkdfSha256,
            kdf: KdfId.HkdfSha256,
            aead: AeadId.Aes128Gcm
          });
 
          const rkp = await suite.kem.generateKeyPair();
      
          const sender = await suite.createSenderContext({
            recipientPublicKey: rkp.publicKey
          });
      
          // encrypt
          const ct = await sender.seal(new TextEncoder().encode("Hello world!"));


          const recipient = await suite.createRecipientContext({
            recipientKey: rkp.privateKey, // rkp (CryptoKeyPair) is also acceptable.
            enc: sender.enc,
          });

          // decrypt
          const pt = await recipient.open(ct);

          // Hello world!
          alert(new TextDecoder().decode(pt));
        } catch (err) {
          alert("failed:", err.message);
        }
      }
      
    </script>
    <button type="button" onclick="doHpke()">do HPKE</button>
  </body>
</html>

Base mode with Single-Shot APIs

Node.js:

import { AeadId, CipherSuite, KdfId, KemId } from "hpke-js";
// const { AeadId, CipherSuite, KdfId, KemId } = require("hpke-js");
// import {
//   Aes128Gcm, CipherSuite, DhkemP256HkdfSha256, HkdfSha256,
// } from "@hpke/core";

async function doHpke() {

  // setup
  const suite = new CipherSuite({
    kem: KemId.DhkemP256HkdfSha256,
    kdf: KdfId.HkdfSha256,
    aead: AeadId.Aes128Gcm
  });

  const rkp = await suite.kem.generateKeyPair();
  const pt = new TextEncoder().encode('Hello world!'),

  // encrypt
  const { ct, enc } = await suite.seal({ recipientPublicKey: rkp.publicKey }, pt);

  // decrypt
  const pt = await suite.open({ recipientKey: rkp.privateKey, enc: enc }, ct);

  // Hello world!
  console.log(new TextDecoder().decode(pt));
}

try {
  doHpke();
} catch (err) {
  console.log("failed:", err.message);
}

Base mode with export-only AEAD

Node.js:

import { AeadId, CipherSuite, KdfId, KemId } from "hpke-js";
// const { AeadId, CipherSuite, KdfId, KemId } = require("hpke-js");
// import {
//   CipherSuite, DhkemP256HkdfSha256, ExportOnly, HkdfSha256,
// } from "@hpke/core";

async function doHpke() {
  // setup
  const suite = new CipherSuite({
    kem: KemId.DhkemP256HkdfSha256,
    kdf: KdfId.HkdfSha256,
    aead: AeadId.ExportOnly,
  });
  // When using "@hpke/core",
  const suite = new CipherSuite({
    kem: new DhkemP256HkdfSha256(),
    kdf: new HkdfSha256(),
    aead: new ExportOnly(),
  });

  const rkp = await suite.kem.generateKeyPair();

  const sender = await suite.createSenderContext({
    recipientPublicKey: rkp.publicKey,
  });

  const recipient = await suite.createRecipientContext({
    recipientKey: rkp.privateKey,
    enc: sender.enc,
  });

  const te = new TextEncoder();

  // export
  const pskS = sender.export(te.encode("jugemujugemu"), 32);
  const pskR = recipient.export(te.encode("jugemujugemu"), 32);
  // pskR === pskS
}

try {
  doHpke();
} catch (err) {
  console.log("failed:", err.message);
}

PSK mode

Node.js:

import { AeadId, CipherSuite, KdfId, KemId } from "hpke-js";
// const { AeadId, CipherSuite, KdfId, KemId } = require("hpke-js");
// import {
//   Aes128Gcm, CipherSuite, DhkemP256HkdfSha256, HkdfSha256,
// } from "@hpke/core";

async function doHpke() {
  // setup
  const suite = new CipherSuite({
    kem: KemId.DhkemP256HkdfSha256,
    kdf: KdfId.HkdfSha256,
    aead: AeadId.Aes128Gcm,
  });

  const rkp = await suite.kem.generateKeyPair();

  const sender = await suite.createSenderContext({
    recipientPublicKey: rkp.publicKey,
    psk: {
      id: new TextEncoder().encode("our-pre-shared-key-id"),
      // a PSK MUST have at least 32 bytes.
      key: new TextEncoder().encode("jugemujugemugokounosurikirekaija"),
    },
  });

  // encrypt
  const ct = await sender.seal(new TextEncoder().encode("Hello world!"));

  const recipient = await suite.createRecipientContext({
    recipientKey: rkp.privateKey,
    enc: sender.enc,
    psk: {
      id: new TextEncoder().encode("our-pre-shared-key-id"),
      // a PSK MUST have at least 32 bytes.
      key: new TextEncoder().encode("jugemujugemugokounosurikirekaija"),
    },
  });

  // decrypt
  const pt = await recipient.open(ct);

  // Hello world!
  console.log(new TextDecoder().decode(pt));
}

try {
  doHpke();
} catch (err) {
  console.log("failed:", err.message);
}

Auth mode

Node.js:

import { AeadId, CipherSuite, KdfId, KemId } from "hpke-js";
// const { AeadId, CipherSuite, KdfId, KemId } = require("hpke-js");
// import {
//   Aes128Gcm, CipherSuite, DhkemP256HkdfSha256, HkdfSha256,
// } from "@hpke/core";

async function doHpke() {
  // setup
  const suite = new CipherSuite({
    kem: KemId.DhkemP256HkdfSha256,
    kdf: KdfId.HkdfSha256,
    aead: AeadId.Aes128Gcm,
  });

  const rkp = await suite.kem.generateKeyPair();
  const skp = await suite.kem.generateKeyPair();

  const sender = await suite.createSenderContext({
    recipientPublicKey: rkp.publicKey,
    senderKey: skp,
  });

  // encrypt
  const ct = await sender.seal(new TextEncoder().encode("Hello world!"));

  const recipient = await suite.createRecipientContext({
    recipientKey: rkp.privateKey,
    enc: sender.enc,
    senderPublicKey: skp.publicKey,
  });

  // decrypt
  const pt = await recipient.open(ct);

  // Hello world!
  console.log(new TextDecoder().decode(pt));
}

try {
  doHpke();
} catch (err) {
  console.log("failed:", err.message);
}

AuthPSK mode

Node.js:

import { AeadId, CipherSuite, KdfId, KemId } from "hpke-js";
// const { AeadId, CipherSuite, KdfId, KemId } = require("hpke-js");
// import {
//   Aes128Gcm, CipherSuite, DhkemP256HkdfSha256, HkdfSha256,
// } from "@hpke/core";

async function doHpke() {
  // setup
  const suite = new CipherSuite({
    kem: KemId.DhkemP256HkdfSha256,
    kdf: KdfId.HkdfSha256,
    aead: AeadId.Aes128Gcm,
  });

  const rkp = await suite.kem.generateKeyPair();
  const skp = await suite.kem.generateKeyPair();

  const sender = await suite.createSenderContext({
    recipientPublicKey: rkp.publicKey,
    senderKey: skp,
    psk: {
      id: new TextEncoder().encode("our-pre-shared-key-id"),
      // a PSK MUST have at least 32 bytes.
      key: new TextEncoder().encode("jugemujugemugokounosurikirekaija"),
    },
  });

  // encrypt
  const ct = await sender.seal(new TextEncoder().encode("Hello world!"));

  const recipient = await suite.createRecipientContext({
    recipientKey: rkp.privateKey,
    enc: sender.enc,
    senderPublicKey: skp.publicKey,
    psk: {
      id: new TextEncoder().encode("our-pre-shared-key-id"),
      // a PSK MUST have at least 32 bytes.
      key: new TextEncoder().encode("jugemujugemugokounosurikirekaija"),
    },
  });

  // decrypt
  const pt = await recipient.open(ct);

  // Hello world!
  console.log(new TextDecoder().decode(pt));
}

try {
  doHpke();
} catch (err) {
  console.log("failed:", err.message);
}

Contributing

We welcome all kind of contributions, filing issues, suggesting new features or sending PRs.

References

1.2.7

3 months ago

1.2.6

3 months ago

1.2.5

5 months ago

1.2.4

6 months ago

1.2.3

8 months ago

1.2.0

9 months ago

1.2.2

8 months ago

1.2.1

9 months ago

1.1.1

9 months ago

1.1.0

9 months ago

1.0.2

9 months ago

1.0.1

9 months ago

1.0.0

9 months ago

0.20.0

10 months ago

1.0.4

9 months ago

1.0.3

9 months ago

0.19.0

10 months ago

0.21.0

9 months ago

0.18.5

11 months ago

0.22.2

9 months ago

0.22.1

9 months ago

0.22.0

9 months ago

0.18.3

1 year ago

0.18.1

1 year ago

0.17.2

1 year ago

0.18.2

1 year ago

0.18.0

1 year ago

0.14.0

2 years ago

0.15.0

1 year ago

0.17.1

1 year ago

0.11.0

2 years ago

0.10.1

2 years ago

0.12.0

2 years ago

0.11.1

2 years ago

0.10.2

2 years ago

0.13.0

2 years ago

0.12.1

2 years ago

0.11.2

2 years ago

0.11.3

2 years ago

0.11.4

2 years ago

0.11.5

2 years ago

0.10.0

2 years ago

0.9.0

2 years ago

0.8.0

2 years ago

0.7.1

2 years ago

0.9.1

2 years ago

0.5.0

2 years ago

0.7.0

2 years ago

0.6.0

2 years ago

0.5.1

2 years ago

0.4.1

2 years ago

0.4.0

2 years ago

0.3.1

2 years ago

0.3.0

2 years ago

0.2.4

2 years ago

0.2.3

2 years ago

0.2.2

2 years ago

0.2.1

2 years ago

0.2.0

2 years ago