3

I have a requirement for generating numeric codes that will be used as redemption codes for vouchers or similar. The requirement is that the codes are numeric and relatively short for speed on data entry for till operators. Around 6 characters long and numeric. We know that's a small number so we have a process in place so that the codes can expire and be re-used.

We started off by just using a sequential integer generator which is working well in terms of generating a unique code. The issue with this is that the codes generated are sequential so predictable which means customers could guess codes that we generate and redeem a voucher not meant for them.

I've been reading up on Format Preserving Encryption which seems like it might work well for us. We don't need to decrypt the code back at any point as the code itself is arbitrary we just need to ensure it's not predictable (by everyday people). It's not crucial for security it's just to keep honest people honest.

There are various ciphers referenced in the wikipedia article but I have very basic cryptographic and mathematical skills and am not capable of writing my own code to achieve this based on the ciphers.

I guess my question is, does anyone know of a c# implementation of this that will encrypt an integer into another integer and maintain the same length?

FPE seems to be used well for encrypting a 16 digit credit card number into another 16 digit number. We need the same sort of thing but not necessarily fixed to a length but as long is the plain values length matches the encrypted values length.

So the following four integers would be encrypted

from 123456 123457 123458 123459

to something non-sequential like this

521482 265012 961450 346582

I'm open to any other suggestions to achieve this FPE just seemed like a good option.

EDIT

Thanks for the suggestions around just generating a unique code and storing them and checking for duplicates. for now we've avoided doing this because we don't want to have to check storage when we generate. This is why we use a sequential integer generator so we don't need to check if the code is unique or not. I'll re-investigate doing this but for now still looking for ways to avoid having to go to storage each time we generate a code.

  • 2
    Why use encryption? You could just generate random codes and retry if you get a code you already used. – Blorgbeard Sep 08 '15 at 02:11
  • 1
    Or just generate all 1 million possible codes, shuffle them, then insert to DB table with an autoincrementing ID and "used" flag defaulting to false. – Blorgbeard Sep 08 '15 at 02:13
  • Agreed with @Blorgbeard here. Just use a good source of entropy and a good PRNG and you shouldn't be able to predict future/past values. – lc. Sep 08 '15 at 02:15
  • Thanks for the comments, we've previously implemented similar solutions which have performed badly under load when hitting storage. I'll reconsider that now though and see if it's viable but I'd still like to hear some suggestions around encryption. – Cam Langsford Sep 08 '15 at 02:25
  • @lc. do you have any suggestions for a c# implementation of a PRNG? – Cam Langsford Sep 08 '15 at 02:26
  • You can use the Windows default, exposed by [`RNGCryptoServiceProvider`](https://msdn.microsoft.com/en-us/library/system.security.cryptography.rngcryptoserviceprovider%28v=vs.110%29.aspx). AFAIK the algorithm is undisclosed, but Windows uses this internally to generate private keys, so it should be "good enough" for discount codes. Or, just feed a sequential counter through a one-way hash, mod 1000000. – lc. Sep 08 '15 at 02:31
  • The Random class is a PRNG. – Lorek Sep 08 '15 at 02:33
  • 1
    @Lorek Yeah, but AFAIK it's the standard multiply-and-add-a-constant-mod-something PRNG, which is probably too predictable in this use case. – lc. Sep 08 '15 at 02:35
  • Look at the Random class reference for information on more secure random number generators or just check out the System.Security.Cryptography.RNGCryptoServiceProvider class. – Lorek Sep 08 '15 at 02:44

3 Answers3

1

I wonder if this will not be off base also, but let me give it a try. This solution will require no storage but will require processing power (a tiny amount, but it would not be pencil-and-paper easy). It is essentially a homemade PRNG but may have characteristics more suitable to what you want to do than the built-in ones do.

To make your number generator, make a polynomial with prime coefficients and a prime modulus. For example, let X represent the Nth voucher you issed. Then:

Voucher Number = (23x^4+19x^3+5x^2+29x+3)%65537. This is of course just an example; you could use any number of terms, any primes you want for the coefficients, and you can make the modulus as large as you like. In fact, the modulus does not need to be prime at all. It only sets the maximum voucher number. Having the coefficients be prime helps cut down on collisions.

In this case, vouchers #100, 101, and 102 would have numbers 26158, 12076, and 6949, respectively. Consider it a sort of toy encryption where the coefficients are your key. Not super secure, but nothing with an output space as small as you are asking for would be secure against a strong adversary. But this should stop the everyday fraudster.

To confirm a valid voucher would take the computer (but calculation only, not storage). It would iterate through a few thousand or tens of thousands of input X looking for the output Y that matches the voucher presented to you. When it found the match, it could signal a valid voucher.

Alternatively, you could issue the vouchers with the serial number and the calculation concatenated together, like a value and checksum. Then you could run the calculation on the value by hand using your secret coefficients to confirm validity.

As long as you do not reveal the coefficients to anyone, it is very hard to identify a pattern in the outputs. I am not sure if this is even close to as secure as what you were looking for, but posting the idea just in case.

I miscalculated the output for 100 (did it by hand and failed). Corrected it just now. Let me add some code to illustrate how I'd check for a valid voucher:

using System;
using System.Numerics;

namespace Vouchers
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.Write("Enter voucher number: ");
            BigInteger input = BigInteger.Parse(Console.ReadLine());
            for (BigInteger i = 0;i<10000000;i++)
            {
                BigInteger testValue = (23 * i * i * i * i + 19 * i * i * i + 5 * i * i + 29 * i + 3) % 65537;
                if(testValue==input)
                {
                    Console.WriteLine("That is voucher # " + i.ToString());
                    break;
                }
                if (i == 100) Console.WriteLine(testValue);
            }
            Console.ReadKey();
        }
    }
}
WDS
  • 966
  • 1
  • 9
  • 17
  • Thanks, I am pretty sure that your approach would meet the security requirements just fine so I will look into this. Our performance issues are mainly a concern when we generate the code rather than when we validate it. Like I said my mathematical skills are sub par so I only really understand half of what you are saying but I am trying to squeeze every ounce of my brain right now to try and figure out how I might implement this – Cam Langsford Sep 08 '15 at 04:02
  • The trap-door is not very strong with this one, but it's flexible. The verification could probably be sped up considerably when the minimal valid voucher id can be retrieved from storage or at least a ball park. – Artjom B. Sep 08 '15 at 07:37
