MainSDK
Search⌘K
Interfaces

Security and privacy

Cryptography

crypto

Seal data, sign and verify, hash, HMAC and make random tokens with modern algorithms only, on WebCrypto with no dependency.

Building for 0.3ships as a beta APIPhase 1 · Core, public betaSpec keysFree in your app, Apache-2.0

Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.

Stability The promise an interface ships with.

  • Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
  • Beta Usable in production; may change before it becomes stable.
  • Experimental May change or go away; needs an explicit opt-in in main.yaml.
Stability levels
Updated 2026-10-28

crypto gives you everyday building blocks: seal and open, sign and verify, hash, HMAC, derive a key and make tokens, with modern algorithms only, on WebCrypto. For records with per-user erasure, use keys.

At a glance

crypto:
  fips: false

Calls

  • crypto.keys.customer(org, { kms })
  • cryptoApi(object: every crypto helper under one name)
  • crypto.token(bytes?)
  • crypto.sign(privateKey, data)
  • crypto.verify(publicKey, { data, signature })
  • crypto.seal(key, data, { aad? })
  • crypto.open(key, sealed, { aad? })
  • crypto.generateKey()
  • crypto.deriveKey(secret, info)
  • crypto.randomBytes(n)
  • crypto.hash(data, alg?)
  • crypto.hashHex(data, alg?)
  • crypto.hmac(key, data, alg?)
  • crypto.verifyHmac(key, data, tag, alg?)
  • crypto.generateSigningKeyPair()
  • crypto.equal(a, b)
  • crypto.b64u(bytes)
  • crypto.fromB64u(text)
  • crypto.toHex(bytes)

Guarantees

Whatever the driver.

  • Modern algorithms only
  • Keys never in code

Drivers

4 drivers. Select one to see its line in main.yaml.

main.yamlWebCrypto (default)
crypto:
  driver: builtin

Change this one line to swap providers; your calls stay the same.Built-in and local drivers integration

Codes

CodeWhere it appearsMeaning
MAIN-CRYPTO-0002Checkmainsdk check, before deployA Main 27 crypto rule, in the family that refuses MD5, SHA-1 and DES
MAIN-CRYPTO-0013Checkmainsdk check, before deploycrypto.fips is on but crypto.module does not name the validated module, its version and certificate number
MAIN-CRYPTO-0001RuntimeError responses and logsSee the spec
MAIN-CRYPTO-0003RuntimeError responses and logsSee the spec
MAIN-CRYPTO-0010RuntimeError responses and logsSee the spec
MAIN-CRYPTO-0011RuntimeError responses and logsSee the spec
MAIN-CRYPTO-0012RuntimeError responses and logsSee the spec

Features

Nothing here has shipped yet. Each feature lands in its phase; the spec ticket builds it.

FeaturePhaseMilestoneSpec ticket
Field encryption, signing and tokensPhase 10.3Not specified yet
Customer-managed keysPhase 31.2hsm-keys#customer-keys
Post-quantum signaturesPhase 51.6post-quantum#pq-signatures
Hybrid post-quantum key exchangePhase 51.6post-quantum#hybrid-kex

Quick start

There is nothing to declare in main.yaml. Import crypto and call it.

app/reset/token.ts

import { crypto } from "mainsdk";

const token = crypto.token();                  // 32 random bytes, 43 URL-safe characters
const stored = await crypto.hashHex(token);    // keep this, never the token

Configuration

None today. crypto has no main.yaml section, and the algorithms are not a free choice: only modern ones are offered. A crypto.driver for a key service such as Vault Transit or a cloud KMS is planned. Until then use keys, which has those drivers.

What you get

The calls are free, run offline on WebCrypto on Node and edge runtimes, and need no dependency. Only modern algorithms are offered, so a weak one cannot be picked by accident. You hold the keys and pass them in; nothing is stored or logged. Reach for it for a single operation such as a token, a hash or a signature. The spec is keys (AC8 to AC13).

Examples

Password reset token

app/reset/actions.ts
import { crypto } from "mainsdk";

export async function startReset() {
  const token = crypto.token();
  const stored = await crypto.hashHex(token);
  // save `stored` with an expiry, then send `token` to the person
  return { token, stored };
}

export async function checkReset(token: string, stored: string) {
  return crypto.equal(await crypto.hashHex(token), stored);   // constant time
}

Store only the hash of the token, with an expiry, never the token itself. crypto.token(bytes) takes 16 or more.

Seal and open data

You hold the key. For keys held in a KMS, with per-user erasure, use keys.

lib/notes.ts
import { crypto } from "mainsdk";

const key = crypto.generateKey();   // 32 bytes; keep it in your secrets driver
const box = await crypto.seal(key, "hello", { aad: "note:1" });
const plain = await crypto.open(key, box, { aad: "note:1" });   // Uint8Array

