Decoding small QR codes by hand

It's not hard to decode QR codes with a pen and paper. For this guide I'm going to grab a random QR code from google images and show the process of decoding it by hand. QR codes contain a lot of error correction information and they can survive a lot of errors, but that's a lot harder to do using just a pen and paper (and 99% of the QR codes you encounter don't have any missing bits, so it's rarely necessary). So I'll highlight where the error correction information is stored, but I won't explain it in this guide.

The picture I found is from http://mikejcaruso.blogspot.ca/2011/04/qr-code-tattoos.html

40310107-qr-1

Before we begin we should rotate it to the proper orientation. QR codes always have 3 timing patterns (big black squares) in all the corners except for the bottom-right. So we need to rotate our picture 90 degrees counterclockwise:

40310146-qr-2

The first thing we should learn is what QR code version we're looking at. The version basically just represents the physical size of the QR code. Count the number of pixels (or modules) across the QR code, subtract 17, and divide by 4. For example, our tattoo QR code is 25 modules wide, and (25-17)/4 = 2, which means this is a version 2 QR code. Very big QR codes (versions 7-40) have a few extra features, but most consumer QR codes are fairly small and simple, so you don't need to worry about that.

Next, we will figure out our QR code's format marker. Every QR code stores two identical copies of the format marker, but we only need one of them. The format marker is 15 bits long: 5 bits of format information, and 10 bits for error correction. The first 5 bits of the format marker hold the error correction level (2 bits) and the data mask (3 bits). These 5 bits are found here:

40310322-qr-3

So in our case the format information is 01100. However this number has been XOR'ed with 10101 before being written into the QR code. So we must flip bits 1, 3 and 5. After flipping the bits, we get a format information string of 01100^10101 = 11001. The first two bits of this value are the error correction level (the amount of error correction data included in the QR code). Again, we can ignore this. The last 3 bits of the format string are 001, and this is the most important piece of information. This means the body of the QR code has been masked against the mask number 001. Here is a table of all the possible mask numbers and their appearance:

40310477-qr-mask

Here's where you need the pen and paper. The reason QR codes are masked in the first place is that sometimes particular combinations of data bytes produce QR codes with certain undesirable features (like big empty blocks in the middle). These undesirable features confuse the QR code reader, so the data is masked against a value in order to make the code easier to process when it's scanned by a QR code reader. The computer then unmasks the original data bytes using the same process, and retrieves the data.

You can imagine the masking process as essentially covering the surface of the QR code in one of the patterns seen above, starting from the top left corner. In our case we have a mask reference number 001, which means all of the odd-numbered rows are black. Once we've tiled the surface of our original QR code using the mask pattern, then every black pixel in the mask means we need to invert the corresponding bit in the original QR code. So in our case, we need to (in our mind, or using the pen and paper) invert all of the bits on odd-numbered rows. Note that we only mask the data pixels, and not the timing patterns or the format marker (otherwise we wouldn't know how to unmask it to get the mask reference number!). The data areas are the yellow areas in this picture:

40310738-qr-data

I've highlighted the data areas of the tattoo QR code below:

40310752-qr-5

