{% extends "base.html" %} {% block title %}Card IDs{% endblock %} {% block body %}

Card ID generation

I'm just here for code.

Fair. My intent with these pages is to describe things in enough detail that they should be simple to implement yourself, but this is one of those things that's quite easy to just drop in some pre-made code for. My local implementation is in python, so that's all you're getting :). As a free bonus, have some test cases too. It's not great code by any stretch, and it liberally uses assertions rather than proper exceptions, but it should be a good enough starting point for your own version.

{% highlight 'python' %}
import binascii
from Crypto.Cipher import DES3


KEY = b""  # Check the DES section for this
_KEY = bytes(i * 2 for i in KEY)  # Preprocess the key

ALPHABET = "0123456789ABCDEFGHJKLMNPRSTUWXYZ"


def enc_des(uid):
    cipher = DES3.new(_KEY, DES3.MODE_CBC, iv=b'\0' * 8)
    return cipher.encrypt(uid)


def dec_des(uid):
    cipher = DES3.new(_KEY, DES3.MODE_CBC, iv=b'\0' * 8)
    return cipher.decrypt(uid)


def checksum(data):
chk = sum(data[i] * (i % 3 + 1) for i in range(15))

while chk > 31:
    chk = (chk >> 5) + (chk & 31)

return chk


def pack_5(data):
    data = "".join(f"{i:05b}" for i in data)
    if len(data) % 8 != 0:
        data += "0" * (8 - (len(data) % 8))
    return bytes(int(data[i:i+8], 2) for i in range(0, len(data), 8))


def unpack_5(data):
    data = "".join(f"{i:08b}" for i in data)
    if len(data) % 5 != 0:
        data += "0" * (5 - (len(data) % 5))
    return bytes(int(data[i:i+5], 2) for i in range(0, len(data), 5))


def to_konami_id(uid):
    assert len(uid) == 16, "UID must be 16 bytes"

    if uid.upper().startswith("E004"):
        card_type = 1
    elif uid.upper().startswith("0"):
        card_type = 2
    else:
        raise ValueError("Invalid UID prefix")

    kid = binascii.unhexlify(uid)
    assert len(kid) == 8, "ID must be 8 bytes"

    out = bytearray(unpack_5(enc_des(kid[::-1]))[:13]) + b'\0\0\0'

    out[0] ^= card_type
    out[13] = 1
    for i in range(1, 14):
        out[i] ^= out[i - 1]
    out[14] = card_type
    out[15] = checksum(out)

    return "".join(ALPHABET[i] for i in out)


def to_uid(konami_id):
    if konami_id[14] == "1":
        card_type = 1
    elif konami_id[14] == "2":
        card_type = 2
    else:
        raise ValueError("Invalid ID")

    assert len(konami_id) == 16, f"ID must be 16 characters"
    assert all(i in ALPHABET for i in konami_id), "ID contains invalid characters"
    card = [ALPHABET.index(i) for i in konami_id]
    assert card[11] % 2 == card[12] % 2, "Parity check failed"
    assert card[13] == card[12] ^ 1, "Card invalid"
    assert card[15] == checksum(card), "Checksum failed"

    for i in range(13, 0, -1):
        card[i] ^= card[i - 1]

    card[0] ^= card_type

    card_id = dec_des(pack_5(card[:13])[:8])[::-1]
    card_id = binascii.hexlify(card_id).decode().upper()

    if card_type == 1:
        assert card_id[:4] == "E004", "Invalid card type"
    elif card_type == 2:
        assert card_id[0] == "0", "Invalid card type"
    return card_id


if __name__ == "__main__":
    assert to_konami_id("0000000000000000") == "007TUT8XJNSSPN2P", "To KID failed"
    assert to_uid("007TUT8XJNSSPN2P") == "0000000000000000", "From KID failed"
    assert to_uid(to_konami_id("000000100200F000")) == "000000100200F000", "Roundtrip failed"
{% endhighlight %}

e-Amusement cards use 16 digit IDs. KONAMI IDs are also 16 digits. Are they related? Yes! In fact, KONAMI IDs are derived from the ID stored on the e-Amusement card.