aad binds the box to a context: opening it with another aad, another key or changed bytes fails with MAIN-CRYPTO-0003. A key that is not 32 bytes fails with MAIN-CRYPTO-0001.

Sign and verify

lib/sign.ts
import { crypto } from "mainsdk";

const pair = await crypto.generateSigningKeyPair();   // ed25519: PKCS#8 private key, 32-byte public key
const signature = await crypto.sign(pair.privateKey, "payload");   // 64 bytes
const ok = await crypto.verify(pair.publicKey, { data: "payload", signature });   // true

verify returns false for a changed message or another key. To keep the private key in a key service, so it never reaches your code, see keys.sign() (planned).

Hash, HMAC and derive a key

lib/hash.ts
import { crypto } from "mainsdk";

const digest = await crypto.hashHex("abc");               // SHA-256; "sha384" and "sha512" are allowed
const secret = crypto.token();   // in an app, read this from your secrets driver
const tag = await crypto.hmac(secret, "body");
const good = await crypto.verifyHmac(secret, "body", tag);   // false on a mismatch, never throws
const subkey = await crypto.deriveKey("a-long-enough-secret", "exports");   // HKDF-SHA-256, 32 bytes

An HMAC key needs 16 bytes or more. MD5, SHA-1 and DES are refused with MAIN-CRYPTO-0002; there is no option to pick them.

The other helpers are crypto.randomBytes(n), crypto.b64u(), crypto.fromB64u() and crypto.toHex().

Use the helpers without the app

cryptoApi is an object, not a function (the catalog lists it as cryptoApi(...)). It is what crypto forwards to: every helper above under one name. Import it when you need these helpers where no app is running, such as a script or a library.

scripts/digest.ts
import { cryptoApi } from "mainsdk";

const digest = await cryptoApi.hashHex("abc");

Planned

  • Password hashing with Argon2id. Not in the code yet. Do not use crypto.hash() for passwords: SHA-2 is fast by design.
  • Field encryption with a data label. Not in the code yet. Until it lands, encrypt a value with keys.encrypt(), which wraps a data key per value.
  • Drivers (libsodium, vault-transit, cloud-kms) behind the same calls.
  • Customer-held keys with crypto.keys.customer() (phase 3, spec hsm-keys); post-quantum signatures and hybrid key exchange (phase 5, spec post-quantum).

Errors and checks

CodeWhenFix
MAIN-CRYPTO-0001A key is the wrong size, an input is not valid base64url, an HMAC key is under 16 bytes, or a token or byte count is out of range (400)Use a 32-byte key from crypto.generateKey() and a long random secret from crypto.token()
MAIN-CRYPTO-0002A weak hash such as MD5 or SHA-1 is asked for (400)Use sha256, sha384 or sha512
MAIN-CRYPTO-0003A sealed box cannot be opened: wrong key, wrong aad, changed bytes or too short (400)Open with the key and aad that sealed it, on unchanged bytes
MAIN-CRYPTO-0010The platform has no implementation of the post-quantum algorithm asked forPick a supported algorithm or runtime (spec post-quantum)
MAIN-CRYPTO-0011FIPS mode is on and the call asked for an algorithm that is not approvedUse an approved algorithm, or turn crypto.fips off
MAIN-CRYPTO-0012FIPS mode is on and the platform has no FIPS-validated crypto providerRun on a validated provider
MAIN-CRYPTO-0013crypto.fips is on but crypto.module does not name the validated module (check)Add crypto.module with name, version and certificate

mainsdk check carries the same rule in source as MAIN-CRYPTO-0002; the rules belong to Main 27.

Swapping drivers

crypto has one driver today, the built-in WebCrypto one, so there is nothing to swap. Drivers for libsodium, vault-transit and cloud-kms are planned behind the same calls. Until then, use keys when the key must live in a key service. See Swap a driver.

Testing

crypto is pure and offline, so tests call it directly.

lib/notes.test.ts
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { crypto } from "mainsdk";

describe("crypto", () => {
  it("opens only with the same key and aad", async () => {
    const key = crypto.generateKey();
    const box = await crypto.seal(key, "hello", { aad: "note:1" });
    await assert.rejects(crypto.open(key, box, { aad: "note:2" }), { code: "MAIN-CRYPTO-0003" });
    assert.equal(new TextDecoder().decode(await crypto.open(key, box, { aad: "note:1" })), "hello");
  });
});

Security and privacy

  • Modern algorithms only: AES-256-GCM, ed25519, SHA-2 and HKDF-SHA-256.
  • Every seal uses a fresh random nonce. Comparison with crypto.equal() takes time that depends only on length.
  • Keys are bytes you pass in; nothing is logged or stored by crypto.

FAQ

When do I use crypto and when keys? keys encrypts records with their own data keys, wrapped by a KMS, with per-user keys for erasure. crypto is for single operations such as a hash, an HMAC or a signature, with keys you hold.

Can I choose SHA-1 or bcrypt? No. Only modern algorithms are offered.

Next

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet