opaqid
v1.0.0
Published
Stateless, reversible integer obfuscation - Convert sequential IDs to URL-safe strings without database storage
Maintainers
Readme
Opaqid
Stateless, reversible integer obfuscation - Convert sequential IDs to URL-safe strings without database storage.
const opaqid = new Opaqid('your-secret-salt');
opaqid.encode(1); // "f4GZ8zzYZaW"
opaqid.encode(2); // "DJb7eUnfdqd"
opaqid.encode(3); // "mpC9L9p6zBw"
opaqid.decode("f4GZ8zzYZaW"); // 1n (BigInt)
opaqid.decodeNumber("f4GZ8zzYZaW"); // 1Why Opaqid?
| Problem | Solution | |---------|----------| | Sequential IDs expose business info | → Obfuscated, random-looking output | | UUID requires DB storage | → Stateless, no storage needed | | Hashids/Sqids are pattern-analyzable | → Feistel cipher resists cryptanalysis |
Installation
npm install opaqidUsage
Basic
import Opaqid from 'opaqid';
const opaqid = new Opaqid('your-secret-salt');
// Encode
const encoded = opaqid.encode(12345); // "5fI79m3L2nJ"
// Decode
const decoded = opaqid.decodeNumber(encoded); // 12345With Options
const opaqid = new Opaqid('your-secret-salt', {
minLength: 12, // Minimum output length (default: 8)
rounds: 6, // Feistel rounds (default: 4, min: 2)
alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', // Custom alphabet
});BigInt Support
const bigNum = BigInt('9007199254740993'); // > Number.MAX_SAFE_INTEGER
const encoded = opaqid.encode(bigNum);
const decoded = opaqid.decode(encoded); // Returns BigInt
console.log(decoded === bigNum); // trueAPI
new Opaqid(salt, options?)
Creates a new Opaqid instance.
| Parameter | Type | Description |
|-----------|------|-------------|
| salt | string | Secret salt (min 4 chars). Different salts produce different outputs. |
| options.minLength | number | Minimum output length (default: 8) |
| options.rounds | number | Feistel cipher rounds (default: 4, min: 2). More = slower but stronger. |
| options.alphabet | string | Custom alphabet (min 16 unique chars, default: Base62) |
encode(id: number | bigint): string
Encodes an integer to an obfuscated string.
decode(encoded: string): bigint
Decodes an obfuscated string back to a BigInt.
decodeNumber(encoded: string): number
Decodes to a number. Throws if result exceeds Number.MAX_SAFE_INTEGER.
isValid(encoded: string): boolean
Checks if a string contains only valid alphabet characters.
How It Works
Opaqid uses a Feistel cipher combined with Base62 encoding:
int → 64-bit bytes → Feistel Encrypt → Base62 Encode → stringWhy Feistel?
- Guaranteed reversibility - Same algorithm for encryption/decryption
- Round function doesn't need to be reversible - Implementation flexibility
- Proven security - DES, Blowfish, Twofish all use Feistel networks
- 4+ rounds = PRP - Luby-Rackoff theorem guarantees pseudorandom permutation
Security Properties
- ✅ Different salts → completely different outputs
- ✅ Sequential inputs → no visible pattern
- ✅ Wrong salt → wrong decode (garbage, not error)
- ✅ No cryptographic dependencies - Pure JavaScript
Performance
Encode: ~500,000 ops/sec
Decode: ~250,000 ops/secComparison
| Feature | Hashids | Sqids | Opaqid | |---------|---------|-------|-----------| | Reversible | ✓ | ✓ | ✓ | | Salt/Key | ✓ | alphabet shuffle | ✓ (Feistel keys) | | minLength | ✓ | ✓ | ✓ | | Pattern resistant | ✗ | ✗ | ✓ | | BigInt | partial | partial | ✓ | | Crypto-grade | ✗ | ✗ | ✓ |
Security Note
Opaqid provides obfuscation, not encryption. While it's much stronger than Hashids/Sqids:
- ✅ Use for: Hiding sequential IDs, preventing enumeration
- ❌ Don't use for: Storing secrets, authentication tokens
Always implement proper access control—obfuscated IDs are not a replacement for authorization.
Documentation
License
MIT
