Do Not Encrypt IDs

Every time IDs are discussed on Hacker News, someone suggests encrypting them instead of using public-facing UUIDv7 or other alternatives. This sounds clever. It's not.

The Problem

Sequential IDs leak information. They are subject to the "German tank problem", allowing attackers to estimate the number of records and guess valid IDs to access resources.

You switch to UUIDv4: Random, unpredictable, and pretty standard. Except your database suffers: random IDs destroy index locality and make your DB lookups slow.

You switch to UUIDv7: Sortable, thus DB-friendly. But the timestamp is embedded. If you can tolerate disclosing creation time, you've solved the problem and you're done.

The Bad Solution: Encrypt Your IDs

Some people see the timestamp and panic. The proposed solution: encrypt the UUIDs with AES to create a shuffling 1:1 mapping. Since AES works on 128-bit blocks, it makes things easy. Encrypt your internal UUID as a block, and get the public-facing output block represented as a standard UUID string (not v4, though). Decrypt the public-facing UUID as a block to get the internal UUID back.

It even works with 64-bit integers, you can use Blowfish which works on 64-bit blocks, same principle.

Sounds clever, right? Well, it's wrong.

New Problem: Key Management

You cannot rotate the key. If your (presumably lifetime) encryption key leaks, rotating it invalidates every ID in use: in URLs, logs, external systems, everywhere. Your IDs are permanent, so your key must be permanent too, which is very hard to manage.

You might prefix IDs with a key identifier and using some scheme like [kid]:[encrypted_uuid]. Now you can rotate keys, but now you have the exact same problem: need to manage the KID (Key ID).

Operations becomes a nightmare. You now have a cryptographic secret to manage. Where does this key live? Environment variables? A secrets manager/vault? Protected by a wrapping key living in a KMS or HSM? Do you use the same key across prod, staging, and dev? If dev needs to test with prod data, does it need access to prod encryption keys? What about CI pipelines? Local developer machines?

You're building a cryptosystem just to generate IDs. That's a lot of operational complexity for identifiers.

Encrypting IDs is easy. Managing keys is hard. Key management is the most difficult part of any cryptosystem. You're taking on that complexity to hide timestamps that aren't sensitive.

What to Do Instead

Use UUIDv7 or other timestamp-based sortable identifiers if your use-case tolerates timestamp disclosure. Remember that some UUIDv7 implementations allows fuzzing the timestamp bits if you're not comfortable with millisecond precision.

If you cannot expose any time information, use UUIDv4 (or other random identifier) as a secondary key for external use and remap it to your internal ID. If you use PostgreSQL, Hash indexes can help here, you'll avoid the locality problem discussed earlier. (MySQL and MariaDB have them too.)

Even if you use non-guessable IDs, please implement proper authorization layer to protect resources from unauthorized accesses.