@ralalabs/rid
v1.0.3
Published
Fast deterministic reversible ID encoding for numbers and strings using AES-128-CTR with base62 output
Maintainers
Readme
@ralalabs/rid
Deterministic, reversible ID obfuscation for Node.js and WebCrypto runtimes. The exporter produces tokens and Atlas decodes them using the same key and prefix. There are no runtime dependencies.
RID is obfuscation, not a confidentiality or authorization boundary. Keep sensitive values and their tokens behind the application's access controls.
Usage
npm install @ralalabs/ridimport { createCodec } from '@ralalabs/rid';
const key = process.env.RID_KEY;
if (!key) throw new Error('RID_KEY is required');
const codec = createCodec({ key, prefix: 'media' });
const token = await codec.encodeString('media:123');
const id = await codec.decodeString(token);Generate a key once with await generateKey(), imported from @ralalabs/rid or @ralalabs/rid/generate. Store it in the application's secret configuration and share it with the services that decode those tokens. Generating a new key on startup makes existing tokens unreadable.
API
createCodec(options) validates configuration synchronously and returns an asynchronous codec.
| Option | Accepted values |
| ----------- | ----------------------------------------------------------------------------------- |
| key | A 32-character hexadecimal string (optional 0x prefix), or a 16-byte Uint8Array |
| prefix | Optional letters, digits and hyphens; an empty string omits the prefix |
| separator | A non-empty string without ASCII letters or digits; defaults to - |
The body uses base62 (0-9A-Za-z). Use URL-safe prefixes and separators when placing tokens in URLs. Decode requires the configured prefix and separator exactly.
| Method | Result |
| ----------------------------------------------- | -------------------------------------------------------- |
| encode(number \| string) | Promise<string> |
| decode(token) | Promise<number \| string> |
| encodeNumber(number) / encodeString(string) | Promise<string>; validates the input type |
| decodeNumber(token) / decodeString(token) | A promise of the requested type; rejects a type mismatch |
| encodeBatch(values) / decodeBatch(tokens) | Results in input order; rejects if any item fails |
Numbers must be non-negative safe integers, including zero. Strings may be empty and must contain well-formed Unicode, with at most 65,535 UTF-8 bytes. Leading Unicode BOM characters are preserved. Numeric 42 and string "42" have different tokens.
Validation failures use RidError. Crypto backend failures can propagate their native errors. Structural validation rejects malformed payloads; successful decoding does not prove authenticity.
Format and runtime
A six-byte header contains the RI marker, version, value type and payload length. Numbers use eight big-endian bytes; strings use UTF-8. AES-128-CTR transforms this payload with a fixed zero counter, then base62 represents the result. The prefix is outside the transformed payload.
The same value and key produce the same body across runtimes. Changing only the prefix does not change that body. Keep keys, prefixes and separators consistent between producers and consumers. Key or format changes require a coordinated strategy for persisted tokens.
Node.js uses node:crypto; environments without a Node version marker use WebCrypto AES-CTR. The build targets ES2022 and exports ESM, CommonJS and TypeScript declarations. Tests exercise both crypto adapters using fixed compatibility vectors.
Reuse codec instances. Adapter selection and imported WebCrypto keys are cached, and the payload avoids JSON serialization. Base62 uses BigInt conversion: this is an ID codec, not a bulk-data encoder. Decode bounds token size before conversion; callers should also limit request sizes and batch lengths for their workload. The batch methods use Promise.all without a concurrency limit.
Security boundary
The fixed counter preserves determinism but reuses the CTR keystream. Known plaintext can expose other values encrypted under the same key over the corresponding bytes. Tokens also have no authentication tag and can be modified. CTR counter reuse does not provide confidentiality; see RFC 9459, Security Considerations.
Prefixes are labels, not cryptographic separation. RID must not protect secrets from a party who can read the tokens, and must not replace authorization or authenticated encryption. Private storage and the application's access controls remain responsible for protecting server-side values.
Development
Use npm for this repository. Commit changes to package-lock.json alongside dependency updates; it is the authoritative lockfile. Use npm ci for reproducible installs and npm install when changing dependencies.
npm ci
npm run prepublishOnlyThe verification script runs lint, formatting, type checking, ESM/CommonJS builds and tests. It does not publish. Source lives in src/, tests in test/, and build output in dist/.