KONAMI IDs have an alphabet of 0123456789ABCDEFGHJKLMNPRSTUWXYZ (note that IOQV are absent), whereas e-A IDs (yeah I'm not typing that out every time) have an alphabet of 0123456789ABCDEF (hex). It stands to reason then that there's additional information present in KONAMI IDs, as they are the same length, but can hold a greater density of information. That intuition would be correct.

Converting KONAMI IDs to e-Amusement IDs

Let's take a look at the format of KONAMI IDs. The first step before we can do anything is to convert it from a string to a series of integers. Each byte is replaced with its index in the alphabet, giving us 16 values ranging from 0 through 31. These bytes has the following meanings:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
e-Amusement ID Check byte Card type Checksum

Due to how IDs are constructed, there are a number of checks we can perform to validate an ID:

To decrypt a KONAMI ID, at a high level we must:

As we'll see in the next section, card IDs have an XOR pass performed. This is what allows for the above encoding check, but we must remove it before we can begin decoding. A line of code speaks a thousand words, so have three:

for i from 13 to 1 inclusive:
    card[i] ^= card[i - 1]
card[0] ^= card_type

The values in the e-Amusement ID field above will all maximally be 31 (0b11111) therefore before we perform the decryption step we first densly pack these 5-bit integers. That is, 0b11111 0b00000 0b11111 would be packed to 0b11111000 0b00111110. This value will be 8 bytes long. We can now decrypt it, reverse it, and convert to hex.

Implementing 5-bit packing

In most languages, implementing a packer can be done one of two ways. The approach chosen by Bemani is to first create a table of every individual bit (each stored in a whole byte!), then iterate through the bits ORing them together. This is a simple but wasteful implementation. The other approach is to use a buffer byte and slowly shift values in, tracking how many bits are stored in the buffer byte, and performing different actions depending on how many bits this is. For both packing and unpacking this requires three cases. It's somewhat more complex, but less wasteful in terms of memory usage. Pick your poison I suppose.

In most languages?

Haha well you see we can actually cheat and use string manipulation. Wasteful? Incredibly. Efficient? Not at all. Quick and easy? Yup!

{% highlight "python" %}def pack_5(data):
    data = "".join(f"{i:05b}" for i in data)
    if len(data) % 8 != 0:
        data += "0" * (8 - (len(data) % 8))
    return bytes(int(data[i:i+8], 2) for i in range(0, len(data), 8))

def unpack_5(data):
    data = "".join(f"{i:08b}" for i in data)
    if len(data) % 5 != 0:
        data += "0" * (5 - (len(data) % 5))
    return bytes(int(data[i:i+5], 2) for i in range(0, len(data), 5)){% endhighlight %}

If your language of choice allows this, and you don't care for efficiency, this can be a great time-saver towards get something working. Truth be told my local implementation originally used the Bemani method (it was a line-for-line port, after all), switched to the second method, then I opted for this hacky string method in the name of code clarity.

Converting e-Amusement IDs to KONAMI IDs

This is mostly the above process, in reverse, but we need to make sure to populate some of the extra check bytes.

Before we start, we need to make sure we have a valid card! FeliCa cards (type 2) will begin with a single null nibble, and magnetic stripe cards (type 1) with the word E004. We then parse the entire ID as a hex string, giving us an 8-byte value.

This value is reversed, and encrypted. After encryption, we need to unpack it from it's 5-packed format. This is the same process as unpacking, but reversed. The unpacked data can be ambiguous in length. It's 13 bytes. If your unpacker produces a 14th byte, it'll be null and can be discarded.

We pad the 13 bytes with 3 extra null bytes, then apply our checks to the ID:

card[0] ^= card_type
card[13] = 1
for i from 0 to 13 inclusive:
    card[i + 1] ^= card[i]
card[14] = card_type
card[15] = checksum(card)

This leaves us with 16 values ranging from 0 to 31, which we apply as indecies to our alphabet to produce the final ID.

Checksums

As if the encryption and XOR wasn't enough, card IDs also contain a checksum to make absolutely sure the card is valid. I could explain in words how the checksum works, but that's probably not very useful. Have a pseudocode snippet instead:

checksum(bytes):
    chk = 0
    for i from 0 to 14 inclusive:
        chk += bytes[i] * (i % 3 + 1)

    while chk > 31:
        chk = (chk >> 5) + (chk & 31)

    return chk

The DES scheme used

For whatever reason, Bemani decided that IDs should be encrypted. Thankfully however they used triple DES, which almost certainly has an existing implementation in your language of choice. It is triple DES, in CBC mode, with a totally null IV. The key is quite easy to find if you hit the right binaries with strings. Alternatively, check the source of this page. The key contains characters that are all within the ASCII range. Before we can use it with DES, the value of every byte needs doubled. This was presumably done to give the values more range, but I sincerely doubt it adds any additional security.

I'm curious how Bemani implemented this in their own code!

Curiosity is a great thing. Unfortunately, this is code that is implement within the game specific DLL files. If you happen to have SDXV 4 in front of you too, head over over to soundvoltex.dll:0x1027316f and you should see everything you need.

As part of breaking down how this all works, I produced a more or less line-for-line Python port of the game code, for testing, validation, etc.. It's not especially pretty, but should give you an idea of how it works under the hood. One interesting observation is that it looks like the initial and final permutation steps were inlined. It's also possible that they did the whole thing with macros rather than inline functions. Either way, my python port didn't do any cleaning up, because we can just use a DES library.

Show me that!
{% highlight "python" %}DES_KEYMAP = [
    [0x02080008, 0x02082000, 0x00002008, 0x00000000, 0x02002000, 0x00080008, 0x02080000, 0x02082008, 0x00000008, 0x02000000, 0x00082000, 0x00002008, 0x00082008, 0x02002008, 0x02000008, 0x02080000, 0x00002000, 0x00082008, 0x00080008, 0x02002000, 0x02082008, 0x02000008, 0x00000000, 0x00082000, 0x02000000, 0x00080000, 0x02002008, 0x02080008, 0x00080000, 0x00002000, 0x02082000, 0x00000008, 0x00080000, 0x00002000, 0x02000008, 0x02082008, 0x00002008, 0x02000000, 0x00000000, 0x00082000, 0x02080008, 0x02002008, 0x02002000, 0x00080008, 0x02082000, 0x00000008, 0x00080008, 0x02002000, 0x02082008, 0x00080000, 0x02080000, 0x02000008, 0x00082000, 0x00002008, 0x02002008, 0x02080000, 0x00000008, 0x02082000, 0x00082008, 0x00000000, 0x02000000, 0x02080008, 0x00002000, 0x00082008],
    [0x08000004, 0x00020004, 0x00000000, 0x08020200, 0x00020004, 0x00000200, 0x08000204, 0x00020000, 0x00000204, 0x08020204, 0x00020200, 0x08000000, 0x08000200, 0x08000004, 0x08020000, 0x00020204, 0x00020000, 0x08000204, 0x08020004, 0x00000000, 0x00000200, 0x00000004, 0x08020200, 0x08020004, 0x08020204, 0x08020000, 0x08000000, 0x00000204, 0x00000004, 0x00020200, 0x00020204, 0x08000200, 0x00000204, 0x08000000, 0x08000200, 0x00020204, 0x08020200, 0x00020004, 0x00000000, 0x08000200, 0x08000000, 0x00000200, 0x08020004, 0x00020000, 0x00020004, 0x08020204, 0x00020200, 0x00000004, 0x08020204, 0x00020200, 0x00020000, 0x08000204, 0x08000004, 0x08020000, 0x00020204, 0x00000000, 0x00000200, 0x08000004, 0x08000204, 0x08020200, 0x08020000, 0x00000204, 0x00000004, 0x08020004],
    [0x80040100, 0x01000100, 0x80000000, 0x81040100, 0x00000000, 0x01040000, 0x81000100, 0x80040000, 0x01040100, 0x81000000, 0x01000000, 0x80000100, 0x81000000, 0x80040100, 0x00040000, 0x01000000, 0x81040000, 0x00040100, 0x00000100, 0x80000000, 0x00040100, 0x81000100, 0x01040000, 0x00000100, 0x80000100, 0x00000000, 0x80040000, 0x01040100, 0x01000100, 0x81040000, 0x81040100, 0x00040000, 0x81040000, 0x80000100, 0x00040000, 0x81000000, 0x00040100, 0x01000100, 0x80000000, 0x01040000, 0x81000100, 0x00000000, 0x00000100, 0x80040000, 0x00000000, 0x81040000, 0x01040100, 0x00000100, 0x01000000, 0x81040100, 0x80040100, 0x00040000, 0x81040100, 0x80000000, 0x01000100, 0x80040100, 0x80040000, 0x00040100, 0x01040000, 0x81000100, 0x80000100, 0x01000000, 0x81000000, 0x01040100],
    [0x04010801, 0x00000000, 0x00010800, 0x04010000, 0x04000001, 0x00000801, 0x04000800, 0x00010800, 0x00000800, 0x04010001, 0x00000001, 0x04000800, 0x00010001, 0x04010800, 0x04010000, 0x00000001, 0x00010000, 0x04000801, 0x04010001, 0x00000800, 0x00010801, 0x04000000, 0x00000000, 0x00010001, 0x04000801, 0x00010801, 0x04010800, 0x04000001, 0x04000000, 0x00010000, 0x00000801, 0x04010801, 0x00010001, 0x04010800, 0x04000800, 0x00010801, 0x04010801, 0x00010001, 0x04000001, 0x00000000, 0x04000000, 0x00000801, 0x00010000, 0x04010001, 0x00000800, 0x04000000, 0x00010801, 0x04000801, 0x04010800, 0x00000800, 0x00000000, 0x04000001, 0x00000001, 0x04010801, 0x00010800, 0x04010000, 0x04010001, 0x00010000, 0x00000801, 0x04000800, 0x04000801, 0x00000001, 0x04010000, 0x00010800],
    [0x00000400, 0x00000020, 0x00100020, 0x40100000, 0x40100420, 0x40000400, 0x00000420, 0x00000000, 0x00100000, 0x40100020, 0x40000020, 0x00100400, 0x40000000, 0x00100420, 0x00100400, 0x40000020, 0x40100020, 0x00000400, 0x40000400, 0x40100420, 0x00000000, 0x00100020, 0x40100000, 0x00000420, 0x40100400, 0x40000420, 0x00100420, 0x40000000, 0x40000420, 0x40100400, 0x00000020, 0x00100000, 0x40000420, 0x00100400, 0x40100400, 0x40000020, 0x00000400, 0x00000020, 0x00100000, 0x40100400, 0x40100020, 0x40000420, 0x00000420, 0x00000000, 0x00000020, 0x40100000, 0x40000000, 0x00100020, 0x00000000, 0x40100020, 0x00100020, 0x00000420, 0x40000020, 0x00000400, 0x40100420, 0x00100000, 0x00100420, 0x40000000, 0x40000400, 0x40100420, 0x40100000, 0x00100420, 0x00100400, 0x40000400],
    [0x00800000, 0x00001000, 0x00000040, 0x00801042, 0x00801002, 0x00800040, 0x00001042, 0x00801000, 0x00001000, 0x00000002, 0x00800002, 0x00001040, 0x00800042, 0x00801002, 0x00801040, 0x00000000, 0x00001040, 0x00800000, 0x00001002, 0x00000042, 0x00800040, 0x00001042, 0x00000000, 0x00800002, 0x00000002, 0x00800042, 0x00801042, 0x00001002, 0x00801000, 0x00000040, 0x00000042, 0x00801040, 0x00801040, 0x00800042, 0x00001002, 0x00801000, 0x00001000, 0x00000002, 0x00800002, 0x00800040, 0x00800000, 0x00001040, 0x00801042, 0x00000000, 0x00001042, 0x00800000, 0x00000040, 0x00001002, 0x00800042, 0x00000040, 0x00000000, 0x00801042, 0x00801002, 0x00801040, 0x00000042, 0x00001000, 0x00001040, 0x00801002, 0x00800040, 0x00000042, 0x00000002, 0x00001042, 0x00801000, 0x00800002],
    [0x10400000, 0x00404010, 0x00000010, 0x10400010, 0x10004000, 0x00400000, 0x10400010, 0x00004010, 0x00400010, 0x00004000, 0x00404000, 0x10000000, 0x10404010, 0x10000010, 0x10000000, 0x10404000, 0x00000000, 0x10004000, 0x00404010, 0x00000010, 0x10000010, 0x10404010, 0x00004000, 0x10400000, 0x10404000, 0x00400010, 0x10004010, 0x00404000, 0x00004010, 0x00000000, 0x00400000, 0x10004010, 0x00404010, 0x00000010, 0x10000000, 0x00004000, 0x10000010, 0x10004000, 0x00404000, 0x10400010, 0x00000000, 0x00404010, 0x00004010, 0x10404000, 0x10004000, 0x00400000, 0x10404010, 0x10000000, 0x10004010, 0x10400000, 0x00400000, 0x10404010, 0x00004000, 0x00400010, 0x10400010, 0x00004010, 0x00400010, 0x00000000, 0x10404000, 0x10000010, 0x10400000, 0x10004010, 0x00000010, 0x00404000],
    [0x00208080, 0x00008000, 0x20200000, 0x20208080, 0x00200000, 0x20008080, 0x20008000, 0x20200000, 0x20008080, 0x00208080, 0x00208000, 0x20000080, 0x20200080, 0x00200000, 0x00000000, 0x20008000, 0x00008000, 0x20000000, 0x00200080, 0x00008080, 0x20208080, 0x00208000, 0x20000080, 0x00200080, 0x20000000, 0x00000080, 0x00008080, 0x20208000, 0x00000080, 0x20200080, 0x20208000, 0x00000000, 0x00000000, 0x20208080, 0x00200080, 0x20008000, 0x00208080, 0x00008000, 0x20000080, 0x00200080, 0x20208000, 0x00000080, 0x00008080, 0x20200000, 0x20008080, 0x20000000, 0x20200000, 0x00208000, 0x20208080, 0x00008080, 0x00208000, 0x20200080, 0x00200000, 0x20000080, 0x20008000, 0x00000000, 0x00008000, 0x00200000, 0x20200080, 0x00208080, 0x20000000, 0x20208000, 0x00000080, 0x20008080]
]

DES_ROTORS = [0x22, 0x0D, 0x05, 0x2E, 0x2F, 0x12, 0x20, 0x29, 0x0B, 0x35, 0x21, 0x14, 0x0E, 0x24, 0x1E, 0x18, 0x31, 0x02, 0x0F, 0x25, 0x2A, 0x32, 0x00, 0x15, 0x26, 0x30, 0x06, 0x1A, 0x27, 0x04, 0x34, 0x19, 0x0C, 0x1B, 0x1F, 0x28, 0x01, 0x11, 0x1C, 0x1D, 0x17, 0x33, 0x23, 0x07, 0x03, 0x16, 0x09, 0x2B, 0x29, 0x14, 0x0C, 0x35, 0x36, 0x19, 0x27, 0x30, 0x12, 0x1F, 0x28, 0x1B, 0x15, 0x2B, 0x25, 0x00, 0x01, 0x09, 0x16, 0x2C, 0x31, 0x02, 0x07, 0x1C, 0x2D, 0x37, 0x0D, 0x21, 0x2E, 0x0B, 0x06, 0x20, 0x13, 0x22, 0x26, 0x2F, 0x08, 0x18, 0x23, 0x24, 0x1E, 0x03, 0x2A, 0x0E, 0x0A, 0x1D, 0x10, 0x32, 0x37, 0x22, 0x1A, 0x26, 0x0B, 0x27, 0x35, 0x05, 0x20, 0x2D, 0x36, 0x29, 0x23, 0x02, 0x33, 0x0E, 0x0F, 0x17, 0x24, 0x03, 0x08, 0x10, 0x15, 0x2A, 0x06, 0x0C, 0x1B, 0x2F, 0x1F, 0x19, 0x14, 0x2E, 0x21, 0x30, 0x34, 0x04, 0x16, 0x07, 0x31, 0x32, 0x2C, 0x11, 0x01, 0x1C, 0x18, 0x2B, 0x1E, 0x09, 0x0C, 0x30, 0x28, 0x34, 0x19, 0x35, 0x26, 0x13, 0x2E, 0x06, 0x0B, 0x37, 0x31, 0x10, 0x0A, 0x1C, 0x1D, 0x25, 0x32, 0x11, 0x16, 0x1E, 0x23, 0x01, 0x14, 0x1A, 0x29, 0x04, 0x2D, 0x27, 0x22, 0x1F, 0x2F, 0x05, 0x0D, 0x12, 0x24, 0x15, 0x08, 0x09, 0x03, 0x00, 0x0F, 0x2A, 0x07, 0x02, 0x2C, 0x17, 0x1A, 0x05, 0x36, 0x0D, 0x27, 0x26, 0x34, 0x21, 0x1F, 0x14, 0x19, 0x0C, 0x08, 0x1E, 0x18, 0x2A, 0x2B, 0x33, 0x09, 0x00, 0x24, 0x2C, 0x31, 0x0F, 0x22, 0x28, 0x37, 0x12, 0x06, 0x35, 0x30, 0x2D, 0x04, 0x13, 0x1B, 0x20, 0x32, 0x23, 0x16, 0x17, 0x11, 0x0E, 0x1D, 0x01, 0x15, 0x10, 0x03, 0x25, 0x28, 0x13, 0x0B, 0x1B, 0x35, 0x34, 0x0D, 0x2F, 0x2D, 0x22, 0x27, 0x1A, 0x16, 0x2C, 0x07, 0x01, 0x02, 0x0A, 0x17, 0x0E, 0x32, 0x03, 0x08, 0x1D, 0x30, 0x36, 0x0C, 0x20, 0x14, 0x26, 0x05, 0x06, 0x12, 0x21, 0x29, 0x2E, 0x09, 0x31, 0x24, 0x25, 0x00, 0x1C, 0x2B, 0x0F, 0x23, 0x1E, 0x11, 0x33, 0x36, 0x21, 0x19, 0x29, 0x26, 0x0D, 0x1B, 0x04, 0x06, 0x30, 0x35, 0x28, 0x24, 0x03, 0x15, 0x0F, 0x10, 0x18, 0x25, 0x1C, 0x09, 0x11, 0x16, 0x2B, 0x05, 0x0B, 0x1A, 0x2E, 0x22, 0x34, 0x13, 0x14, 0x20, 0x2F, 0x37, 0x1F, 0x17, 0x08, 0x32, 0x33, 0x0E, 0x2A, 0x02, 0x1D, 0x31, 0x2C, 0x00, 0x0A, 0x0B, 0x2F, 0x27, 0x37, 0x34, 0x1B, 0x29, 0x12, 0x14, 0x05, 0x26, 0x36, 0x32, 0x11, 0x23, 0x1D, 0x1E, 0x07, 0x33, 0x2A, 0x17, 0x00, 0x24, 0x02, 0x13, 0x19, 0x28, 0x1F, 0x30, 0x0D, 0x21, 0x22, 0x2E, 0x04, 0x0C, 0x2D, 0x25, 0x16, 0x09, 0x0A, 0x1C, 0x01, 0x10, 0x2B, 0x08, 0x03, 0x0E, 0x18, 0x12, 0x36, 0x2E, 0x05, 0x06, 0x22, 0x30, 0x19, 0x1B, 0x0C, 0x2D, 0x04, 0x02, 0x18, 0x2A, 0x24, 0x25, 0x0E, 0x03, 0x31, 0x1E, 0x07, 0x2B, 0x09, 0x1A, 0x20, 0x2F, 0x26, 0x37, 0x14, 0x28, 0x29, 0x35, 0x0B, 0x13, 0x34, 0x2C, 0x1D, 0x10, 0x11, 0x23, 0x08, 0x17, 0x32, 0x0F, 0x0A, 0x15, 0x00, 0x20, 0x0B, 0x1F, 0x13, 0x14, 0x30, 0x05, 0x27, 0x29, 0x1A, 0x06, 0x12, 0x10, 0x07, 0x01, 0x32, 0x33, 0x1C, 0x11, 0x08, 0x2C, 0x15, 0x02, 0x17, 0x28, 0x2E, 0x04, 0x34, 0x0C, 0x22, 0x36, 0x37, 0x26, 0x19, 0x21, 0x0D, 0x03, 0x2B, 0x1E, 0x00, 0x31, 0x16, 0x25, 0x09, 0x1D, 0x18, 0x23, 0x0E, 0x2E, 0x19, 0x2D, 0x21, 0x22, 0x05, 0x13, 0x35, 0x37, 0x28, 0x14, 0x20, 0x1E, 0x15, 0x0F, 0x09, 0x0A, 0x2A, 0x00, 0x16, 0x03, 0x23, 0x10, 0x25, 0x36, 0x1F, 0x12, 0x0D, 0x1A, 0x30, 0x0B, 0x0C, 0x34, 0x27, 0x2F, 0x1B, 0x11, 0x02, 0x2C, 0x0E, 0x08, 0x24, 0x33, 0x17, 0x2B, 0x07, 0x31, 0x1C, 0x1F, 0x27, 0x06, 0x2F, 0x30, 0x13, 0x21, 0x26, 0x0C, 0x36, 0x22, 0x2E, 0x2C, 0x23, 0x1D, 0x17, 0x18, 0x01, 0x0E, 0x24, 0x11, 0x31, 0x1E, 0x33, 0x0B, 0x2D, 0x20, 0x1B, 0x28, 0x05, 0x19, 0x1A, 0x0D, 0x35, 0x04, 0x29, 0x00, 0x10, 0x03, 0x1C, 0x16, 0x32, 0x0A, 0x25, 0x02, 0x15, 0x08, 0x2A, 0x2D, 0x35, 0x14, 0x04, 0x05, 0x21, 0x2F, 0x34, 0x1A, 0x0B, 0x30, 0x1F, 0x03, 0x31, 0x2B, 0x25, 0x07, 0x0F, 0x1C, 0x32, 0x00, 0x08, 0x2C, 0x0A, 0x19, 0x06, 0x2E, 0x29, 0x36, 0x13, 0x27, 0x28, 0x1B, 0x26, 0x12, 0x37, 0x0E, 0x1E, 0x11, 0x2A, 0x24, 0x09, 0x18, 0x33, 0x10, 0x23, 0x16, 0x01, 0x06, 0x26, 0x22, 0x12, 0x13, 0x2F, 0x04, 0x0D, 0x28, 0x19, 0x05, 0x2D, 0x11, 0x08, 0x02, 0x33, 0x15, 0x1D, 0x2A, 0x09, 0x0E, 0x16, 0x03, 0x18, 0x27, 0x14, 0x1F, 0x37, 0x0B, 0x21, 0x35, 0x36, 0x29, 0x34, 0x20, 0x0C, 0x1C, 0x2C, 0x00, 0x01, 0x32, 0x17, 0x07, 0x0A, 0x1E, 0x31, 0x24, 0x0F, 0x14, 0x34, 0x30, 0x20, 0x21, 0x04, 0x12, 0x1B, 0x36, 0x27, 0x13, 0x06, 0x00, 0x16, 0x10, 0x0A, 0x23, 0x2B, 0x01, 0x17, 0x1C, 0x24, 0x11, 0x07, 0x35, 0x22, 0x2D, 0x0C, 0x19, 0x2F, 0x26, 0x0B, 0x37, 0x0D, 0x2E, 0x1A, 0x2A, 0x03, 0x0E, 0x0F, 0x09, 0x25, 0x15, 0x18, 0x2C, 0x08, 0x32, 0x1D, 0x1B, 0x06, 0x37, 0x27, 0x28, 0x0B, 0x19, 0x22, 0x04, 0x2E, 0x1A, 0x0D, 0x07, 0x1D, 0x17, 0x11, 0x2A, 0x32, 0x08, 0x1E, 0x23, 0x2B, 0x18, 0x0E, 0x1F, 0x29, 0x34, 0x13, 0x20, 0x36, 0x2D, 0x12, 0x05, 0x14, 0x35, 0x21, 0x31, 0x0A, 0x15, 0x16, 0x10, 0x2C, 0x1C, 0x00, 0x33, 0x0F, 0x02, 0x24]
byte_102D4AA0 = [0x1F, 0x1E, 0x1D, 0x1C, 0x1B, 0x1A, 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x0F, 0x0E, 0x0D, 0x0C, 0x0B, 0x0A, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x1B, 0x1A, 0x19, 0x18, 0x17, 0x16, 0x13, 0x12, 0x11, 0x10, 0x0F, 0x0E, 0x0B, 0x0A, 0x09, 0x08, 0x07, 0x06, 0x03, 0x02, 0x01, 0x00, 0x1F, 0x1E]

KEY_DATA = [0] * 96


def roll_right(arg2, arg3):
    return (arg2 << 32 - arg3 | (arg2 >> arg3)) & 0xffffffff


def des_small_flips_encrypt(key, output, uid):
    v3 = uid[0] | (uid[1] << 8) | (uid[2] << 16) | (uid[3] << 24)
    v0 = uid[4] | (uid[5] << 8) | (uid[6] << 16) | (uid[7] << 24)
    v4 = (16 * ((v3 ^ (v0 >> 4)) & 0xF0F0F0F)) ^ v0
    v5 = (v3 ^ (v0 >> 4)) & 0xF0F0F0F ^ v3
    v6 = (v4 ^ (v5 >> 16)) & 0xffff
    v7 = (v6 << 16) ^ v5
    v8 = v6 ^ v4
    v9 = (v7 ^ (v8 >> 2)) & 0x33333333
    v10 = (4 * v9) ^ v8
    v11 = v9 ^ v7
    v12 = (v10 ^ (v11 >> 8)) & 0xFF00FF
    v13 = (v12 << 8) ^ v11
    v14 = roll_right(v12 ^ v10, 1)
    v15 = (v13 ^ v14) & 0x55555555
    v16 = v15 ^ v14
    v17 = roll_right(v15 ^ v13, 1)

    for i in range(0, 32, 4):
        v19 = roll_right(v17 ^ key[i + 1], 28)
        v16 ^= (0
            ^ DES_KEYMAP[0][((v17 ^ key[i]) >> 26) & 0x3f]
            ^ DES_KEYMAP[1][((v17 ^ key[i]) >> 18) & 0x3f]
            ^ DES_KEYMAP[2][(((v17 ^ key[i]) >> 8) >> 2) & 0x3f]
            ^ DES_KEYMAP[3][((v17 ^ key[i]) >> 2) & 0x3f]
            ^ DES_KEYMAP[4][(v19 >> 26) & 0x3f]
            ^ DES_KEYMAP[5][(v19 >> 18) & 0x3f]
            ^ DES_KEYMAP[6][(v19 >> 10) & 0x3f]
            ^ DES_KEYMAP[7][(v19 >> 2) & 0x3f]
        )
        v20 = roll_right(v16 ^ key[i + 3], 28)
        v17 ^= (0
            ^ DES_KEYMAP[0][((v16 ^ key[i + 2]) >> 26) & 0x3f]
            ^ DES_KEYMAP[1][((v16 ^ key[i + 2]) >> 18) & 0x3F]
            ^ DES_KEYMAP[2][(((v16 ^ key[i + 2]) >> 8) >> 2) & 0x3f]
            ^ DES_KEYMAP[3][((v16 ^ key[i + 2]) >> 2) & 0x3F]
            ^ DES_KEYMAP[4][(v20 >> 26) & 0x3f]
            ^ DES_KEYMAP[5][(v20 >> 18) & 0x3F]
            ^ DES_KEYMAP[6][(v20 >> 10) & 0x3f]
            ^ DES_KEYMAP[7][(v20 >> 2) & 0x3f]
        )
    v21 = roll_right(v16, 31)
    v22 = (v17 ^ v21) & 0x55555555
    v23 = v22 ^ v21
    v24 = roll_right(v22 ^ v17, 31)
    v25 = (v24 ^ (v23 >> 8)) & 0xFF00FF
    v26 = (v25 << 8) ^ v23
    v27 = v25 ^ v24
    v28 = (v26 ^ ((v25 ^ v24) >> 2)) & 0x33333333
    v29 = (4 * v28) ^ v27
    v30 = v28 ^ v26
    v31 = (v29 ^ (v30 >> 16)) & 0xffff
    v32 = ((v31 << 16) & 0xffffffff) ^ v30
    v33 = v31 ^ v29
    v34 = (v32 ^ (v33 >> 4)) & 0xF0F0F0F
    v35 = (v34 << 4) ^ v33
    v34 = v34 ^ v32

    output[0] = v34 & 0xff
    output[1] = (v34 >> 8) & 0xff
    output[2] = (v34 >> 16) & 0xff
    output[3] = (v34 >> 24) & 0xff
    output[4] = v35 & 0xff
    output[5] = (v35 >> 8) & 0xff
    output[6] = (v35 >> 16) & 0xff
    output[7] = (v35 >> 24) & 0xff


def des_small_flips_decrypt(key, output, uid):
    v3 = uid[0] | (uid[1] << 8) | (uid[2] << 16) | (uid[3] << 24)
    v0 = uid[4] | (uid[5] << 8) | (uid[6] << 16) | (uid[7] << 24)
    v4 = (16 * ((v3 ^ (v0 >> 4)) & 0xF0F0F0F)) ^ v0
    v5 = (v3 ^ (v0 >> 4)) & 0xF0F0F0F ^ v3
    v6 = (v4 ^ (v5 >> 16)) & 0xffff
    v7 = (v6 << 16) ^ v5
    v8 = v6 ^ v4
    v9 = (v7 ^ (v8 >> 2)) & 0x33333333
    v10 = (4 * v9) ^ v8
    v11 = v9 ^ v7
    v12 = (v10 ^ (v11 >> 8)) & 0xFF00FF
    v13 = (v12 << 8) ^ v11
    v14 = roll_right(v12 ^ v10, 1)
    v15 = (v13 ^ v14) & 0x55555555
    v16 = v15 ^ v14
    v17 = roll_right(v15 ^ v13, 1)

    for i in range(0, 32, 4):
        v19 = roll_right(v17 ^ key[(31 - i)], 28)
        v16 ^= (0
            ^ DES_KEYMAP[0][((v17 ^ key[(30 - i)]) >> 26) & 0x3f]
            ^ DES_KEYMAP[1][((v17 ^ key[(30 - i)]) >> 18) & 0x3F]
            ^ DES_KEYMAP[2][(((v17 ^ key[(30 - i)]) >> 8) >> 2) & 0x3f]
            ^ DES_KEYMAP[3][((v17 ^ key[(30 - i)]) >> 2) & 0x3F]
            ^ DES_KEYMAP[4][(v19 >> 26) & 0x3f]
            ^ DES_KEYMAP[5][(v19 >> 18) & 0x3F]
            ^ DES_KEYMAP[6][(v19 >> 10) & 0x3f]
            ^ DES_KEYMAP[7][(v19 >> 2) & 0x3f]
        )
        v20 = roll_right(v16 ^ key[(29 - i)], 28)
        v17 ^= (0
            ^ DES_KEYMAP[0][((v16 ^ key[(28 - i)]) >> 26) & 0x3f]
            ^ DES_KEYMAP[1][((v16 ^ key[(28 - i)]) >> 18) & 0x3F]
            ^ DES_KEYMAP[2][(((v16 ^ key[(28 - i)]) >> 8) >> 2) & 0x3f]
            ^ DES_KEYMAP[3][((v16 ^ key[(28 - i)]) >> 2) & 0x3F]
            ^ DES_KEYMAP[4][(v20 >> 26) & 0x3f]
            ^ DES_KEYMAP[5][(v20 >> 18) & 0x3f]
            ^ DES_KEYMAP[6][(v20 >> 10) & 0x3f]
            ^ DES_KEYMAP[7][(v20 >> 2) & 0x3f]
        )

    v21 = roll_right(v16, 31)
    v22 = (v17 ^ v21) & 0x55555555
    v23 = v22 ^ v21
    v24 = roll_right(v22 ^ v17, 31)
    v25 = (v24 ^ (v23 >> 8)) & 0xFF00FF
    v26 = (v25 << 8) ^ v23
    v27 = v25 ^ v24
    v28 = (v26 ^ ((v25 ^ v24) >> 2)) & 0x33333333
    v29 = (4 * v28) ^ v27
    v30 = v28 ^ v26
    v31 = (v29 ^ (v30 >> 16)) & 0xffff
    v32 = (v31 << 16) ^ v30
    v33 = v31 ^ v29
    v34 = (v32 ^ (v33 >> 4)) & 0xF0F0F0F
    v35 = (v34 << 4) ^ v33
    v34 = v34 ^ v32

    output[0] = v34 & 0xff
    output[1] = (v34 >> 8) & 0xff
    output[2] = (v34 >> 16) & 0xff
    output[3] = (v34 >> 24) & 0xff
    output[4] = v35 & 0xff
    output[5] = (v35 >> 8) & 0xff
    output[6] = (v35 >> 16) & 0xff
    output[7] = (v35 >> 24) & 0xff


def des3_encryption(key, kid_out, uid):
    des_small_flips_encrypt(key,      kid_out, uid)
    des_small_flips_decrypt(key[32:], kid_out, kid_out)
    des_small_flips_encrypt(key[64:], kid_out, kid_out)


def des_setkey(key, offset, out):
    b1 = bytearray(56);

    for i in range(8):
        v3 = out[i]
        for j in range(7):
            b1[-7 * i - j + 55] = (v3 >> (j + 1)) & 1

    for i in range(32):
        v5 = 0
        for j in range(24):
            v5 |= b1[DES_ROTORS[24 * i + j]] << byte_102D4AA0[24 * (i & 1) + j]

        key[offset + i] = v5


def des3_setkey(key, out):
    for i in range(3):
        des_setkey(key, 32 * i, out[8 * i:])


def load_key(key):
    key_data = bytearray(24)
    for i in range(24):
        key_data[i] = 2 * key[i % len(key)]
    des3_setkey(KEY_DATA, key_data){% endhighlight %}
{% endblock %}