crypt-ids
v1.0.0
Published
Given a 128 bit secret, create functions to encrypt a 32 bit signed integer to a base58 encoded string and vice versa for things such as coupon codes, order ids, short URLs, serial numbers, etc. ## usage ```shell npm install crypt-ids cat /dev/urandom
Downloads
4
Readme
crypt-ids
Given a 128 bit secret, create functions to encrypt a 32 bit signed integer to a base58 encoded string and vice versa for things such as coupon codes, order ids, short URLs, serial numbers, etc.
usage
npm install crypt-ids
cat /dev/urandom | head -c 16 > 16byteSecretKey
# or https://www.random.org/bytes/
# or /dev/null for testing purposes
const b = require('fs').readFileSync('./16byteSecretKey');
const cryptids = new (require('crypt-ids').Cryptids)(new Uint8Array(b.buffer, b.byteOffset, b.byteLength));
let original = 5;
let encrypted = cryptids.i2s(original);
let decrypted = cryptids.s2i(encrypted);
console.log(original, encrypted, decrypted);
5 meGJU 5
why
The type of database identity columns are commonly an integer or a UUID.
The problems with exposing an auto-incremented integer publicly is it's easy to guess another valid id and figure out how many entities there are. Why not randomize the integer? This works fine until you start getting collisions which the probability increases exponentially as the number of records approaches the maximum value of the integer. Your inserts would grind to a halt looking for an usused but random integer. What about pregenerating a randomized permutation of the sequence? Sure, if you have the entropy available to generate 2^32 random numbers and 16gb to shuffle and store the ids (assuming 32 bit integer and Fisher–Yates shuffle)
What about UUIDs? While the probability that a UUID will be duplicated is not zero, it is close enough to zero to be negligible. The biggest issue with them is size. Databases work faster with smaller columns, and humans checking such a long identifier for equality is very error prone.
An auto-incremented integer is the most convenient type for a databases identity column. A linear-feedback shift register or a linear congruential generator can be used to map to a seemingly random number within the column's limits, but it's not cryptographically secure. This recommends using a 32 bit block cipher, but those are rare. You could roll your own, or use skip32
, a derivative of skipjack
, but the NIST does not recommend its use after 2010.
What if a proven encryption algorithm such as AES could be used? Unfortunately, its block size is too big (128 bits). Format preserving encryption to the rescue! Using the FF1 algorithm approved by the NIST, essentially AES can be used with any block size!
development
I didn't actually do anything other than bundle things together...
dependencies
notes
this package was generated with cargo new --lib crypt-ids
to build the npm package from the rust source, wasm-pack build --target nodejs
FAQ
Isn't format preserving encryption slow?
With this benchmark code on a 3950x
const b = require('fs').readFileSync('./16byteSecretKey');
const cryptids = new (require('crypt-ids').Cryptids)(new Uint8Array(b.buffer, b.byteOffset, b.byteLength));
const l = 1000000;
let decrypted = new Array(l);
let encrypted = new Array(l);
const start = process.hrtime.bigint();
for(let i = 0; i < l; ++i) {
encrypted[i] = cryptids.i2s(i);
}
const e = process.hrtime.bigint();
for(let i = 0; i < l; ++i) {
decrypted[i] = cryptids.s2i(encrypted[i]);
}
const d = process.hrtime.bigint();
console.log((e - start) / 1000000000n, (d - e) / 1000000000n);
it outputs
33n 33n
which translates to 30303 encodes or decodes per second.
What is the encoded string length?
5 or 6.