I’m writing my own list manager and I need a simple short URL for users to “one click” to unsubscribe, e.g. http://unsubscribe.example.com/50d178fa
50d178fa is just a hex of epoch, which is too guessable I like to think. One could be malicious and just unsubscribe people by correctly guessing the hex for a certain time period.
So I’m looking for some light protection. xor seems simplest. Any other “suck less” suggestions using a secret that generates an “unguessable string” not longer than 8 characters that I might be missing?
epoch=$(date +%s)
hex=$(printf '%x' $epoch)
echo Convert epoch to hex
echo d:$epoch h:$hex
echo h:$hex is ${#hex} characters long
echo Conversion from hex back to epoch
echo h:$hex d:$(printf "%dn" 0x$hex)
n=911 # secret number
obfuscated=$(($epoch ^ $n))
obfuscatedhex=$(printf '%x' $obfuscated)
echo d:$obfuscated, h:$obfuscatedhex is ${#obfuscatedhex} characters long
echo Conversion from hex back to epoch
echo $(($(printf "%dn" 0x$obfuscatedhex) ^ $n))
To expand, xor is not very good for obfuscating if you xor against the same thing every time. If someone gets a few examples of these links, they will get
a ^ n,b ^ n,c ^ n, fora,b, andc, which are times (and thus, will generally only differ in the last few bits). They can calculate(a ^ n) ^ (b ^ n)=(a ^ b) ^ (n ^ n)=(a ^ b), which will show them just the bits that differ betweenaandb. If they do the same withcand either of those, they’ll realize that it’s just the low-order bits which differ in each case. So they can take the values they have, and start guessing by flipping low-order bits. xor against a secret hasn’t actually bought you any protection from someone trying to guess values; flipping a few low-order bits is just as easy for timestamps xored against a fixed value as it is against the timestamp directly.For it to be secure, you need to use a different value every time; a one time pad, in which you use a different, secure, random number each time, or a function that can securely generate a new value from a key. In the first case, if you’re using a new random number each time, why not just use that number as the key? In the second, you would need to use a stream cipher, which generates a pseudorandom stream that you could use for xoring your output from a secret key that you hold. But you would then need to keep track of where you were in that stream; if you ever repeat the stream, you run into the same old problem that you had, so this approach is much more complicated and delicate.
You already have a good, easy to use source of cryptographically secure unguessable values in
/dev/urandom. You might as well use that. Just store the random value per subscriber; it’s just 4 bytes per subscriber (or 8 if you store the hex value). Now, 32 bits is not great security; with enough tries, there’s a good chance that someone could unsubscribe someone else. But if you do anything to rate-limit attempts, it shouldn’t be too bad. And you can always easily increase the security by just fetching more bytes for each link.