10

I expect this's been asked before but haven't really found an appropriate answer here and also don't have the time to come up with my own solution...

If we have a user table with int identity primary key then our users have consecutive IDs while they register on the site.

The we have user public profile page on the site URL:

www.somesite.com/user/1234

where 1234 is the actual user ID. There is nothing vulnerable to see user's ID per se, but it does give anyone the ability to check how many users are registered on my site... Manually increasing the number eventually gets me to an invalid profile.

This is the main reason why I wand a reversible ID mapping to a seemingly random number with fixed length:

www.somesite.com/user/6123978458176573

Can you point me to a simple class that does this mapping? It is of course important that this mapping is simply reversible otherwise I'd have to save the mapping along with other user's data.

I want to avoid GUIDs

GUIDs are slower to index search them because they're not consecutive so SQL has to scan the whole index to match a particular GUID instead just a particular calculated index page...

If I'd have ID + GUID then I would always need to fetch original user ID to do any meaningful data manipulation which is again speed degradation...

A mathematical reversible integer permutation seems the fastest solution...

Robert Koritnik
  • 103,639
  • 52
  • 277
  • 404
  • 3
    Why is it a problem if someone can work out how many users are registered on the site? You don't have to start at 1! – ChrisF Jan 06 '12 at 09:29
  • Have you considered just using GUIDs? Even if your table already exists you could generate a guid for each row and use that for the mapping – Jonathon Bolster Jan 06 '12 at 09:32
  • @JonathonBolster: GUIDs are bad for SQL indexing because searching is slower (Just by GUID SQL can't know on which page result will be)... And having ID+GUID requires additional SQL query to get actual use ID to do any data manipulation... I'd like to avoid GUIDs for these two reasons. – Robert Koritnik Jan 06 '12 at 09:43

3 Answers3

17

I would 100% go with the "Add a GUID column to the table" approach. It will take seconds to generate one for each current user, and update your insert procedure to generate one for each new user. This is the best solution.

However, if you really dont want to take that approach there are any number of obfuscation techniques you could use.

Simply Base64 encoding the string representation of your number is one (bad) way to do it.

    static public string EncodeTo64(string toEncode)
    {

      byte[] toEncodeAsBytes
            = System.Text.ASCIIEncoding.ASCII.GetBytes(toEncode);
      string returnValue
            = System.Convert.ToBase64String(toEncodeAsBytes);
      return returnValue;
    }

    static public string DecodeFrom64(string encodedData)
    {
      byte[] encodedDataAsBytes
          = System.Convert.FromBase64String(encodedData);
      string returnValue =
         System.Text.ASCIIEncoding.ASCII.GetString(encodedDataAsBytes);
      return returnValue;
    }

Bad because anyone with half an ounce of technical knowledge (hackers/scriptkiddies tend to have that in abundance) will instantly recognise the result as Base64 and easily reverse-engineer.


Edit: This blogpost Obfuscating IDs in URLs with Rails provides quite a workable example. Converting to C# gives you something like:

static int Prime = 1580030173;
static int PrimeInverse = 59260789;

public static int EncodeId(int input)
{
    return (input * Prime) & int.MaxValue;
}

public static int DecodeId(int input)
{
    return (input * PrimeInverse) & int.MaxValue;
}

Input --> Output
1234 --> 1989564746
5678 --> 1372124598
5679 --> 804671123

This follow up post by another author explains how to secure this a little bit more with a random XOR, as well as how to calculate Prime and PrimeInverse - ive just used the pre-canned ones from the original blog for demo.

Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • As I outlined in the comment to @JonathonBolster I would like to avoid GUIDs for speed and added querying reasons. – Robert Koritnik Jan 06 '12 at 09:44
  • 2
    Oh yes... The primes calculation is great! **Much better than Base64 encoding.** – Robert Koritnik Jan 06 '12 at 10:44
  • How do the prime and primeinverse relate to eachother? I mean, can you calculate the primeinverse when i have another prime than 1580030173 – Michel Sep 18 '12 at 14:09
  • @Michel - The answer to that is linked in this very answer: [This follow up post by another](http://www.miguelsanmiguel.com/2011/04/03/hideous-obfuscated-ids) author explains how to secure this a little bit more with a random XOR, **as well as how to calculate Prime and PrimeInverse** – Jamiec Sep 18 '12 at 14:33
  • 1
    Here is a working link for the follow post: http://www.miguelsanmiguel.com/obfuscation – Blegger Nov 06 '12 at 22:57
  • Any working links on the relation between the int.max and the primes? – VisualBean Sep 17 '15 at 13:06
  • 1
    @VisualBean: I found a nice explanation and some code here: [Eric Lippert's Blog](http://ericlippert.com/2013/11/12/math-from-scratch-part-thirteen-multiplicative-inverses/) From it, I was able to make a C# multiplicative inverse calculator. Note that the calculation for encoding/decoding in C# using int values looks more like: `return (int) (Math.BigMul(input, Prime) % int.MaxValue);` which avoids integer overflow. (Or you could turn the overflow safeties off using unchecked, but I think the above better serves the intent of the alogrithm.) – donperk Jan 06 '16 at 00:20
  • Here is an article referencing the mentioned blogpost: https://robvs.wordpress.com/2010/07/08/obfuscating-ids-in-urls-with-rails/ and the archived version to calculate Prime and PrimeInverse: https://web.archive.org/web/20120711235339/http://miguelsanmiguel.com/2011/04/03/hideous-obfuscated-ids – Yuntao Mar 19 '16 at 00:00
1
  1. Use UUIDs

  2. Make another column in the user table for, e.g. 64 bit integers, and fill it with a random number (each time a new user registered - generate it and check it's unique). A number looks better than UUID, however a bit more coding required.

  3. Use maths. ;) You could generate pair of numbers X, Y such as X*Y = 1 (mod M). E.g. X=10000000019L, Y=1255114267L, and M=2^30. Then, you will have two simple functions:

.

long encode(long id)
{  return (id * X) & M; }

long decode(long encodedId)
{  return (encodedId * Y) & M; }

It will produce nearly random encoded ids. It's easy, but hackable. If someone would bother to hack it, he will be able to guess your numbers and see encoded values too. However, I am not completely sure which complexity it is, but as I remember it's not very easy to hack.

kan
  • 28,279
  • 7
  • 71
  • 101
  • hacking is not a problem... I'm not encrypting sensitive data... IDs are public... But would just like to obfuscate them... – Robert Koritnik Jan 06 '12 at 09:50
  • 1
    When try the third option. If you decide use it, I could remember algebra to explain how to generate such `X` and `Y` numbers for your own. You should keep them in secret, everybody who know them is able to decode your encoded IDs. – kan Jan 06 '12 at 09:56
0

May I suggest that you use a UUID instead. This could be indexable and generated within a stored procedure when you add a new user to the database. This would mean either adding a new column to the database table or a new table containing UUIDs but with the User ID as related key.

edit

If you really want to avoid GUIDs then why not use the users "username" whilst they access their profile page. After all I imagine that you don't assign a user an ID until they have entered valid information and that data has been saved into the database.

ChrisBD
  • 9,104
  • 3
  • 22
  • 35