diff --git a/cardid.html b/cardid.html new file mode 100644 index 0000000..399b36d --- /dev/null +++ b/cardid.html @@ -0,0 +1,488 @@ + + + + + + + + e-Amusement API + + + + + + + + + + + + +
ContentsTransport layerPacket formatApplication Protocol
+ +

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.

+
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"
+
+
+

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:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
0123456789101112131415
e-Amusement IDCheck byteCard typeChecksum
+ +

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!

+
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))
+

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! +
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)
+
+
+ + \ No newline at end of file diff --git a/index.html b/index.html index e7fba18..f120af7 100644 --- a/index.html +++ b/index.html @@ -42,6 +42,18 @@

If you're here because you work on one of those aforementioned closed source projects, hello! Feel free to share knowledge with the rest of the world, or point out corrections. Or don't; you do you.

+

Code snippets

+

Across these pages there are a number of code snippets. They roughly break down into three categories:

+ +

If you yoink chunks of Python code, attribution is always appreciated, but consider it under CC0 (just don't be + that person who tries to take credit for it, yeah?).

+

Contents

  1. Transport layer
  2. @@ -56,6 +68,14 @@
  3. Binary schemas
  4. Binary data
+
  • Communication protocol details
  • + +
  • Misc pages
  • +
      +
    1. Parsing and converting card IDs
    2. +

    Getting started

    diff --git a/packet.html b/packet.html index 370d5f4..3c266ea 100644 --- a/packet.html +++ b/packet.html @@ -159,11 +159,11 @@ + 0x40 0xBF ISO-8859-1 ISO_8859-1 - 0x60 diff --git a/proto/cardmng.html b/proto/cardmng.html index acdde4a..98c4a64 100644 --- a/proto/cardmng.html +++ b/proto/cardmng.html @@ -27,13 +27,13 @@ are identified in requests using the cardtype attribute as in the below table.

    e-Amusement cards have a "card number" and a "card id". Confusingly, neither is a number. The card number is the - one printed on your card. The card ID is your KONAMI ID. The number is derrived from your ID using an algorithm - that I'll detail here once I get round to it.

    + one printed on your card. The card ID is your KONAMI ID. You can (and should) read about the algorithm used for + these IDs on the Card IDs page.

    In the interest of not wasting space, cardid and cardtype will be omitted from individual breakdowns where their meaning is obvious.

    Card types:

    - +
    @@ -69,7 +69,7 @@
    <call ...>
         <cardmng method="inquire" cardid="" cardtype="" update="" model*="" />
     </call>
    -
    cardtype
    +
    @@ -84,7 +84,7 @@ seen for this specific game. If we have never seen this card used on this game, it is possible this card was used with an older version of this game, and migration is supported, in which case we report as if we had found a profile for this game.

    -
    update Should the tracked last play time be updated by this inquiry? (Just a guess)
    +
    @@ -115,7 +115,7 @@
    <call ...>
         <cardmng method="getrefid" cardtype="" cardid=" newflag="" passwd="" model*="" />
     </call>
    -
    refid A reference to this card to be used in other requests
    +
    @@ -130,7 +130,7 @@
    <response>
         <cardmng status="status" refid="" dataid="" pcode="" />
     </response>
    -
    newflag ?
    +
    @@ -174,7 +174,7 @@
    <call ...>
         <cardmng method="authpass" refid="" pass="" model*="" />
     </call>
    -
    refid A reference to this card to be used in other requests
    +
    refid The reference we received either during cardmng.inquire or cardmng.getrefid