1

I have a table of orders and I want to give users a unique code for an order whilst hiding the incrementing identity integer primary key because I don't want to give away how many orders have been made.

One easy way of making sure the codes are unique is to use the primary key to determine the code.

So how can I transform an integer into a friendly, say, eight alpha numeric code such that every code is unique?

ataravati
  • 8,891
  • 9
  • 57
  • 89
Ian Warburton
  • 15,170
  • 23
  • 107
  • 189
  • 1
    Why not just create an offset to start at... Any number will do, and that way the numbers will be both sequential and the actual order count cannot be discerned. – Haney Aug 27 '13 at 18:32
  • 1
    yes, 4 bytes can be very easily represented by 8 alphanumeric characters. There is infinite number of [functions that uniquely map](http://en.wikipedia.org/wiki/Bijection) `int` to sequence of 8 characters... so you need to provide some criteria to pick one. – Alexei Levenkov Aug 27 '13 at 18:33
  • Maybe you should concatenate 8 random numbers or characters. Something very pro would be to have 2 letters and 6 numbers randomly choosen :) Of course it forces you to test the newly generated key each time :( – Julien Aug 27 '13 at 18:34
  • @DavidH If someone makes more than one order then they can tell how many have happened inbetween. – Ian Warburton Aug 27 '13 at 18:51
  • @Julien That's what I want to avoid! – Ian Warburton Aug 27 '13 at 18:51
  • 1
    possible duplicate of [Integer ID obfuscation techniques](http://stackoverflow.com/questions/2565478/integer-id-obfuscation-techniques) – Jim Mischel Aug 27 '13 at 18:54
  • And once you obfuscate the number, just base64 encode it for display to the user. Strip the padding characters for display and add them back if you want to convert the obfuscated key back to an integer. – Jim Mischel Aug 27 '13 at 18:59

5 Answers5

1

The easiest way (if you want an alpha numeric code) is to convert the integer primary key to HEX (like below). And, you can Use `PadLeft()' to make sure the string has 8 characters. But, when the number of orders grow, 8 characters will not be enough.

var uniqueCode = intPrimaryKey.ToString("X").PadLeft(8, '0');

Or, you can create an offset of your primary key, before converting it to HEX, like below:

var uniqueCode = (intPrimaryKey + 999).ToString("X").PadLeft(8, '0');
ataravati
  • 8,891
  • 9
  • 57
  • 89
  • 1
    That doesn't really obfsucate it. You'd end up with "00000001", "00000002", "00000003" ... "000010A9", "000010AA", etc. – Jim Mischel Aug 27 '13 at 18:55
  • It's the easiest way, as I said in my answer, and I think it should be good enough in a lot of cases. Unless there are less than 16 orders in the database, which I'm sure is not the case. Also, you can always add offset the primary key by a certain number and then convert it to HEX. – ataravati Aug 27 '13 at 20:14
0

A quick and easy way to do this is to have a guid column that has a default value of

left(newid(),8)

This solution will generally give you a unique value for each row. But if you have extremely large amounts of orders this will not be unique and you should use just the newid() value to generate the guid.

Avitus
  • 15,640
  • 6
  • 43
  • 53
0

I would just use MD5 for this. MD5 offers enough "uniqueness" for a small subset of integers that represent your customer orders.

For an example see this answer. You will need to adjust input parameter from string to int (or alternatively just call ToString on your number and use the code as-is).

Community
  • 1
  • 1
oleksii
  • 35,458
  • 16
  • 93
  • 163
  • Except MD5 produces a 16 byte hash value. That's kind of a long key when you only need 4 bytes. – Jim Mischel Aug 27 '13 at 18:50
  • OP says he needs "eight alpha numeric code". Given most chars take 2 bytes in C# string this fits perfectly. Although conversion to base64 would take take more space. In any case it is possible to truncate MD5 hash. – oleksii Aug 27 '13 at 19:49
  • OP wants to turn an integer, a 4-byte binary value, into an 8-character (maximum) human-readable key. That *usually* means using ASCII characters. Are you certain that truncating an MD5 hash value will give a unique value for all possible 32 bit integers? – Jim Mischel Aug 27 '13 at 20:11
  • This type of solution is tempting but unsatisfactory because if a duplicate is generated then you need handle it. So you might as well just use a cheap and cheerful random number. – Ian Warburton Aug 27 '13 at 22:23
0

If you would like something that would be difficult to trace and you don;t mind it being 16 characters, you could use something like this that includes some random numbers and mixes the byte positions of the original input with them: (EDITED to make a bit more untraceable, by XOR-ing with the generated random numbers).

public static class OrderIdRandomizer
{
    private static readonly Random _rnd = new Random();

    public static string GenerateFor(int orderId)
    {
        var rndBytes = new byte[4];
        _rnd.NextBytes(rndBytes);
        var bytes = new byte[]
        {
            (byte)rndBytes[0],
            (byte)(((byte)(orderId >> 8)) ^ rndBytes[0]),
            (byte)(((byte)(orderId >> 24)) ^ rndBytes[1]),
            (byte)rndBytes[1],
            (byte)(((byte)(orderId >> 16)) ^ rndBytes[2]),
            (byte)rndBytes[2],
            (byte)(((byte)(orderId)) ^ rndBytes[3]),
            (byte)rndBytes[3],
        };
        return string.Concat(bytes.Select(b => b.ToString("X2")));
    }
    public static int ReconstructFrom(string generatedId)
    {
        if (generatedId == null || generatedId.Length != 16)
            throw new InvalidDataException("Invalid generated order id");
        var bytes = new byte[8];
        for (int i = 0; i < 8; i++)
            bytes[i] = byte.Parse(generatedId.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber);
        return (int)(
                ((bytes[2] ^ bytes[3]) << 24) |
                ((bytes[4] ^ bytes[5]) << 16) |
                ((bytes[1] ^ bytes[0]) << 8) |
                ((bytes[6] ^ bytes[7])));
    }
}

Usage:

var obfuscatedId = OrderIdRandomizer.GenerateFor(123456);
Console.WriteLine(obfuscatedId);
Console.WriteLine(OrderIdRandomizer.ReconstructFrom(obfuscatedId));

Disadvantage: If the algorithm is know, it is obviously easy to break. Advantage: It is completely custom, i.e. not an established algorithm like MD5 that might be easier to guess/crack if you do not know what algorithm is being used.

Alex
  • 13,024
  • 33
  • 62
0

Assuming the total number of orders being created isn't going to get anywhere near the total number of identifiers in your pool, a reasonably effective technique is to simply generate a random identifier and see if it is used already; continue generating new identifiers until you find one not previously used.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • @IanWarburton Why not? It's the simplest and most effective solution to the problem. None of the other solutions posted here actually *work* (or if they work, they are easily reversed, thus not providing adequate protection). As I said, assuming your identifier pool is dramatically larger than the number of orders you have, you can ensure an average number of attempts to be only slightly above one, and much less than two. – Servy Aug 28 '13 at 13:49
  • Yeah that's what I've done! Pushed the repeat code into the repository. I even used RNGCryptoServiceProvider to generate random bytes because its thread safe. – Ian Warburton Aug 28 '13 at 19:31