views:

644

answers:

7

I'm working on an online event ticketing system, where users will be able to self print his tickets and show up at the event where it will be scanned (barcode) and ideally the person will get in. My problem is how to create a "ticket code" that fulfills the following requirements:

  • each "ticket code" need to be sufficiently different from each other (ie not sequentially numbered)
  • ideally the ticket will be checked against a central DB to prevent reuse, but it NEEDS to be able to work off line too, in which case the system has to check for a "valid" ticket code and that it has not been used in this gate.
  • the "ticket code" has to be small enough to facilitate keying it if needed
  • the ticket holder would only need the ticket to get in (ie no ID check)

The range of the data is very small, there will only be about 20 events over 4 days with about 5,000 tickets per event (about 100,000 different ticket codes)

Now I have several fields that are not printed on the ticket and not known to the user that I can use to encode part of the "ticket code", so I could use the EventId, OrderId, EventDate and some salt to create a small "hash" for part of the code (ideas?), but I'm still stuck with the ticket id that is sequential or a GUID (would be too long)

So any ideas or pointers on how to do this?

+5  A: 

I suggest you give the Verhoeff algorithm a try.

Alix Axel
This sounds interesting...
Jaime
+3  A: 

Two ways I can see:

  1. Generate a random number, or at least a random part for a number, and store it in a central database. Then download the database into all the gate systems to check against.
  2. The number has to be self-sufficient. In other words, the number has to be able to check out without the saved list. This sounds like some kind of checksum system. For instance, you could issue numbers from 1 and upwards, make them 5 digits (00000-99999 = 100.000 numbers), and prepende 1-3 letters, making sure you end up with a checksum that would check out.
Lasse V. Karlsen
The problem with no. 1 is that I must be able to work off line, and a ticket could have been bought online just a few minutes earlier, so I could never have an up to date version of the ticket db. Now as for no 2, how would you make one ticket code sufficiently different from the previous one?
Jaime
A: 

For offline verification, I see only one easy solution..

Append to the ticket ID a hash of the ticket ID and a per-event salt. You can truncate any cryptographic hash to the size desired. I can't think of a particular reason to use anything but a random number for the base ticket ID itself.

This allows you to limit the size of the ticket ID and have a clearly proportional security in relation to the size of the ticket ID.

chuck
This is what I'm thinking too, but how valid (in terms of variability and number of hashes) is to use only part of say an MD5 hash?
Jaime
If it's a cryptographically secure hash (even discounting the fact that MD5 has been broken), using part of the hash should provide the expected/proportional decrease in variability and number of hashes. For example, to decrease the hash size from 128 bits to 32 bits (regardless of which 32 bits you choose), you decrease the number of possible hashes from 2^128 to 2^32. The complexity of a brute-force attack or likelyhood of an accidental collision is made proportionally easier as well.
chuck
+1  A: 

Consider a very simple scheme based on a Feistel network to permute, say, the ticket ID number. This message (which happens to be on the PostgreSQL lists but doesn't really have much to do with PostgreSQL) describes a simple Feistel network. On each ticket you could print a ticket ID number (sequentially chosen), then a "ticket secret code" that's the result of putting the ID number through a Feistel network. Possible variations include appending a check digit to the secret code, and basing the input to the Feistel network on more than just the sequentially generated number (number + 10,000 * event ID number, et cetera).

kquinn
Thanks, this pointed me in the right direction combined with a Verhoeff check digit and a hash of what I can reconstruct.
Jaime
A: 

You could do a CRC calculation.

Basically, just start to add each character in the string, and limit the length to a long integer.

You may start with a known random number and store it in the first 4 bytes, and have the last four be a calculation as I described earlier.

This would be two ints, or eight bytes.

James Black
A: 

Here's a scheme that has the advantage of letting you calculate the next ticket hash from the previous (so you can verify whether one is missing), but doesn't let outsiders calculate the next one:

Ticket.0 = substring(HASH(SALT + IV        ), 0, LENGTH)
Ticket.i = substring(HASH(SALT + Ticket.i-1), 0, LENGTH)

where

  • HASH is any hashing function that distributes its entropy relatively evenly across the output string
  • SALT is a constant you keep secret; it's a good idea to use a different one for each event
  • IV is another constant you keep secret
  • LENGTH is the length of the ticket ID you want (10 in your question, but 12 is not out of the question and gives you 256 times as many ticket IDs)
James A. Rosen
That can't be verified if the computer is down. Or are you suggesting the computers at the door calc up to the ticket number from scratch?
jmucchiello
The problem I see is that the computer at the door can't verify if a given ticket is valid, since it does not have all the ticket locally. Or am I missing something?
Jaime
I assume any solution has to have a local copy of the database of all generated tickets. The advantage of this solution is that you can generate the database offline from the 4-tuple<SALT, IV, LENGTH, numberOfTicketsSold>. And if numberOfTicketsSold changes (b/c someone buys one online 7 minutes before the show), it's easy to append more values to the database. Yet the values are hard to calculate without the 4-tuple.
James A. Rosen
@jmucchiello you can't have "the computers at the door calc up to the ticket number from scratch" because the ticket # might be invalid, so the program would never halt. That's why you need numberOfTicketsSold.
James A. Rosen
Ideally I would have access to the central db where I can verify that the ticket is a) valid and b) has not been used before. The problem lies where I lose all the connection to the central db, so I can't get updates into the local db and I can't verify valid/unused ticket numbers, in which case, in order not to block the entry of people, is "reasonable" to just make sure the ticket code is not forged and that hasn't been used at that gate (that info can be kept locally), an let the person in.
Jaime
+4  A: 

Why reinvent the wheel? Just do something like this (Python Code, ask me if you need clarification):

import hashlib

secretpassword = "blah"

def createticket(eventnum, ticketnum):
    m = hashlib.md5() # or any crypto hash you like
    m.update("%s%s%s" % (eventnum, ticketnum, secretpassword))
    return m.hexdigest()[:10]

Example:

Event Number 1

Ticket Number 123

createticket(1,123)
# output: 2d7f242597

Mr ticketman comes around with his verifier and enters in the event/ticket number and the hash:

def verifier(eventnum, ticketnum, hash):
    return hash == createticket(eventnum, ticketnum)

verifier(1,123, "2d7f242597")
# ouput: True
Unknown
Maybe I'm not getting this right, but my problem with a full hash (ala md5) is that I don't have the ticket number for verification, so I cannot create a hash to verify against. This may be a different question, how valid is to just use the first x digits of the hash?
Jaime
@jaimedp, that's why when you print the ticket, you include the event number and the ticket number along with the hash. It is completely valid to use the first x digits of the hash because all good cryptographic hashes should distribute entropy to all digits (avalanche effect).
Unknown
@jaimedp, also when you print out the barcode, just encode the event number and ticket number as the first few positions. There's no reason for you not to have the information. In fact, this solution is practically the same as the one that you accepted except that you have to muck around with your own block cipher.
Unknown
@Jaime: This is the correct answer. Writing a custom hash is not only certainly less secure, but *(more importantly, for your case)* is much more work!
BlueRaja - Danny Pflughoeft