Skip to content

Alterion-Software/alterion-encrypt-js

Repository files navigation

Alterion Logo

License: GPL-3.0 npm TypeScript ESM + CJS GitHub

The JavaScript/TypeScript frontend/client-side counterpart to alterion-encrypt — X25519 ECDH key exchange, AES-256-GCM session encryption, and a MessagePack + Deflate request/response pipeline, all in a framework-agnostic package.

Frontend only. This package is intended for use in browser environments and client-side runtimes. The server-side implementation lives in the alterion-encrypt Rust crate. Do not use this package as a server-side encryption backend.


What it does

Each request to the server is packaged as a Request:

Client → Request { data: AES-256-GCM ciphertext, kx, client_pk: ephemeral X25519, key_id, ts }

Request path (buildRequestPacket):

  1. JSON-serialise the payload and deflate-compress it.
  2. Generate a random 32-byte AES-256 enc_key per request.
  3. AES-256-GCM encrypt the payload with enc_key.
  4. Generate an ephemeral X25519 key pair, perform ECDH against the server's public key.
  5. Derive a wrap_key via HKDF-SHA256, use it to AES-GCM wrap enc_keykx.
  6. Encode a Request { data, kx, client_pk, key_id, ts } with MessagePack.
  7. Store enc_key client-side keyed by request ID — it is never sent in plaintext.

Response path (decodeResponsePacket):

  1. MessagePack-decode the Response { payload, hmac }.
  2. Derive the HMAC key from enc_key via HKDF-SHA256 ("alterion-response-mac" info label).
  3. Constant-time verify the HMAC — reject if invalid.
  4. AES-256-GCM decrypt payload with enc_key.
  5. MessagePack-decode → deflate-decompress → JSON-parse → typed result.

No second ECDH round-trip is needed for the response; the server re-uses the enc_key it unwrapped from the request. The server side is implemented in alterion-encrypt.


Package layout

alterion-encrypt (JS)
├── serializer   buildRequestPacket / decodeResponsePacket — the main public API
├── crypt        aesEncrypt / aesDecrypt — AES-256-GCM with prepended nonce
├── compress     compress / decompress — deflate-raw via CompressionStream
└── keys         deriveWrapKey / deriveResponseMacKey — HKDF-SHA256 key derivation

Quick start

1. Add the dependency

npm install alterion-encrypt

2. Fetch the server's public key

The server exposes a POST /api/ecdh/init endpoint (from the alterion-encrypt Rust crate) that returns a one-time ephemeral key pair:

const { handshake_id, public_key } = await fetch("/api/ecdh/init", { method: "POST" })
    .then(r => r.json());

const serverPk = Uint8Array.from(atob(public_key), c => c.charCodeAt(0));
const keyId    = handshake_id;

3. Encrypt a request

import { buildRequestPacket } from "alterion-encrypt";

const { wireBytes, encKey } = await buildRequestPacket(
    { username: "alice", action: "login" },
    serverPk,
    keyId,
);

// Store encKey client-side, keyed by request ID, to decrypt the response.
// Send wireBytes as application/octet-stream body.

4. Decrypt the response

import { decodeResponsePacket } from "alterion-encrypt";

const rawResponse = await fetch("/api/example", {
    method:  "POST",
    body:    wireBytes,
    headers: { "Content-Type": "application/octet-stream" },
});
const bytes = new Uint8Array(await rawResponse.arrayBuffer());

const result = await decodeResponsePacket<{ token: string }>(bytes, encKey);
console.log(result.token);

API

buildRequestPacket

function buildRequestPacket(
    value:    unknown,
    serverPk: Uint8Array,
    keyId:    string,
): Promise<{ wireBytes: Uint8Array; encKey: Uint8Array }>

Encrypts value and returns the wire bytes to send and the enc_key to hold client-side.

Parameter Description
value Any JSON-serialisable payload
serverPk Server's 32-byte X25519 public key (base64-decoded from the key endpoint)
keyId Key identifier returned alongside the server's public key

Returns { wireBytes, encKey }. Store encKey indexed by request ID and pass it to decodeResponsePacket when the response arrives.


decodeResponsePacket

function decodeResponsePacket<T = unknown>(
    wireBytes: Uint8Array,
    encKey:    Uint8Array,
): Promise<T>

Verifies and decodes a server response. Throws if the HMAC is invalid or decryption fails.

Parameter Description
wireBytes Raw bytes from the server response body
encKey The AES key returned by the matching buildRequestPacket call

Lower-level exports

import { aesEncrypt, aesDecrypt }         from "alterion-encrypt";
import { compress, decompress }            from "alterion-encrypt";
import { deriveWrapKey, deriveResponseMacKey } from "alterion-encrypt";
Function Description
aesEncrypt(plaintext, key) AES-256-GCM encrypt — 12-byte nonce prepended to output
aesDecrypt(data, key) AES-256-GCM decrypt — reads nonce from first 12 bytes
compress(data) Deflate-raw compress via CompressionStream
decompress(data) Deflate-raw decompress via DecompressionStream
deriveWrapKey(sharedSecret, clientPk, serverPk) HKDF-SHA256 wrap key — salt = clientPk ‖ serverPk, info = "alterion-wrap"
deriveResponseMacKey(encKey) HKDF-SHA256 HMAC key — info = "alterion-response-mac"

Pipelines

Client request (buildRequestPacket)

Any JSON-serialisable value
        │
        ▼
  JSON.stringify → TextEncoder
        │
        ▼
  deflate-raw compress (CompressionStream)
        │
        ▼
  MessagePack encode  ──→  Uint8Array
        │
        ▼
  AES-256-GCM encrypt  (random enc_key — stored client-side by request ID)
        │
        ▼
  Ephemeral X25519 keygen  ──→  ECDH(client_sk, server_pk)  ──→  HKDF-SHA256  ──→  wrap_key
        │
        ▼
  AES-256-GCM wrap enc_key  (wrap_key)  ──→  kx
        │
        ▼
  Request { data, kx, client_pk, key_id, ts }
        │
        ▼
  MessagePack encode  ──→  wire bytes  ──→  sent to server

enc_key is returned to the caller and must be stored client-side (e.g. keyed by request ID). kx lets the server recover enc_key via ECDH without it ever appearing in plaintext on the wire.

Server response (decodeResponsePacket)

wire bytes received from server
        │
        ▼
  MessagePack decode  ──→  Response { payload, hmac }
        │
        ▼
  HKDF-SHA256(enc_key, info="alterion-response-mac")  ──→  mac_key
        │
        ▼
  HMAC-SHA256 verify (mac_key, payload)  ──  reject if invalid
        │
        ▼
  AES-256-GCM decrypt payload  (enc_key)
        │
        ▼
  MessagePack decode  ──→  Uint8Array
        │
        ▼
  deflate-raw decompress (DecompressionStream)
        │
        ▼
  JSON.parse  ──→  T

Compatibility

Requires CompressionStream / DecompressionStream — available in all modern browsers and Node.js 18+.


Contributing

See CONTRIBUTING.md. Open an issue before writing any code.


License

GNU General Public License v3.0 — see LICENSE.


Made with ❤️ by the Alterion Software team

Discord Website GitHub

About

X25519 ECDH + AES-256-GCM + HMAC-SHA256 client-side encryption pipeline — JS/TS counterpart to alterion-encrypt

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors