132 lines
4.4 KiB
Python
132 lines
4.4 KiB
Python
import hmac
|
|
import hashlib
|
|
import click
|
|
|
|
from Crypto.Cipher import AES
|
|
from Crypto.Util.Padding import pad, unpad
|
|
from pathlib import Path
|
|
|
|
|
|
class CardEncryptor:
|
|
def __init__(
|
|
self,
|
|
cardid_hex,
|
|
key_hex,
|
|
store_card_id_str="FAKESTORE",
|
|
merchant_code_str="NOTSEGA",
|
|
store_branch_id_str="11111",
|
|
passphrase_str="573",
|
|
output_file="authdata.bin",
|
|
):
|
|
self.cardid = bytes.fromhex(cardid_hex)
|
|
self.key = bytearray.fromhex(key_hex)
|
|
self.output_file = output_file
|
|
|
|
if len(self.cardid) != 0x20:
|
|
raise ValueError("Card ID must be 32 bytes (64 hex characters)")
|
|
if len(self.key) != 0x40:
|
|
raise ValueError("Key must be 64 bytes (128 hex characters)")
|
|
|
|
# XOR the key with 0x1C as in original Java
|
|
for i in range(len(self.key)):
|
|
self.key[i] ^= 0x1C
|
|
|
|
self.store_card_id = self._str_to_bytes(store_card_id_str, 0x10)
|
|
self.merchant_code = self._str_to_bytes(merchant_code_str, 0x14)
|
|
self.store_branch_id = self._str_to_bytes(store_branch_id_str, 0x0C)
|
|
self.passphrase = self._str_to_bytes(passphrase_str, 0x10)
|
|
|
|
# Construct full data payload
|
|
self.data = (
|
|
self.store_card_id
|
|
+ self.merchant_code
|
|
+ self.store_branch_id
|
|
+ self.passphrase
|
|
+ bytes([0x00]) # +1 null terminator / padding byte
|
|
)
|
|
|
|
def _str_to_bytes(self, s, length):
|
|
b = bytearray(length)
|
|
b[: len(s)] = s.encode()
|
|
return bytes(b)
|
|
|
|
def _bytes_to_str(self, b):
|
|
return b.decode()
|
|
|
|
def _bytes_to_hex(self, b):
|
|
return b.hex().upper()
|
|
|
|
def _calculate_hmac_sha256(self, key, data, length):
|
|
h = hmac.new(key, data, hashlib.sha256)
|
|
return h.digest()[:length]
|
|
|
|
def _aes_cbc_encrypt(self, key, data, iv):
|
|
cipher = AES.new(key, AES.MODE_CBC, iv)
|
|
return cipher.encrypt(pad(data, AES.block_size))
|
|
|
|
def _aes_cbc_decrypt(self, key, data, iv):
|
|
cipher = AES.new(key, AES.MODE_CBC, iv)
|
|
return unpad(cipher.decrypt(data), AES.block_size)
|
|
|
|
def run(self):
|
|
print("Card ID:\t\t", self._bytes_to_hex(self.cardid))
|
|
print("Store Card ID:\t\t", self._bytes_to_str(self.store_card_id))
|
|
print("Merchant Code:\t\t", self._bytes_to_str(self.merchant_code))
|
|
print("Store Branch ID:\t", self._bytes_to_str(self.store_branch_id))
|
|
print("Passphrase:\t\t", self._bytes_to_str(self.passphrase))
|
|
|
|
hmac_output = self._calculate_hmac_sha256(self.key, self.cardid, 0x20)
|
|
# print("HMAC:\t\t", self._bytes_to_hex(hmac_output))
|
|
|
|
iv = bytes([hmac_output[i + 16] ^ hmac_output[i] for i in range(16)])
|
|
# print("IV:\t\t", self._bytes_to_hex(iv))
|
|
|
|
encrypted = self._aes_cbc_encrypt(hmac_output, self.data, iv)
|
|
# print("ENCRYPTED:\t", self._bytes_to_hex(encrypted))
|
|
Path(self.output_file).write_bytes(encrypted)
|
|
|
|
decrypted = self._aes_cbc_decrypt(hmac_output, encrypted, iv)
|
|
# print("DECRYPTED:\t", self._bytes_to_hex(decrypted))
|
|
|
|
|
|
@click.command()
|
|
@click.option(
|
|
"--cardid",
|
|
default="0102030401020304010203040102030401020304010203040102030401020304",
|
|
help="Card ID (64 hex characters)",
|
|
)
|
|
@click.option(
|
|
"--key",
|
|
required=True,
|
|
help="Key (128 hex characters, required)",
|
|
)
|
|
@click.option(
|
|
"--store-card-id", default="FAKESTORE", help="Store Card ID (padded to 16 bytes)"
|
|
)
|
|
@click.option(
|
|
"--merchant-code", default="NOTSEGA", help="Merchant Code (padded to 20 bytes)"
|
|
)
|
|
@click.option(
|
|
"--store-branch-id", default="11111", help="Store Branch ID (padded to 12 bytes)"
|
|
)
|
|
@click.option("--passphrase", default="573", help="Passphrase, used for the pfx password (padded to 16 bytes)")
|
|
@click.option("--output", default="authdata.bin", help="Output filename")
|
|
def cli(cardid, key, store_card_id, merchant_code, store_branch_id, passphrase, output):
|
|
if len(key) != 128 or not all(c in "0123456789abcdefABCDEF" for c in key):
|
|
raise click.BadParameter("The key must be a 128-character hexadecimal string.")
|
|
|
|
encryptor = CardEncryptor(
|
|
cardid,
|
|
key,
|
|
store_card_id_str=store_card_id,
|
|
merchant_code_str=merchant_code,
|
|
store_branch_id_str=store_branch_id,
|
|
passphrase_str=passphrase,
|
|
output_file=output,
|
|
)
|
|
encryptor.run()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
cli()
|