Note that there's a little island in the middle of the data area that we must work around. That's called an alignment pattern, and whenever you see one in the data just skip past it to read the data bytes.
From now on we need to always remember the mask pattern above, and whenever we read bits from the data section we need to account for the mask pattern, and flip any bits that would be masked off by it (in our case, that's every odd row).

The data section consists of [header][data] chunks. Technically QR codes are allowed to have several of these chunks, but most QR codes just have one big chunk that holds all the data, so it won't matter. The header has an encoding type and a length (the number of data bytes). The encoding type is always 4 bits, but the length is stored in 8-10 bits depending on the encoding type. Here's the encoding type of our tattoo:

40310861-qr-4

But remember, every odd row needs to have its bits inverted. To help us remember, I'll use green to highlight every cell that should be inverted when we read it:

Now, the encoding type is stored as the bottom-right 4 bits, starting from the bottom right and working left and right in a zig-zag motion.

40311191-qr-7

So in our case, the tattoo itself has the bits 1000. However the bottom row is masked, so we invert the first two bits: 0100. This means that our QR code's encoding type is 0100. Here's the table of encoding types:

  • 0001 Numeric
  • 0010 Alphanumeric
  • 0100 8-bit Byte

The other encoding types are rarely used in consumer QR codes. They're used for encoding japanese characters, custom charsets and spreading a message across several QR codes in series. For our purposes these 3 encodings will be enough.

So that means that our QR code uses 8-bit byte encoding. The next piece of information is the length field, or the number of characters (clusters of bits) that are in the message. Like I said, the length field changes size depending on the encoding type. Here's the number of bits in the length field, for each encoding type:

  • Numeric (10 bits)
  • Alphanumeric (9 bits)
  • 8-bit Byte (8 bits)

Since we're using 8-bit byte mode, our length field is 8 bits long. We read the next 8 bits in a vertical zig-zag motion, like this:

So the tattoo itself contains the bits 11011100 (reading from bottom to top). However we need to mask two of these rows, so the actual length field has the value 00010000. That's 16 in decimal, which means this QR code has a message that is sixteen 8-bit bytes long. After the length field, the bytes themselves are stored, one after another, MSB first. We continue climbing in a vertical zig-zag motion until we hit the top, and then we curl over and continue downwards as seen in this picture:

40311284-qr-placement

So the first byte would be 10000110 (masked), which is 01001101 (unmasked). That corresponds to ASCII character 'M'. The next byte would be 10101101 (masked), which is 01100001 (unmasked). That corresponds to ASCII character 'a'. So far we've decoded "Ma".

Continuing up and around the corner: 10100000 (masked), or 01100011 (unmasked). That's ASCII 'c'. Then it's 'i'.

By continuing in this way, we can decode the full message: Maci Clare Peltz.

  • heavyw8t

    So WTF is Maci Clare Peltz supposed to be?

  • qartis

    My guess is someone's girlfriend's name, but I'm not sure.

  • Raymond Decelles

    Probably the tattoo owner's girlfriend, wife, mother, daughter, whatever.

  • Luke

    how do we know that it XORed with 10101?

  • qartis

    ISO 18004:2006, section 6.9.1 paragraph 4 says:

    The 15-bit error corrected format information shall then be XORed with the Mask Pattern 101010000010010,
    in order to ensure that no combination of Error Correction Level and data mask pattern will result in an all-zero
    data string.

    where the first 5 bits of the Mask Pattern are applied to the 5 data bits of the format information.

  • Les Potter

    So how do you zigzag around the alignment thingy? Also, the red box at the top of the QR is only 7 bits wide. Can you explain why?

  • Les Potter

    Your paragraph on the format code says it's 14 bits long, but then below the spec is quoted as saying it 15 bits long. Can you explain?

  • qartis

    Whoops, that was a typo. You're correct, the format code is 15 bits long.

  • qartis

    My previous pictures had a border line incorrectly drawn, making it look like there were only 7 bits along the top edge. I have redone the pictures to hopefully make it a bit more clear. As for zig-zagging around the alignment pattern, the easiest way to think about it is that you simply skip past the bits that are occupied by the alignment pattern. Here's an image from Wikipedia that shows several examples of zig-zags passing near alignment patterns (image credit Walter Tuvell, wtuvell)

  • Martie Anderson

    I'm stuck on deciphering this code. Any takers? Thanks, Martie (sorry it's so faint.)

  • qartis

    Hi Martie, that code isn't a QR code, it's a Data Matrix code. It represents the text string: "358166070831158". I have attached a clearer version here for you to attempt to decode (see the Data Matrix wikipedia article: https://en.wikipedia.org/wiki/Data_Matrix). If there is enough interest I can create a blog post about decoding other 2D barcode formats by hand.

  • Martie Anderson

    Anything you can do would be so helpful. The code was adhered to the back of my phone. Could it be a new format of GPS tracking? Is thishe what is put on produce to track sales? Thanks for all of your help.

  • 刘文景

    If there are some errors in the data code, I wonder how you can decode it?

  • Iri

    https://uploads.disquscdn.com/images/5c239e6cf4e9e5ded58cac253f360a820253225c446f7898317aeb3dbe9b3811.jpg I am stuck decoding a qr code.... I have tried several ways. It was sent a long time ago so maybe it has expired. Any takers?

  • It's a base64 encoded text. Literally, this QR code says:
    ENC;(...):Lz0YbziKr+6AIUiR9l8jwISyBWES4ah/xVBBIMV4eTs=

    QR codes never expire.

  • Iri

    Thank you Felfa! I got the same code from a QR decoder... I have no idea what this code means. Could it be a further encrypted message or is it a line of code you think?

  • PointyOintment

    GPS? It's literally just a label. It was probably used in the factory to track parts and completed phones moving through the assembly and packaging processes.

  • kamrom dechu

    I scanned the tattoo QR code into Pokemon Moon. It gave me a Taillow.

  • Ninan Naner

    May you teach me to decode qr code as show below,please?
    I try several time as follow your solution to decode but I still can't get the answer "A1".

    Thank you very much. I hope you will see my question and answer me in early.

    https://uploads.disquscdn.com/images/51d2418d871d509ef1ee0076aa38b94dc13ea44b3b868f7ec112d3c4939d2de7.jpg

  • David Konsumer

    It's binary data, base64 encoded. You can get the binary by pasting Lz0YbziKr+6AIUiR9l8jwISyBWES4ah/xVBBIMV4eTs= here: http://www.motobit.com/util/base64/decoder

  • Ninan Naner

    I got it. Thank you.

  • Edmund French

    Hi there,

    I'm in the process of decoding a QR code, and using this guide as..well, a guide, but one thing I can't get my head around - the tattoo is clearly 25x25, but the pictures shown which aren't the tattoo are 21x21. I don't have any programming experience or anything similar, so I suspect I'm missing something obvious.. I've gone right up to the 'divide into 8 bit blocks' stage, but don't know how to section it up before I decode, because the sizes don't match.

    Any help greatly appreciated.

  • cal soderberg

    so i scanned exactly 100 QR-Codes and i found 3 diffrent ones that said Maci Clare Peltz. why is that?

  • Dennis Danne

    Because of the version, format and masking stuff. You can encode the same message in different formats, use different masks everytime and if the encoded messages has different byte sizes the QR code's version might change too.

  • Hi, great post!
    I need help or a way to decode something similar but they are different located in technology on Integrated Circuits, can you help? https://uploads.disquscdn.com/images/c9de0de78bc22615bd417f60cd754e955379db5d5187fa5c386e84465446f618.png

  • qartis

    That's a Data Matrix code, and the encoded data is 65654022002GG5VAAA141K501K

  • Hi
    This would the part number but i searched and nothing found, thanks for the help.