10

I'm trying to design a promo code system for a server app I'm designing. Basically, I generate a promo code for a certain item and give the code out to someone, who then types it into the server.

Currently, the generator I wrote gives out a 20-character alphanumeric code, but the codes are too easy to manipulate. To fix this issue, I'd like to implement some sort of symmetric encryption (so that the generator app can make the code, but the server can also read it) to keep users from figuring out how codes are generated.

Is there a symmetric encryption algorithm I can use that will return 20 alphanumeric characters?

If possible, I'd like to find an algorithm that can easily be implemented into Java code.

poncho
  • 154,064
  • 12
  • 239
  • 382
Kaz Wolfe
  • 203
  • 2
  • 8

3 Answers3

14

Both of the other answers tackle the question of encryption in a particular format, but I would argue that neither of them is necessarily a good fit for your use case. You want to be able to generate 20 character codes that a server will be able to verify. A symmetric MAC is sufficient for this use case, if you don't need the codes to contain any secret information.

For example, use the format ID||HMAC(key, ID), with ID an arbitrary unique number for each code. You can choose the ID size and then truncate the HMAC to match your length requirements. E.g. 32-bit ID and 168 left bits of HMAC-SHA256. Then encode in the format you want.

IDs can be generated randomly or using a counter or it can include encoded information. The server would store the association between an ID number and whatever the promo code is for etc. An attacker cannot modify a promo code without knowing the MAC key, except by chance with probability $2^{-l}$, where $l$ is the MAC length in bits. You can go as low as $l = 64$ securely. (Perhaps even lower if you make an online guessing attack sufficiently difficult/slow.)

otus
  • 32,462
  • 5
  • 75
  • 167
7

What you are asking is a straight application for Format Preserving Encryption, which builds ciphers which input and output are in a constrained format (generically: common to input and output, hence preserved). The FPE field has many articles with proven techniques; and proposed standards, including BPS and SP800-38G Draft.

Specifically, it looks like you want a cipher on the space of 20-characters alphanumeric codes, perhaps with some restriction or special rule in consideration of the operator entering the value into the server; if you restrict to uppercase and digits, and assimilate 0125 to OIZS, you are left with $26+10-4=32=2^5$ symbols, and your message space has a nice $100$-bit size. Among many simple techniques to build a cipher on this space, you could use a Feistel construction with AES as the round function. Adapting an earlier answer:

The AES block cipher is used with a fixed secret key

parameters:
    B = 50                        // half the number of bits per block
    N = 8                         // number of rounds (could be lowered)

enciphering plaintext block P, assumed to be 2*B bits
    L = P>>B                      // extract left  B bits
    R = P & ((1<<B)-1)            // extract right B bits
    for I from 1 to N             // round loop
      encipher ((I<<B) | R) with AES, keep the B right bits H
      L = L ^ H
      exchange R and L
    C = (R<<B) | L                // append the halves, with R on the left
    output ciphertext block C

deciphering ciphertext block C, assumed to be 2*B bits
    L = C>>B                      // extract left  B bits
    R = C & ((1<<B)-1)            // extract right B bits
    for I from N downto 1         // round loop
      encipher ((I<<B)|R) with AES, keep the B right bits H
      L = L ^ H
      exchange R and L
    P = (R<<B) | L                // append the halves, with R on the left
    output plaintext block P

Notice that after deciphering the enciphered alphanumeric code, you need to check enough of the recovered plaintext (perhaps, the last 10 symbols out of 20 must be A) so that a wrong ciphertext (resulting in an essentially random plaintext) will fail the check with high confidence.

fgrieu
  • 149,326
  • 13
  • 324
  • 622
2

Based on the clarifications in the comments, what you are looking for is a block cipher over 100 bits. This will enable you to Base32 encode into a 20 byte string, and to decrypt as well. Note that encrypting directly with a block cipher is in general not secure. However, I assume that with promo codes you will always encrypt a unique plaintext. If this assumption is correct, then using a block cipher (pseudorandom permutation) is secure.

In order to achieve this, you just need to build a Feistel structure, where in each round you use AES truncated to 50 bits. Note that the input to each round is 50 bits as well, so you must pad this to 128. First, you have to pad the round number (0,1,2,3) and then the rest can just be zeroes (it need not be reversible). I stress that the round number must be included! This will give you an invertible pseudorandom permutation, by the famed Luby-Rackoff theorem. For 100 bits, you would need 12 rounds of Feistel (this is the current recommendation).

Yehuda Lindell
  • 28,270
  • 1
  • 69
  • 86