2 ms·
This is an approach that has been around for a long time, mostly with 32-bit and 64-bit block sizes. (e.g. if you search around, you’ll hear about Skip32 as a c
by chrismorgan 4y ago
This is an approach that has been around for a long time, mostly with 32-bit and 64-bit block sizes. (e.g. if you search around, you’ll hear about Skip32 as a common choice Back In The Day, a variant of Skipjack crafted for the purpose.)
I’ve been working on this problem for a few months from the perspective of yielding human-friendly IDs, and reached what I consider a finished state a few weeks ago, but haven’t published it because I was thinking about other things. I’ll probably publish it in the middle of next week. If anyone’s interested in a sneak peak (and final review!), they can email me and I’ll give a link.
My initial just-about-ready-to-publish version encrypted with Speck32/64 (for 32-bit IDs) and Speck64/128 (for 64-bit IDs), and did base-58 stringification (ASCII alphanumeric minus 0, O, I, l). This produced about four billion 6-character IDs, then 11-character IDs after that.
I was also toying with things with different block sizes, since I wasn’t enthusiastic about the 6-/11-character ID length. After feedback from interested people I became more determined to do so, because the feedback convinced me that case-sensitivity was sufficiently undesirable, and going down to base-36 or lower took it up to 7/13 characters.
I tried Hasty Pudding, but it was too big and complex and unknown for my liking, though I did implement it and it worked… though I couldn’t even find the official test vectors, since they’re on a dead FTP site and the author didn’t respond to an email. I looked into FF1 and FF3-1 too, but patent encumbrance decided me against them. I ended up daring to create a variant of Speck with multiples-of-five word sizes. This actually ended up making my code smaller: the JavaScript implementation of my entire scheme is 1680 bytes minified, 915 gzipped with zopfli (and shedding a bit of functionality it can easily be taken under 1200/700).
So now it’s using my custom Speck variant and a base-32 alphabet (lowercase letters and numbers minus 0, o, 1, l). [0, 2²⁰) becomes four characters, [2²⁰, 2³⁰) six, [2³⁰, 2⁴⁰) eight, &c. up to [2⁹⁰, 2¹⁰⁰) twenty. I also add one more step to allow for making valid IDs less guessable and the inclusion of a type discriminant if desired (and these are why I had the cipher go up to a 100-bit blocksize): id → sparsity × id + discriminant.
Performance: my Rust implementation encodes all four-character IDs (about a million of them) in about 50ms on my AMD Ryzen 5800HS laptop, single-threaded, which at about 4.4GHz is a bit over 200 cycles each; but that’s actually 99% stringification: a benchmark of just encrypting all 20-bit values takes about 475,000ns, which looks like it might be exactly 2 cycles each, while still being Real Encryption. (I also have Python and JavaScript implementations, but I haven’t benchmarked them.)