From c26e6c9dd6197f3ce1a263bd836d50b8f4656ece Mon Sep 17 00:00:00 2001 From: Lennart Rosam Date: Mon, 22 Jun 2026 00:13:38 +0200 Subject: [PATCH 1/3] fix: decode OPC hexlify result to string binascii.hexlify returns bytes in Python 3, causing OPC values to be bytes instead of strings. This made dict output inconsistent (mixed str/bytes). --- kiopcgenerator/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kiopcgenerator/lib.py b/kiopcgenerator/lib.py index 7c83659..b157788 100755 --- a/kiopcgenerator/lib.py +++ b/kiopcgenerator/lib.py @@ -45,7 +45,7 @@ def calc_opc_hex(self, k_hex, op_hex=None): aes_crypt = AES.new(ki, mode=AES.MODE_CBC, IV=iv) data = op o_pc = self._xor_str(data, aes_crypt.encrypt(data)) - return binascii.hexlify(o_pc) + return binascii.hexlify(o_pc).decode('utf-8') def _xor_str(self, s, t): """xor two strings together""" From 111a0869f80baad4138f124aed4bbc2b9551e519 Mon Sep 17 00:00:00 2001 From: Lennart Rosam Date: Mon, 22 Jun 2026 00:14:17 +0200 Subject: [PATCH 2/3] feat: add Ki recovery from eKi Adds aes_128_cbc_decrypt and recover_ki to reverse the eKi encryption, and a -e/--eki CLI flag to recover Ki (with OPC and eKi) from an encrypted triplet given the OP and Transport Key. --- kiopcgen | 7 ++++++- kiopcgenerator/lib.py | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/kiopcgen b/kiopcgen index 7fca28f..01b75fa 100755 --- a/kiopcgen +++ b/kiopcgen @@ -17,9 +17,14 @@ parser = OptionParser() parser.add_option("-o", "--op", dest="op", help="32 char OP key") parser.add_option("-t", "--transport", dest="trans", help="32 char Transport key") parser.add_option("-k", "--ki", dest="ki", help="Optional 32 char Ki key (to avoid random generation)") +parser.add_option("-e", "--eki", dest="eki", help="32 char eKI key (to recover Ki)") (options, args) = parser.parse_args() -if options.op and options.trans: +if options.eki and options.trans and options.op: + result = kiopcgenerator.recover_ki(options.op, options.trans, options.eki) + pprint.pprint(result) + sys.exit(0) +elif options.op and options.trans: if not options.ki: options.ki = kiopcgenerator.gen_ki() diff --git a/kiopcgenerator/lib.py b/kiopcgenerator/lib.py index b157788..0270b85 100755 --- a/kiopcgenerator/lib.py +++ b/kiopcgenerator/lib.py @@ -79,6 +79,17 @@ def gen_opc(op, ki): return hss.calc_opc_hex(ki, op).upper() +def aes_128_cbc_decrypt(key, ciphertext): + """ + implements aes 128b decryption with cbc. + """ + keyb = binascii.unhexlify(key) + ciphertextb = binascii.unhexlify(ciphertext) + decryptor = AES.new(keyb, AES.MODE_CBC, IV=IV) + plaintext = decryptor.decrypt(ciphertextb) + return plaintext.hex().upper() + + def gen_eki(transport, ki): """ generates eKI based on ki and transport key @@ -86,5 +97,13 @@ def gen_eki(transport, ki): return aes_128_cbc_encrypt(transport, ki) +def recover_ki(op, transport, eki): + """ + recovers ki from eKI and generates opc using the transport key and op + """ + ki = aes_128_cbc_decrypt(transport, eki) + return {"KI": ki, "OPC": gen_opc(op, ki), "eKI": eki} + + def gen_opc_eki(op, transport, ki): return {"KI": ki, "OPC": gen_opc(op, ki), "eKI": gen_eki(transport, ki)} From 07a04edb1d01bd68a59c42b3fff5bd5fa4e3a517 Mon Sep 17 00:00:00 2001 From: Lennart Rosam Date: Mon, 22 Jun 2026 00:34:17 +0200 Subject: [PATCH 3/3] docs: add recover_ki usage to README --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index c2cf50c..a8c0f93 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Options: -o OP, --op=OP 32 char OP key -t TRANS, --transport=TRANS 32 char Transport key -k KI, --ki=KI Optional 32 char Ki key (to avoid random generation) + -e EKI, --eki=EKI 32 char eKI key (to recover Ki) $ kiopcgen -o D7DECB1F50404CC29ECBF989FE73AFC5 -t 2257CC6E9746434B89F346F0276CCAEC {'KI': '780E6AC95A2E43449C15BDCDD0450982', @@ -51,6 +52,13 @@ $ kiopcgen -o D7DECB1F50404CC29ECBF989FE73AFC5 -t 2257CC6E9746434B89F346F0276CCA {'KI': '8978B79E7C104F678FA5C336509DB188', 'OPC': '6F2E82855DEE7C893CB1F7A72FD08B57', 'eKI': 'FBE8C170F6A5C6C257E5324719674818'} + +Or recover Ki from an encrypted eKi: + +$ kiopcgen -o D7DECB1F50404CC29ECBF989FE73AFC5 -t 2257CC6E9746434B89F346F0276CCAEC -e 4601138387FCF7D666ED24BBB3EE37B8 +{'KI': '780E6AC95A2E43449C15BDCDD0450982', + 'OPC': '2274B84B8043105A28AABBE53EF1D014', + 'eKI': '4601138387FCF7D666ED24BBB3EE37B8'} ``` Or import the kiopcgenerator module to use in your scripts @@ -72,6 +80,10 @@ print (kiopcgenerator.gen_eki(transport, ki)) # 8FAC9FE22D306EA4CB86279B3473D8CB print (kiopcgenerator.gen_opc_eki(op, transport, ki)) # {'KI': 'EBD77DF6CFF949448ACF82B8FE4E59E3', 'eKI': '8FAC9FE22D306EA4CB86279B3473D8CB', 'OPC': '33244F04A86408A53110D1FCAFD04288'} + +# Recover Ki from eKi +print (kiopcgenerator.recover_ki(op, transport, eki)) +# {'KI': 'EBD77DF6CFF949448ACF82B8FE4E59E3', 'eKI': '8FAC9FE22D306EA4CB86279B3473D8CB', 'OPC': '33244F04A86408A53110D1FCAFD04288'} ``` ## Installation