A few years ago I made a scheme whereby you could use sequential numeric IDs in your database, but expose them as short random strings (length 4–20 step 2, depending on numeric value and sparsity configuration). It used some custom instances of the Speck cipher family, and I think it’s robust and rather neat.

Although I finished it, I never quite published it properly for some reason, probably partly because I shelved the projects where I had been going to use it (I might unshelve one of them next year).

Well, I might as well share it, because it’s quite relevant here and interesting:

https://temp.chrismorgan.info/2025-09-17-tesid/

My notes on its construction, pros and cons are fairly detailed.

Maybe I’ll go back and publish it properly next year.

Nice. See also sqids (previously known as hashids)

https://sqids.org/

I would not recommend it to anyone for any purpose: https://temp.chrismorgan.info/2025-09-17-tesid/more/#hashids

(Ah, it’s fun reading through that document a bit again. A few things I’d need to update now, like the Hashids name, or in the UUID section how UUIDv7 is no longer a draft, and of sidenote 12 I moved to India and got married and so took a phone number ending in 65536, replacing my Australian 32768. :-) )

> I would not recommend it to anyone for any purpose

The most likely purpose for this kind of encoding is to discourage users (as in other developers) from trying to derive meaning from the values that is not actually there.

This happens all the time: Another developer using your API observes sequential IDs, for example, and soon they start building their software on top of that observation, assuming it to be an intended property of the system. It even works perfectly for a while... until you want to change your implementation and break those assumptions. Which you now can't do, because breaking users is the cardinal sin of software development, leaving you forever beholden to implementation details that were never intended to leak out. That's not a good place to be. Making the IDs "opaque" indicates to the user that there is no other meaning.

That they are guessable doesn't matter. I dare say it may even be beneficial to be able to easily reverse the strings back into their original form to aid with things like debugging. Software development is primarily about communicating with other people, and using IDs that, at first glance, look random communicates a lot — even if they aren't actually random.

There may be a time and place for actually secure IDs, but more often than not you don't really need them. What you do regularly need, though, especially in large organizations, is a way to effectively work with others who don't read the documentation.

> It’s just bad

This is the first I've heard of Hashids, so I'll take your word for it, but I'm not sure you actually articulated why. I'll grant you that excluding profanity is a stupid need, but it is understandable why one might have to accept that as a necessary feature even if ultimately ridiculous.

[deleted]

I'd never use hashids/sqids for anything secure. It's reversible by design.

However, it is fit for purpose if your purpose is showing user-facing ids that can't be trivially incremented. For example, in a url, or in an api response. It does, in fact, "protect" against the "attack" of "Oh, I see in the url that my id is 19563, I wonder what I get if I change it to 19564.”

Now, the system should absolutely have authorization boundaries around data, but that doesn't mean there's no value in avoiding putting an "attractive nuisance" in front of users.

> "protect" against the "attack"

If it's not a real attack, it's not worth protecting against even in the slightest. If it's a real attack, it doesn't matter if it's trivial or not, does it?

It very much can be worth protecting so that your users don't become dependent on thinking that increment IDs is a feature. It's not a security concern in that context, but it is a future maintainability concern where you don't intend to provide that as a feature in environments where you don't have a tight leash on how users are using your APIs.

Hey Chris, that's a really nice blogpost. Not only the content but also the design / sidenotes. What kind of software stack do you run your block with?

https://chrismorgan.info/blog/2019-website/

It’s lasted for three years of use and three years of disuse, and I hope to replace it with something utterly different (stylistically and technically) by the end of this year, though it may slip to next year. The replacement will be based on handwriting.

Thanks. I like it very much, perfect dark mode. The serif font could be a tiny bit bigger for readability. Not a fan of handwriting fonts but you do you :-)

Who said handwriting fonts?

(I’m not a fan of handwriting fonts either. They’re never truly satisfying, though some with quite a few variants for each character get past the point of feeling transparently inauthentic. But when you can write and draw what you choose, where you choose, that’s liberating.)

Here is a Ruby gem for generating and managing pretty, human-readable keys in ActiveRecord models - uses sqids and a ticket table:

https://github.com/noreastergroup/active_record_pretty_key

Oh thanks for sharing this. Many years ago I was asked to code such a thing during an interview and I totally screwed it up, and of course I forgot the name of this technique.

I wanted to use it many times in project for non-iteratable IDs but never found it again.

I was interested in something similar with Speck for obfuscating bigserial PKIDs but the shortage of cross-platform implementations - especially in pgcrypto - led to choosing base58(AES_K1(id{8} || HMAC_K2(id{8})[0..7])) instead, which we could implement in almost anything and is performant enough, albeit longer output (typically 22 characters)

For this specific use case, you don’t need anything fancy like a constant time implementation, and I found it easy enough to implement from the paper—except that, mindbogglingly, they didn’t address endianness at all, even though you have to take it into account; so you need to read https://www.spinics.net/lists/arm-kernel/msg633602.html as well.

Look at https://git.chrismorgan.info/tesid/blob/HEAD:/rust/src/fpeck..., it’s very simple.

[deleted]