0

When a user creates an order on my website, the order needs a code which should be unique across the app. I don't want to use a GUID because they're long and ungainly - I just want an alpha numeric code of eight characters or something.

I think its best to pre-generate these codes so that at runtime the code doesn't get stuck looking for something unique.

Furthermore, I was thinking of putting these codes into a separate database so that backing up and moving my main database doesn't involve shuffling around megabytes of random strings - the values that get used will be copied into the order table in the main database.

Does this sound like a good idea?

Ian Warburton
  • 15,170
  • 23
  • 107
  • 189

1 Answers1

1

There is no need for a database. An encryption will give you a random looking set of unique numbers. DES uses a 64 bit block, so by encrypting 0, 1, 2, 3, ... you will get a set of random appearing 64 bit numbers.

There is no standard cypher with a 32 bit blocksize, but you can either write a simple Feistel cypher for yourself (see below) or use Hasty Pudding cypher which has a variable block size.

/**
 * IntegerPerm is a reversible keyed permutation of the integers.
 * This class is not cryptographically secure as the F function
 * is too simple and there are not enough rounds.
 *
 * @author rossum
 */
public final class IntegerPerm {
    //////////////////
    // Private Data //
    //////////////////

    /** Non-zero default key, from www.random.org */
    private final static int DEFAULT_KEY = 0x6CFB18E2;

    private final static int LOW_16_MASK = 0xFFFF;
    private final static int HALF_SHIFT = 16;
    private final static int NUM_ROUNDS = 4;

    /** Permutation key */
    private int mKey;

    /** Round key schedule */
    private int[] mRoundKeys = new int[NUM_ROUNDS];

    //////////////////
    // Constructors //
    //////////////////
    public IntegerPerm() { this(DEFAULT_KEY); }

    public IntegerPerm(int key) { setKey(key); }


    ////////////////////
    // Public Methods //
    ////////////////////
    /** Sets a new value for the key and key schedule. */
    public void setKey(int newKey) {
        assert (NUM_ROUNDS == 4) : "NUM_ROUNDS is not 4";
        mKey = newKey;

        mRoundKeys[0] = mKey & LOW_16_MASK;
        mRoundKeys[1] = ~(mKey & LOW_16_MASK);
        mRoundKeys[2] = mKey >>> HALF_SHIFT;
        mRoundKeys[3] = ~(mKey >>> HALF_SHIFT);
    } // end setKey()

    /** Returns the current value of the key. */
    public int getKey() { return mKey; }

    /**
     * Calculates the enciphered (i.e. permuted) value of the given integer
     * under the current key.
     *
     * @param plain the integer to encipher.
     *
     * @return the enciphered (permuted) value.
     */
    public int encipher(int plain) {
        // 1 Split into two halves.
        int rhs = plain & LOW_16_MASK;
        int lhs = plain >>> HALF_SHIFT;

        // 2 Do NUM_ROUNDS simple Feistel rounds.
        for (int i = 0; i < NUM_ROUNDS; ++i) {
            if (i > 0) {
                // Swap lhs <-> rhs
                final int temp = lhs;
                lhs = rhs;
                rhs = temp;
            } // end if
            // Apply Feistel round function F().
            rhs ^= F(lhs, i);
        } // end for

        // 3 Recombine the two halves and return.
        return (lhs << HALF_SHIFT) + (rhs & LOW_16_MASK);
    } // end encipher()


    /**
     * Calculates the deciphered (i.e. inverse permuted) value of the given
     * integer under the current key.
     *
     * @param cypher the integer to decipher.
     *
     * @return the deciphered (inverse permuted) value.
     */
    public int decipher(int cypher) {
        // 1 Split into two halves.
        int rhs = cypher & LOW_16_MASK;
        int lhs = cypher >>> HALF_SHIFT;

        // 2 Do NUM_ROUNDS simple Feistel rounds.
        for (int i = 0; i < NUM_ROUNDS; ++i) {
            if (i > 0) {
                // Swap lhs <-> rhs
                final int temp = lhs;
                lhs = rhs;
                rhs = temp;
            } // end if
            // Apply Feistel round function F().
            rhs ^= F(lhs, NUM_ROUNDS - 1 - i);
        } // end for

        // 4 Recombine the two halves and return.
        return (lhs << HALF_SHIFT) + (rhs & LOW_16_MASK);
    } // end decipher()


    /////////////////////
    // Private Methods //
    /////////////////////

    // The F function for the Feistel rounds.
    private int F(int num, int round) {
        // XOR with round key.
        num ^= mRoundKeys[round];
        // Square, then XOR the high and low parts.
        num *= num;
        return (num >>> HALF_SHIFT) ^ (num & LOW_16_MASK);
    } // end F()

} // end class IntegerPerm
rossum
  • 15,344
  • 1
  • 24
  • 38
  • Couldn't this result in repeating random numbers? – Ian Warburton Oct 17 '12 at 13:44
  • No. Because it is an encryption it is reversible, and hence there are no repeats. Different inputs will *always* result in different outputs, providing you use the same key. A block cypher (which is what this is) is a permutation of all possible values of the block. The domain and the range are the same size so there are no repeats and no values left out. Stick to a single key value, and encrypt 0, 1, 2, 3, ... in order with no repeats and the output is guaranteed never to repeat until you overflow back to 0. – rossum Oct 17 '12 at 14:20
  • Sounds great. One problem is that I've had pre-generated random order codes in my database for a few years. So I reckon I'd need to change the format otherwise this mechanism might create a duplicate. The same problem would arise if the key and code were compromised as well. (The idea in part is to stop users working out how many orders have been made.) – Ian Warburton Oct 17 '12 at 16:47
  • The key must indeed be kept secret, not so the code. Any attacker is always assumed to know the code. See http://en.wikipedia.org/wiki/Kerckhoffs's_principle Yes, you will need to separate out the old order codes. Perhaps just add a distinguishing character to either the old or the new codes, X##### is old, while K##### is new or whatever. – rossum Oct 17 '12 at 17:15
  • I think that is a weakness of the mechanism though - if not a very serious one. There'd be no way to know if someone has gained access to the key. Normally one might change the key regularly but in this case that would mean changing the format of the resulting code and there are not very many formats for around eight alpha numeric characters. – Ian Warburton Oct 17 '12 at 18:45
  • Have a nine character code, one letter followed by 8 digits. That gives you 26 different keys: the letter indicates which key to use from a small fixed pool of keys. – rossum Oct 17 '12 at 20:35
  • Yes... I suppose if someone could get access to a key they could also access several thousand codes stored in a db. I know this might sound a bit fussy but is there some way of achieving this without having a const character to identify a given key? Is it possible say to know that different keys will restrict their output to a range of values? – Ian Warburton Oct 17 '12 at 22:48
  • It is possible, but you are looking at a more complex implementation. Start by reading http://en.wikipedia.org/wiki/Format-Preserving_Encryption – rossum Oct 18 '12 at 10:52