ID Obfuscation Part II

Last week, I wrote a simple function for obfuscating a string that can be used to shorten URLs. I got a few emails from people who would actually like to obfuscate an ID key (E.G. a numeric primary key) of a large size (E.G. a PostgreSQL ‘bigserial’ type which can go up to 231). So many examples out there, but they seem convert the input to integers first, which can lead to loss of precision, especially in PHP.

I use Postgres too and I’ve moved around the big number problem by appending a random digit or two to the front and then encoding the whole thing. So when I need the original, I just decode it and remove the front digit(s). This does two things: It obfuscates the ID (no one needs to know 10001 and 10002 are neighbors) and makes sure each one is unique as long as the key given to it is unique. Of course if it’s a primary key from a database, you won’t have to worry too much about uniqueness; it already is. And since I’m always appending the same number of digits as I’ll remove when decoding, it doesn’t matter how large the number gets.

So here’s a function that will create a shortened ID from a given numeric key in PHP :

public function ConvertKey( $k, $create = false ) {
	$range = str_split( '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' );
	$il = strlen( $k );
	$cl = 62; // count( $range ) is redundant;
	$out = '';
	// Get number from key
	if( $create ) {
		$out = 0;
		// Letter/number to array key swap
		$a = array_flip( $range );
		for( $i = 0; $i < $il; $i++ ) {
			$c = $k[$i];
			$n = bcpow( $cl, $il - $i - 1 );
			$out = bcadd( $out, bcmul( $a[$c], $n ) );
		// Strip front two random digits (appended below)
		$out = substr( $out, 2 );
	} else {
		// Append two random digits to the front
		// (NOT added, just attached to the front)
		$k = mt_rand( 10, 99 ) . $k . '.0';
		do {
			$c = bcmod( $k, $cl );
			$out .= $range[$c];
			$k = bcdiv( bcsub( $k, $c ), $cl );
		} while( bccomp( $k, 0 ) > 0 );
		// We worked from back to front
		$out = strrev( $out );
	return $out;

You can test this out by sticking it in a loop :

for( $i = 5000; $i < 6000; $i++ ) {
	$kConverted = ConvertKey( $i );
	$kOriginal = ConvertKey( $kConverted, true );
	echo $i . ' - ' . $kConverted . ' - ' . $kOriginal . '<br />';

Of course, you’ll need to keep in mind that the generated key will be different each time you run it, however the end result after decoding will be the same.

I also wrote a post on encryption with… *ahem*… colorful comments and, thankfully, most people stuck to the actual code itself when contacting me about it. Yes, I did change the encryption mode from CFB to CBC. CFB doesn’t need padding so I wasn’t lying about the sleep-deprivation. Thanks to those who wrote to me about it.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s