Hoernchen has uploaded this change for review.
GP: fix kcb for non block aligned keys
how encrypt_key() pads a kcv:
len(key) % blocksize bytes
what it should do to actually do it right:
blocksize - len(key) % blocksize
so the plaintext handed to the cipher was only block aligned by luck as
long as the key length happened to be a multiple of half the block size.
And of course decrypt_key() did not invert encrypt_key() at all,
the clear text length of Table 11-70 precedes the ENCRYPTED kcv,
but it was parsed out of the DECRYPTED data, and the length byte itself
was fed to the cipher along with the cryptogram.
Fix this up with a helper and tests so it is actually usable.
Change-Id: I02b4f2ed948c31e1741e40f0226fb49757fa2570
---
M pySim/global_platform/scp.py
M tests/unittests/test_globalplatform.py
2 files changed, 48 insertions(+), 6 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/pysim refs/changes/00/43200/1
diff --git a/pySim/global_platform/scp.py b/pySim/global_platform/scp.py
index a5fcf51..a50b17e 100644
--- a/pySim/global_platform/scp.py
+++ b/pySim/global_platform/scp.py
@@ -215,11 +215,19 @@
def gen_ext_auth_apdu(self, security_level: int = 0x01) -> bytes:
pass
+ def pad_to_blocksize(self, data: bytes) -> bytes:
+ """Right pad the data with zero bytes to a multiple of the DEK cipher block size."""
+ if len(data) % self.sk.blocksize:
+ data += b'\x00' * (self.sk.blocksize - len(data) % self.sk.blocksize)
+ return data
+
def encrypt_key(self, key: bytes) -> bytes:
"""Encrypt a key with the DEK."""
- num_pad = len(key) % self.sk.blocksize
- if num_pad:
- return bertlv_encode_len(len(key)) + self.dek_encrypt(key + b'\x00'*num_pad)
+ if len(key) % self.sk.blocksize:
+ # The kcv is right padded before encryption and the kcb
+ # is formatted as described in Table 11-70: preceded by the actual length of the
+ # clear text kcv.
+ return bertlv_encode_len(len(key)) + self.dek_encrypt(self.pad_to_blocksize(key))
return self.dek_encrypt(key)
def decrypt_key(self, encrypted_key:bytes) -> bytes:
@@ -232,9 +240,8 @@
# Block provides the actual length of the key component value, which allows recovering the
# clear-text key component value after decryption of the encrypted key component value and removal
# of padding bytes.
- decrypted = self.dek_decrypt(encrypted_key)
- key_len, remainder = bertlv_parse_len(decrypted)
- return remainder[:key_len]
+ key_len, remainder = bertlv_parse_len(encrypted_key)
+ return self.dek_decrypt(remainder)[:key_len]
else:
# If the length of the Key Component Block is a multiple of the block size of the encryption
# algorithm (i.e. 8 bytes for DES, 16 bytes for AES), then it shall be assumed that no padding
diff --git a/tests/unittests/test_globalplatform.py b/tests/unittests/test_globalplatform.py
index 8698470..576407d 100644
--- a/tests/unittests/test_globalplatform.py
+++ b/tests/unittests/test_globalplatform.py
@@ -283,6 +283,41 @@
# FIXME: test auth with random (0x60) vs pseudo-random (0x70) challenge
+class KeyComponentBlock_Test(unittest.TestCase):
+ """Tests for the kcb of GP CardSpec v2.3
+ - Table 11-70 kcv that required padding, preceded by its clear-text length
+ - Table 11-71 no padding required"""
+
+ def setUp(self):
+ # SCP02 (3DES DEK, 8 byte blocks), same vectors as SCP02_Test
+ self.scp02 = SCP02(card_keys=ck_3des_70)
+ self.scp02.gen_init_update_apdu(host_challenge=h2b('40A62C37FA6304F8'))
+ self.scp02.parse_init_update_resp(h2b('00000000000000000000700200016B4524ABEE7CF32EA3838BC148F3'))
+ self.scp02.gen_ext_auth_apdu()
+ # SCP03 (AES DEK, 16 byte blocks), same vectors as SCP03_Test_AES128_11
+ self.scp03 = SCP03(card_keys=KEYSET_AES128)
+ self.scp03.gen_init_update_apdu(h2b('b13e5f938fc108c4'))
+ self.scp03.parse_init_update_resp(h2b('000000000000000000003003703eb51047495b249f66c484c1d2ef1948000002'))
+ self.scp03.gen_ext_auth_apdu(0x11)
+
+ def test_encrypt_decrypt_key(self):
+ for scp in (self.scp02, self.scp03):
+ bs = scp.sk.blocksize
+ for keylen in range(1, 3 * bs + 1):
+ with self.subTest(scp=type(scp).__name__, keylen=keylen):
+ key = bytes(range(keylen))
+ kcb = scp.encrypt_key(key)
+ if keylen % bs:
+ # Table 11-70: <length of clear key component> || <encrypted padded value>
+ self.assertEqual(kcb[0], keylen)
+ self.assertEqual((len(kcb) - 1) % bs, 0)
+ self.assertEqual(len(kcb) - 1, keylen + (bs - keylen % bs))
+ else:
+ # Table 11-71: only the encrypted key component value
+ self.assertEqual(len(kcb), keylen)
+ self.assertEqual(scp.decrypt_key(kcb), key)
+
+
class SCP03_KCV_Test(unittest.TestCase):
def test_kcv(self):
self.assertEqual(compute_kcv('aes', KEYSET_AES128.enc), h2b('C35280'))
To view, visit change 43200. To unsubscribe, or for help writing mail filters, visit settings.