5

A company is handing out gift vouchers with a unique code128 barcode. The barcode consist of 12 fixed characters containing meta information and sequence number plus a 4 digit checksum. For example:

B2DS 40453072 6531

  • first 4 characters: identifier of the company who will receive the vouchers
  • next 8 characters: sequential id (ranging from 00000001 to 99999999)
  • last 4 characters: checksum on the previous 12 characters

I wonder if this scheme can be hacked if a certain person receives X vouchers, typically sequential ones. It is obvious in this case to find the meaning of the first 12 characters, how hard is it to deduce to logic used to calculate the checksum?

4
  • 4
    This seems more like a question for cryptography.stackexchange.com. But I suspect that the answer is that it will be quite easy. A better solution might be to just hand out randomly-generated codes, keep a database of the codes you handed out and use rate limiting to prevent people from guessing too many codes in a short period of time. – Philipp Dec 27 '17 at 22:10
  • @Philipp Checksumming is an integrity mechanism, and especially as this is about a specific implication, it seems like it's more relevant to Security.SE than Crypto.SE. – forest Dec 28 '17 at 5:40
  • 3
    If this is gift cards that are essentially money equivalent that people paid for, then this would be criminally insecure. But if this gift card is more like promotional vouchers (e.g. buy 2 get 1 free or 10% off with the voucher), then the serial number is really just a way to track where you received the voucher, which they can use to determine which ads are working and which are flopping. – Lie Ryan Dec 28 '17 at 10:19
  • Are you sure it's a code128 barcode? From what I'm seeing, the code128 check code is modulo 103, so the maximum checksum would be 3 digits (103). I don't see how it could b 6531. – forest Dec 29 '17 at 8:01
10

The code128 checksum is not cryptographically secure.

I think you are misunderstanding the purpose of a checksum. A checksum is designed to make it possible to detect accidental corruption or mistakes in reading data. The data is converted into a checksum, and compared with the expected checksum. If the two differ, you know that there was an error in reading the data. The intention is to reduce the chance that an accidental mistake in reading results in the same checksum. The checksum algorithm itself is public and is easy to calculate, such as with the following function, where code128_table is a specific index table (Code A, B, or C):

uint8_t code128_checksum(uint8_t *buf, uint32_t len)
{
    uint32_t sum = *buf - 32;

    while (--len)
        sum += len * code128_table[*(buf + len)];

    return sum % 103 + 32;
}

This checksum is extremely simple, designed only to make detection of errors during the scanning process possible. It is a "true" sum, meaning that it is nothing more than the summation of the representations of each component character multiplied by a weight factor. Thus the sum is literally just that, a sum of the data it is protecting. It is not designed to provide any sort of cryptographic properties to the barcode. The way it is designed, all the following are trivially possible, in order of increasing computational complexity:

  • Hashing - Given an un-checksummed barcode, calculate the checksum.
  • Collision - Create two barcodes with different contents but an identical checksum.
  • Second preimage - Given a checksummed barcode, create another with the same checksum.
  • First preimage - Given only a checksum, create a barcode with the same checksum.

The only thing which it is (modestly) good at is ensuring that a random change to the barcode will not result in an identical checksum. Even for that, it's not particularly good. A superior (albeit still not cryptographically secure) checksum of the same digest size would be CRC8, as a CRC is superior to a regular checksum for the purpose of error detection. A simple implementation of an 8-bit CRC (where crc8_table is a carefully selected 8-bit CRC polynomial) would look like this:

uint8_t crc8(uint8_t *buf, uint32_t len)
{
    uint8_t crc = ~0;

    while (len--)
        crc = crc8_table[crc ^ *buf++];

    return crc;
}

Regardless of whether you use a simple checksum, CRC, or a significantly more complex and slower cryptographically secure hash function, if the keyspace is merely 8 decimal characters and all 8 characters from the sequential ID are valid for the company ID B2DS, you could, quite trivially, create all 99,999,998 possible barcodes, with matching associated checksums. A completely unoptimized implementation took 19 seconds on my own computer. The keyspace is just too small, even with a cryptographically secure hash.

As @Philipp mentioned in a comment, a better solution would be to hand out barcodes with a random ID. Allow each ID to be used a limited number of times, and monitor for attempts to use a barcode with an ID which has not been explicitly issued. If that is done, then even if there are a thousand valid barcodes at any given time, a random barcode would have only a one in ten thousand chance of being valid, making it far harder to use the codes fraudulently.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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