0

One option is to build an in-place random permutation of the numbers. Consider this code:

private static readonly Random random = new Random((int)DateTime.UtcNow.Ticks);

private static int GetRandomPermutation(int input)
{
    char[] chars = input.ToString().ToCharArray();
    for (int i = 0; i < chars.Length; i++ )
    {
        int j = random.Next(chars.Length);
        if (j != i)
        {
            char temp = chars[i];
            chars[i] = chars[j];
            chars[j] = temp;
        }
    }
    return int.Parse(new string(chars));
}

You mentioned running into performance issues with some other techniques. This method does a lot of work, so it may not meet your performance requirements. It's a neat academic exercise, anyway.

Lorek
  • 855
  • 5
  • 11
  • thank you for that. Performance is more an issue around storage as our processing is scaled where as storage is not. And I think I've probably put you on the wrong path by my last comment as the result needs to be generated in a consistent way not a random way so we can still have a level of certainty that the result we generate from the sequential number is also unique. – Cam Langsford Sep 08 '15 at 03:05
  • Which pretty much brings me back full circle to why I thought PFE encryption was a good option – Cam Langsford Sep 08 '15 at 03:06
0

Thanks for the help from the comments to my original post on this from Blogbeard and lc. It Turns out we needed to hit storage when generating the codes anyway so this meant implementing a PRNG was a better option for us rather than messing around with encryption.

This is what we ended up doing

  1. Continue to use our sequential number generator to generate integers
  2. Create an instance of C# Random class (a PRNG) using the sequential number as a seed.
  3. Generate a random number within the range of the minimum and maximum number we want.
  4. Check for duplicates and regenerate until we find a unique one

Turns out using c# random with a seed makes the random numbers actually quite predictable when using the sequential number as a seed for each generation.

For example with a range between 1 and 999999 using a sequential seed I tested generating 500000 values without a single collision.

Community
  • 1
  • 1