We want to generate coupon code series and then encrypt those coupon code in numeric only, and that must also be decipherable.

We will be sharing encrypted code as coupon code with customer.

These are the conditions:

  1. Encrypted code must not be in series or not easily able to guess;
  2. Encrypted code must be numeric.
  3. Encrypted code must be having a specific number of digits (think: size/length).

Example :

We generate coupon code from 1 to 100 (needs in millions and plainCode will be either numeric or alphanumeric doesn't matter).

We want share encrypted numeric coupon code with specific digits to user. i.e. if code is 52 then encrypted code gives us between 10000 to 99999 (if 5 digits are specified or it need to be fix number of digits)

We are also able to decrypt the same.


  1. Above is just an example. possibilities are in millions.
  2. Security is not a major concern. just not able to guess is matters a lot.
  3. encrypted code must be in numeric.


Is there an established, cryptographical secure way to do this? If not, what would we need to do to handle this?

Any suggestions or ideas are more than welcome.

We have looked and FPE. Any good algo for the same recommended which return in specific number of digits. We have one which gives encrypted numbers less than specified number but we want in between specific range to maintain number of digits without 0 appending.

  • 2
    $\begingroup$ I don't get it; why not just store a list of coupons somewhere internal to your system, then you just generate them randomly and associate them with a coupon, simple and no need for FPE... is there a requirement I'm missing here? $\endgroup$
    – Thomas
    Oct 22, 2016 at 9:09
  • $\begingroup$ I tried to make an answer, but looking carefully, "Needs in millions" seems entirely incompatible with "between 10000 to 99999" which limits to 90000 distinguishible "encrypted numeric coupon", significantly less than a million. Can you clarify what that thing needed in millions is? Or does the coupon contain two fields, one of which is the 5-digit integer not starting in 0? $\endgroup$
    – fgrieu
    Oct 22, 2016 at 9:19
  • $\begingroup$ @fgrieu Its just an short example. In system we are ready for 10 digit coupon generation. $\endgroup$ Oct 24, 2016 at 6:17
  • $\begingroup$ @Thomas : yes we are doing this right now but its time and resource consuming so we are working on improving this. $\endgroup$ Oct 24, 2016 at 6:18
  • $\begingroup$ I conjecture that in certain special situations the mapping provided by permutation polynomials mod 2**n could be useful to you in practice. Let the range of entities to be mapped be of n1 bits and the range of the associated type or other informations be of n2 bits, then with n=n1+n2 you have a bijective mapping via the said kind of polynomials. $\endgroup$ Jan 21, 2017 at 9:03

2 Answers 2


I have an almost identical requirement for location identifiers I use in a custom inventory system. I have a 35-bit code, which is displayed as 7 5-bit elements, which need to be human readable and unique. The method to generate the codes does not necessarily have to be format preserving, that can just be a side effect of the algorithm used.

The method I use generates a 7 character code, with each character being alphanumeric 0-9 and A-Z, omitting OIZS because they are too similar visually to 0125. Internally the algorithm is a block cipher called Bromine with 5 7-bit elements in a generalized Feistel network, which is based on the round structure of the HIGHT block cipher. The input is a numerical sequence, and the output is a pseudorandom alphanumeric sequence:


Because the input sequence is numeric, there are only 10 million output combinations for the given 34.36 billion possibilities for a 35-bit value. This limits the number of ciphertexts available to reverse engineer the algorithm or determine the key used. The algorithm is simple but has enough rounds to be secure given the key size (in theory). The use environment has no requirement for this kind of security, but it allows it to be used for other applications if needed. The numeric input is a security and usability requirement, and not a limitation of the algorithm.

If used to generate coupon codes, certain input elements or bits can be fixed, and those fixed bits can define the coupon type or discount. The system would only need to keep track of used coupon codes, and decode them to determine the type and validity. Only 1/3436 codes entered randomly will decode to a valid input, and the number of coupons from valid inputs can also be limited further.

For example, your input could be structured as XXYYYYZ, where XX is the coupon type, YYYY is the value, and Z is a fixed element such as 0. Coupon 01 would be assigned to a 5% discount with only 100 allowed coupon uses before it expires. When entered into the system, it is decoded, its structural validity is determined (is numeric and Z=0), XX is determined to be a valid coupon, and YYYY is determined to have not been yet used for a given XX. If all requirements are met, the discount is given, and XXYYYY is saved for comparison with further coupons. Only 0.003% of this example format meets the required structure and only 1% of these decode to the desired coupon type.

If a longer code is required, variants of such a cipher are easily generated in other sizes. The 40-bit version extends the subwords to 8-bits, and is displayed as an 8 character code. A 42-bit version for display as 6-bit 7 character codes is created by extending the 5 subword 7-bit Feistel structure to 6 subwords, and the 48-bit version extends its subwords to 8-bits.

  • 2
    $\begingroup$ I would recommend not showing customers coupon codes generated in this way. Your 35-bit code generator is liable to produce all sorts of unfortunate results (BUMH0LE for example). $\endgroup$
    – r3mainer
    Oct 21, 2016 at 3:55
  • 3
    $\begingroup$ squeamish is correct, accidental obscenity is quite common with character encodings. I would consider using Crockford's variant of Base-32 encoding (crockford.com/wrmg/base32.html) which eliminates the letter U specifically to vastly reduce the chances of accidental (English) obscenity. It works for our customer facing application. Microsoft uses it for license key encoding. Although we did have someone call in once upset their customer number started with "COW". $\endgroup$
    – rmalayter
    Oct 21, 2016 at 4:31
  • $\begingroup$ @squeamishossifrage luckily a series of simple filters can be programmed to exclude those, or they can be manually reviewed after generation $\endgroup$ Oct 21, 2016 at 5:21
  • 2
    $\begingroup$ @Richie_Frame if you were just randomly generating IDs you could filter, but if you are using the encoding deterministicly, such as for encryption or encoding an HMAC, filtering obscenity is non-trivial. "increment and retry" strategies aren't constant time and can really complicate the code. $\endgroup$
    – rmalayter
    Oct 21, 2016 at 12:18

If I understand this correctly, you want to "encrypt" a number, turn it into a different one, and reverse the process? I think this algorithm is ok for this purpose…

Pseudocode: (for your "from number 1 to 100" example)

int i = 1
int key
int max = 100
list(of int) intList
read value for key
do until i = max
if i mod 2 = 0
intList.add(i * (i + key))
intList.add(round((i + _key) * i / 3))
end if

I implemented this algorithm in VB and I got some pretty random looking numbers https://s22.postimg.org/42m3uoy01/bitmap.jpg

And you can reverse the algorithm to "decrypt" the numbers.

I got no collision (2 numbers producing the same value) but some got pretty close. Now if you were to sort the values by length (or even randomize their order) you would have a nice number of coupons that can be reversed to their initial number. Note that the smallest encrypted number doesn't equal the smallest decrypted number etc for each number.



Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.