Skip to Content

Crypto overview

What @sukujs/crypto offers and which tool fits which job.

@sukujs/crypto is a small set of security helpers built on the Web Crypto API. It has no dependencies, runs on Node 22+, Bun and edge runtimes, and works with or without the rest of Suku.

bun add @sukujs/crypto

This package has not had an independent security audit. It implements no cryptography of its own: every operation is done by the platform, and the package adds safe defaults and input checking around it.

Importing

Use the default export, which carries every function:

import sukuCrypto from '@sukujs/crypto';

const hash = await sukuCrypto.hashPassword(password);
const token = sukuCrypto.randomToken();

Each function is also a named export, which lets a bundler drop the ones you do not use:

import { hashPassword, randomToken } from '@sukujs/crypto';

They are the same functions. These pages use the default export.

Choosing the right tool

Most mistakes come from using the wrong tool, not from using a tool badly.

You need toUseWhy
Let a client prove who it is on each requestJWTVerified without a database lookup.
Let other services verify your tokensJWT with a key pairThey hold only the public key.
Store a passwordhashPasswordSlow on purpose, salted, upgradeable.
Send a value the client may read but not changecreateSignerTamper-proof, still readable.
Send a value the client must not readcreateSealerEncrypted and tamper-proof, with expiry.
Create a session id, API key or reset tokenrandomToken()256 bits from the system's secure source.
Fingerprint a file or cache keysha256()Fast. Never for passwords.
Compare two secretstimingSafeEqual()Does not leak where they differ.

Signed, sealed, or a JWT?

Signed valueSealed valueJWT
Client can read itYesNoYes
Client can change itNoNoNo
Built-in expiryNoOptional ttlYes, by default
Verifiable by another serviceOnly with the shared secretOnly with the shared secretYes, with a public key
Typical useCookie value, webhook, linkEncrypted cookie, OAuth state, reset linkAPI authentication

A JWT's payload is only encoded, not encrypted. Anyone holding the token can read it.

Everything in the package

AreaFunctions
TokenscreateJwt, bearerToken, decodeJwtUnverified
PasswordshashPassword, verifyPassword, needsRehash
Signed valuescreateSigner
Encrypted valuescreateSealer
RandomrandomToken, randomBytes, randomHex, randomUUID, randomInt
Hashingsha256, sha512, digest
Encoding and comparisonbase64urlEncode, base64urlDecode, hexEncode, timingSafeEqual

Secrets

Every helper that takes a secret requires at least 32 bytes and refuses anything shorter. Generate one per purpose and keep it in your environment.

import sukuCrypto from '@sukujs/crypto';

console.log(sukuCrypto.randomToken(32));   // run once, paste into your secret store

Validate them at startup with configuration, so a missing secret stops the app before it serves a request.

export const Env = defineConfig(
  'env',
  z.object({
    JWT_SECRET: z.string().min(43),
    COOKIE_SECRET: z.string().min(43),
  }),
);

Errors

Functions that verify something you control return a plain result: verifyPassword gives true or false, and unpack and unseal give the value or undefined. They never throw on bad input.

jwt.verify throws a JwtError with a code, because the reason matters for logging. Whatever the code, respond to the client with a plain 401. See best practices.

Not included

Encrypted JWTs (JWE), fetching keys from an identity provider (JWKS), OAuth and OpenID Connect flows, token revocation lists, and Argon2 or bcrypt.

On this page