4. Classical AiMe Access Codes
Classical AiMe codes are divided into three sections. The first 5 digits identify the type of card, the following 8 digits are the card serial, encrypted, and the final 7 digits are a digest of the card serial.
The prefix both identifies the type of card, and selects the key used for encryption of the serial.
Prefix | Card type | Card key |
---|---|---|
0101 0 |
Initial D 5-8 | B3E8CFA7D2149560 |
0102 9 |
MJ4 Evo Mobile | 89F56CD1B3EA7402 |
0103 1 |
MJ4 Evo Card | 471B3E8629D0CA5F |
0103 2 |
Diva Card | 5C9AB3E816DF4027 |
0103 3 |
Diva Mobile | C4E81B3A5920F67D |
0103 4 |
MJ4 Evo Pro Mahjong Player | E5FA60281B3D479C |
0103 5 |
AiMe Mobile | 5CD3E81B9024F67A |
0103 6 |
AiMe Card | A1B3E86CF02974D5 |
0103 7 |
Crows Mobile | 0000000000000000 |
0103 8 |
Crows Card | 0000000000000000 |
0104 0 |
Sengoku Card | 817FA56CB3E0249D |
0104 8 |
Rec Check Golf Card | 1B3097D65E82A4FC |
0104 9 |
Reserved | 81B7AD9C03EF4652 |
0105 0 |
Reserved | 84DC50F1B3E276A9 |
0105 1 |
Reserved | 57C3E81BF4269AD0 |
0105 2 |
Reserved | 706F81B3E4259CDA |
0105 3 |
Reserved | E8179645DB3FC02A |
0105 4 |
Reserved | 82DAF451B3E076C9 |
0105 5 |
Reserved | 54FE81B302DC9A67 |
0105 6 |
Reserved | 65D81B3E902F4C7A |
0105 7 |
Reserved | FD81B3E4A627C095 |
Warning
This is not guaranteed to be exhaustive. If you have a card not listed here, please do get in touch.
The card serial is encrypted with a solitaire cipher, using the digest as the key.
Computing the digest is done as follows:
- Zero-prefix the serial number to 8 characters
- Compute the MD5 of the serial
- Shuffle the serial, using each nibble of the key as an index (ie
data[i]=digest[key_nibbles[i]]
) - Treating the 128-bit value as a single little endian number, XOR every 23 bits together (counting from LSB, such that the final operation will be performed on only 13 bits, which will include the MSB)
- Zero-prefix the result of the operation to 7 characters
The following python snippet implements this behavior with some nasty bit-string hacks. A less succinct but potentially nicer implementation would use bit shifts instead.
import hashlib
def digest(serial: int, key: str):
real_digest = hashlib.md5(str(serial).zfill(8).encode()).digest()
digest = bytes(real_digest[int(key[i], 16)] for i in range(16))
bitstring = "".join(bin(i)[2:].zfill(8)[::-1] for i in digest)[::-1].zfill(6 * 23)
computed = 0
while bitstring:
work = int(bitstring[:23], 2)
computed ^= work
bitstring = bitstring[23:]
return str(computed).zfill(7)