From 86777756985a34dc62509703bf095c7aa7a117c0 Mon Sep 17 00:00:00 2001 From: Helder Eijs Date: Sat, 6 Jan 2024 21:22:37 +0100 Subject: [PATCH] Add AES-GCM as PBES2/PKCS8 mode --- lib/Crypto/IO/_PBES.py | 126 +++++++++++++----- lib/Crypto/SelfTest/IO/test_PBES.py | 41 ++++-- lib/Crypto/SelfTest/IO/test_PKCS8.py | 34 +++++ .../SelfTest/PublicKey/test_import_ECC.py | 112 +++++++++++++++- .../SelfTest/PublicKey/test_import_RSA.py | 39 ++++++ .../ECC/ecc_ed25519_private_p8_2.der | Bin 0 -> 158 bytes .../PublicKey/ECC/ecc_ed448_private_p8_2.der | Bin 0 -> 174 bytes .../PublicKey/ECC/ecc_p192_private_p8_2.der | 0 .../PublicKey/ECC/ecc_p224_private_p8_2.der | Bin 0 -> 223 bytes .../PublicKey/ECC/ecc_p256_private_p8_2.der | Bin 0 -> 239 bytes .../PublicKey/ECC/ecc_p384_private_p8_2.der | Bin 0 -> 288 bytes .../PublicKey/ECC/ecc_p521_private_p8_2.der | Bin 0 -> 353 bytes .../PublicKey/ECC/gen_ecc_p224.sh | 1 + .../PublicKey/ECC/gen_ecc_p256.sh | 1 + .../PublicKey/ECC/gen_ecc_p384.sh | 1 + .../PublicKey/ECC/gen_ecc_p521.sh | 1 + .../PublicKey/ECC/gen_ed25519.sh | 1 + .../PublicKey/ECC/gen_ed448.sh | 1 + .../PublicKey/RSA/gen_rsa_2048.sh | 1 + .../PublicKey/RSA/rsa2048_private_p8.der | Bin 0 -> 1329 bytes .../pycryptodome_test_vectors/__init__.py | 2 +- test_vectors/src/pbes2_botan.cpp | 40 ++++++ 22 files changed, 359 insertions(+), 42 deletions(-) create mode 100644 test_vectors/pycryptodome_test_vectors/PublicKey/ECC/ecc_ed25519_private_p8_2.der create mode 100644 test_vectors/pycryptodome_test_vectors/PublicKey/ECC/ecc_ed448_private_p8_2.der create mode 100644 test_vectors/pycryptodome_test_vectors/PublicKey/ECC/ecc_p192_private_p8_2.der create mode 100644 test_vectors/pycryptodome_test_vectors/PublicKey/ECC/ecc_p224_private_p8_2.der create mode 100644 test_vectors/pycryptodome_test_vectors/PublicKey/ECC/ecc_p256_private_p8_2.der create mode 100644 test_vectors/pycryptodome_test_vectors/PublicKey/ECC/ecc_p384_private_p8_2.der create mode 100644 test_vectors/pycryptodome_test_vectors/PublicKey/ECC/ecc_p521_private_p8_2.der create mode 100644 test_vectors/pycryptodome_test_vectors/PublicKey/RSA/rsa2048_private_p8.der create mode 100644 test_vectors/src/pbes2_botan.cpp diff --git a/lib/Crypto/IO/_PBES.py b/lib/Crypto/IO/_PBES.py index 10611ab87..93d39446f 100644 --- a/lib/Crypto/IO/_PBES.py +++ b/lib/Crypto/IO/_PBES.py @@ -40,6 +40,7 @@ DerObjectId, DerInteger, ) +from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from Crypto.Protocol.KDF import PBKDF1, PBKDF2, scrypt @@ -59,7 +60,9 @@ _OID_AES128_CBC = "2.16.840.1.101.3.4.1.2" _OID_AES192_CBC = "2.16.840.1.101.3.4.1.22" _OID_AES256_CBC = "2.16.840.1.101.3.4.1.42" - +_OID_AES128_GCM = "2.16.840.1.101.3.4.1.6" +_OID_AES192_GCM = "2.16.840.1.101.3.4.1.26" +_OID_AES256_GCM = "2.16.840.1.101.3.4.1.46" class PbesError(ValueError): pass @@ -152,26 +155,26 @@ def decrypt(data, passphrase): from Crypto.Hash import MD5 from Crypto.Cipher import DES hashmod = MD5 - ciphermod = DES + module = DES elif pbe_oid == _OID_PBE_WITH_MD5_AND_RC2_CBC: # PBE_MD5_RC2_CBC from Crypto.Hash import MD5 from Crypto.Cipher import ARC2 hashmod = MD5 - ciphermod = ARC2 + module = ARC2 cipher_params['effective_keylen'] = 64 elif pbe_oid == _OID_PBE_WITH_SHA1_AND_DES_CBC: # PBE_SHA1_DES_CBC from Crypto.Hash import SHA1 from Crypto.Cipher import DES hashmod = SHA1 - ciphermod = DES + module = DES elif pbe_oid == _OID_PBE_WITH_SHA1_AND_RC2_CBC: # PBE_SHA1_RC2_CBC from Crypto.Hash import SHA1 from Crypto.Cipher import ARC2 hashmod = SHA1 - ciphermod = ARC2 + module = ARC2 cipher_params['effective_keylen'] = 64 else: raise PbesError("Unknown OID for PBES1") @@ -183,7 +186,7 @@ def decrypt(data, passphrase): key_iv = PBKDF1(passphrase, salt, 16, iterations, hashmod) key, iv = key_iv[:8], key_iv[8:] - cipher = ciphermod.new(key, ciphermod.MODE_CBC, iv, **cipher_params) + cipher = module.new(key, module.MODE_CBC, iv, **cipher_params) pt = cipher.decrypt(encrypted_data) return unpad(pt, cipher.block_size) @@ -260,35 +263,57 @@ def encrypt(data, passphrase, protection, prot_params=None, randfunc=None): pbkdf = "scrypt" enc_algo = res.group(3) + aead = False if enc_algo == 'DES-EDE3-CBC': from Crypto.Cipher import DES3 key_size = 24 module = DES3 cipher_mode = DES3.MODE_CBC enc_oid = _OID_DES_EDE3_CBC + enc_param = {'iv': randfunc(8)} elif enc_algo == 'AES128-CBC': - from Crypto.Cipher import AES key_size = 16 module = AES cipher_mode = AES.MODE_CBC enc_oid = _OID_AES128_CBC + enc_param = {'iv': randfunc(16)} elif enc_algo == 'AES192-CBC': - from Crypto.Cipher import AES key_size = 24 module = AES cipher_mode = AES.MODE_CBC enc_oid = _OID_AES192_CBC + enc_param = {'iv': randfunc(16)} elif enc_algo == 'AES256-CBC': - from Crypto.Cipher import AES key_size = 32 module = AES cipher_mode = AES.MODE_CBC enc_oid = _OID_AES256_CBC + enc_param = {'iv': randfunc(16)} + elif enc_algo == 'AES128-GCM': + key_size = 16 + module = AES + cipher_mode = AES.MODE_GCM + enc_oid = _OID_AES128_GCM + enc_param = {'nonce': randfunc(12)} + aead = True + elif enc_algo == 'AES192-GCM': + key_size = 24 + module = AES + cipher_mode = AES.MODE_GCM + enc_oid = _OID_AES192_GCM + enc_param = {'nonce': randfunc(12)} + aead = True + elif enc_algo == 'AES256-GCM': + key_size = 32 + module = AES + cipher_mode = AES.MODE_GCM + enc_oid = _OID_AES256_GCM + enc_param = {'nonce': randfunc(12)} + aead = True else: raise ValueError("Unknown encryption mode '%s'" % enc_algo) - # Get random data - iv = randfunc(module.block_size) + iv_nonce = list(enc_param.values())[0] salt = randfunc(prot_params.get("salt_size", 8)) # Derive key from password @@ -310,10 +335,10 @@ def encrypt(data, passphrase, protection, prot_params=None, randfunc=None): if pbkdf2_hmac_algo != 'SHA1': try: - hmac_oid = Hash.HMAC.new(digestmod).oid + hmac_oid = Hash.HMAC.new(b'', digestmod=digestmod).oid except KeyError: raise ValueError("No OID for HMAC hash algorithm") - pbkdf2_params.append(DerObjectId(hmac_oid)) + pbkdf2_params.append(DerSequence([DerObjectId(hmac_oid)])) kdf_info = DerSequence([ DerObjectId(_OID_PBKDF2), # PBKDF2 @@ -321,7 +346,7 @@ def encrypt(data, passphrase, protection, prot_params=None, randfunc=None): ]) elif pbkdf == 'scrypt': - # It must be scrypt + count = prot_params.get("iteration_count", 16384) scrypt_r = prot_params.get('block_size', 8) scrypt_p = prot_params.get('parallelization', 1) @@ -341,11 +366,15 @@ def encrypt(data, passphrase, protection, prot_params=None, randfunc=None): raise ValueError("Unknown KDF " + res.group(1)) # Create cipher and use it - cipher = module.new(key, cipher_mode, iv) - encrypted_data = cipher.encrypt(pad(data, cipher.block_size)) + cipher = module.new(key, cipher_mode, **enc_param) + if aead: + ct, tag = cipher.encrypt_and_digest(data) + encrypted_data = ct + tag + else: + encrypted_data = cipher.encrypt(pad(data, cipher.block_size)) enc_info = DerSequence([ DerObjectId(enc_oid), - DerOctetString(iv) + DerOctetString(iv_nonce) ]) # Result @@ -405,10 +434,12 @@ def decrypt(data, passphrase): if left > 0: try: + # Check if it's an INTEGER kdf_key_length = pbkdf2_params[idx] - 0 left -= 1 idx += 1 except TypeError: + # keyLength is not present pass # Default is HMAC-SHA1 @@ -434,34 +465,55 @@ def decrypt(data, passphrase): enc_info = DerSequence().decode(pbes2_params[1]) enc_oid = DerObjectId().decode(enc_info[0]).value + aead = False if enc_oid == _OID_DES_EDE3_CBC: # DES_EDE3_CBC from Crypto.Cipher import DES3 - ciphermod = DES3 + module = DES3 + cipher_mode = DES3.MODE_CBC key_size = 24 + cipher_param = 'iv' elif enc_oid == _OID_AES128_CBC: - # AES128_CBC - from Crypto.Cipher import AES - ciphermod = AES + module = AES + cipher_mode = AES.MODE_CBC key_size = 16 + cipher_param = 'iv' elif enc_oid == _OID_AES192_CBC: - # AES192_CBC - from Crypto.Cipher import AES - ciphermod = AES + module = AES + cipher_mode = AES.MODE_CBC key_size = 24 + cipher_param = 'iv' elif enc_oid == _OID_AES256_CBC: - # AES256_CBC - from Crypto.Cipher import AES - ciphermod = AES + module = AES + cipher_mode = AES.MODE_CBC + key_size = 32 + cipher_param = 'iv' + elif enc_oid == _OID_AES128_GCM: + module = AES + cipher_mode = AES.MODE_GCM + key_size = 16 + cipher_param = 'nonce' + aead = True + elif enc_oid == _OID_AES192_GCM: + module = AES + cipher_mode = AES.MODE_GCM + key_size = 24 + cipher_param = 'nonce' + aead = True + elif enc_oid == _OID_AES256_GCM: + module = AES + cipher_mode = AES.MODE_GCM key_size = 32 + cipher_param = 'nonce' + aead = True else: - raise PbesError("Unsupported PBES2 cipher") + raise PbesError("Unsupported PBES2 cipher " + enc_algo) if kdf_key_length and kdf_key_length != key_size: raise PbesError("Mismatch between PBES2 KDF parameters" " and selected cipher") - IV = DerOctetString().decode(enc_info[1]).payload + iv_nonce = DerOctetString().decode(enc_info[1]).payload # Create cipher if kdf_oid == _OID_PBKDF2: @@ -477,8 +529,18 @@ def decrypt(data, passphrase): else: key = scrypt(passphrase, salt, key_size, iteration_count, scrypt_r, scrypt_p) - cipher = ciphermod.new(key, ciphermod.MODE_CBC, IV) + cipher = module.new(key, cipher_mode, **{cipher_param:iv_nonce}) # Decrypt data - pt = cipher.decrypt(encrypted_data) - return unpad(pt, cipher.block_size) + if len(encrypted_data) < cipher.block_size: + raise ValueError("Too little data to decrypt") + + if aead: + tag_len = cipher.block_size + pt = cipher.decrypt_and_verify(encrypted_data[:-tag_len], + encrypted_data[-tag_len:]) + else: + pt_padded = cipher.decrypt(encrypted_data) + pt = unpad(pt_padded, cipher.block_size) + + return pt diff --git a/lib/Crypto/SelfTest/IO/test_PBES.py b/lib/Crypto/SelfTest/IO/test_PBES.py index b2a4f94ac..029e824a4 100644 --- a/lib/Crypto/SelfTest/IO/test_PBES.py +++ b/lib/Crypto/SelfTest/IO/test_PBES.py @@ -34,7 +34,6 @@ """Self-tests for Crypto.IO._PBES module""" import unittest -from Crypto.Util.py3compat import * from Crypto.IO._PBES import PBES2 @@ -42,8 +41,8 @@ class TestPBES2(unittest.TestCase): def setUp(self): - self.ref = b("Test data") - self.passphrase = b("Passphrase") + self.ref = b"Test data" + self.passphrase = b"Passphrase" def test1(self): ct = PBES2.encrypt(self.ref, self.passphrase, @@ -53,29 +52,53 @@ def test1(self): def test2(self): ct = PBES2.encrypt(self.ref, self.passphrase, - 'PBKDF2WithHMAC-SHA1AndAES128-CBC') + 'PBKDF2WithHMAC-SHA224AndAES128-CBC') pt = PBES2.decrypt(ct, self.passphrase) self.assertEqual(self.ref, pt) def test3(self): ct = PBES2.encrypt(self.ref, self.passphrase, - 'PBKDF2WithHMAC-SHA1AndAES192-CBC') + 'PBKDF2WithHMAC-SHA256AndAES192-CBC') pt = PBES2.decrypt(ct, self.passphrase) self.assertEqual(self.ref, pt) def test4(self): ct = PBES2.encrypt(self.ref, self.passphrase, - 'scryptAndAES128-CBC') + 'PBKDF2WithHMAC-SHA384AndAES256-CBC') pt = PBES2.decrypt(ct, self.passphrase) self.assertEqual(self.ref, pt) def test5(self): ct = PBES2.encrypt(self.ref, self.passphrase, - 'scryptAndAES192-CBC') + 'PBKDF2WithHMAC-SHA512AndAES128-GCM') pt = PBES2.decrypt(ct, self.passphrase) self.assertEqual(self.ref, pt) def test6(self): + ct = PBES2.encrypt(self.ref, self.passphrase, + 'PBKDF2WithHMAC-SHA512-224AndAES192-GCM') + pt = PBES2.decrypt(ct, self.passphrase) + self.assertEqual(self.ref, pt) + + def test7(self): + ct = PBES2.encrypt(self.ref, self.passphrase, + 'PBKDF2WithHMAC-SHA3-256AndAES256-GCM') + pt = PBES2.decrypt(ct, self.passphrase) + self.assertEqual(self.ref, pt) + + def test8(self): + ct = PBES2.encrypt(self.ref, self.passphrase, + 'scryptAndAES128-CBC') + pt = PBES2.decrypt(ct, self.passphrase) + self.assertEqual(self.ref, pt) + + def test9(self): + ct = PBES2.encrypt(self.ref, self.passphrase, + 'scryptAndAES192-CBC') + pt = PBES2.decrypt(ct, self.passphrase) + self.assertEqual(self.ref, pt) + + def test10(self): ct = PBES2.encrypt(self.ref, self.passphrase, 'scryptAndAES256-CBC') pt = PBES2.decrypt(ct, self.passphrase) @@ -88,6 +111,8 @@ def get_tests(config={}): listTests += list_test_cases(TestPBES2) return listTests + if __name__ == '__main__': - suite = lambda: unittest.TestSuite(get_tests()) + def suite(): + return unittest.TestSuite(get_tests()) unittest.main(defaultTest='suite') diff --git a/lib/Crypto/SelfTest/IO/test_PKCS8.py b/lib/Crypto/SelfTest/IO/test_PKCS8.py index 5dca2c2de..3525796be 100644 --- a/lib/Crypto/SelfTest/IO/test_PKCS8.py +++ b/lib/Crypto/SelfTest/IO/test_PKCS8.py @@ -341,6 +341,30 @@ """ )) +# hexdump -v -e '32/1 "%02x" "\n"' botan_scrypt.der +botan_scrypt = """ +3081f1305206092a864886f70d01050d3045302806092b06010401da47040b30 +1b040c316c5c7a847276a838a668280202200002010102010102012030190609 +60864801650304012e040c293e9bcddc0d59d64e060cb604819ab92318063480 +16148081a3123bb092b636ec0cc3b964628e181504c13eaf94987e6fb9f171d4 +9c45baeeb79c1d805d5a762d9bfd6d1995669df60a2cd0174b6d204693964de7 +05bc3fdc3a4ce5db01f30a994c82b0aac786e4f8655138c952f1cf2cc6093f90 +b5e5ca507beb539ff497b7b6370ba7f31f4928d3385dbe8bcd2395813ba1324e +6795e81a8518aff0f0a9e01396539f937b8b7b08 +""" + +# hexdump -v -e '32/1 "%02x" "\n"' botan_pbkdf2.der +botan_pbkdf2 = """ +3081f3305e06092a864886f70d01050d3051303006092a864886f70d01050c30 +23040cc91c89b368db578d2ec4c32002020fa0020118300c06082a864886f70d +02090500301d060960864801650304011604102a7147289e7c914a7d8257e4a1 +a2135b048190a648955fc96ecae56dcb4d0ab19edc5b7ef1219c88c7c3b2d0ed +b21e25d2559447f53e20b90b2f20e72456d943561c4925aad6067a4c720afb3d +691e14dfffa10ef77898e21d134f19136d35088a7aac508b296fd00d5742ad69 +8c693293b6a591e3660b130d718724d23d696f4da9bf4031475fafb682d7955c +996363f37032e10ac85afebb7cc1cbfc0e5d4c60a4c2 +""" + def txt2bin(inputs): s = b('').join([b(x) for x in inputs if not (x in '\n\r\t ')]) return unhexlify(s) @@ -413,6 +437,16 @@ def test4(self): randfunc=rng) self.assertEqual(wrapped, t[4]) + def test_import_botan_keys(self): + botan_scrypt_der = txt2bin(botan_scrypt) + key1 = PKCS8.unwrap(botan_scrypt_der, + b'your_password') + botan_pbkdf2_der = txt2bin(botan_pbkdf2) + key2 = PKCS8.unwrap(botan_pbkdf2_der, + b'your_password') + self.assertEqual(key1, key2) + + def get_tests(config={}): from Crypto.SelfTest.st_common import list_test_cases listTests = [] diff --git a/lib/Crypto/SelfTest/PublicKey/test_import_ECC.py b/lib/Crypto/SelfTest/PublicKey/test_import_ECC.py index 60d59c70d..aa54921ff 100644 --- a/lib/Crypto/SelfTest/PublicKey/test_import_ECC.py +++ b/lib/Crypto/SelfTest/PublicKey/test_import_ECC.py @@ -376,6 +376,15 @@ def test_import_private_pkcs8_encrypted_2(self): key = ECC.import_key(key_file, "secret") self.assertEqual(self.ref_private, key) + def test_import_private_pkcs8_encrypted_3(self): + key_file = load_file("ecc_p224_private_p8_2.der") + + key = ECC._import_der(key_file, "secret") + self.assertEqual(self.ref_private, key) + + key = ECC.import_key(key_file, "secret") + self.assertEqual(self.ref_private, key) + def test_import_x509_der(self): key_file = load_file("ecc_p224_x509.der") @@ -486,6 +495,15 @@ def test_import_private_pkcs8_encrypted_2(self): key = ECC.import_key(key_file, "secret") self.assertEqual(self.ref_private, key) + def test_import_private_pkcs8_encrypted_3(self): + key_file = load_file("ecc_p256_private_p8_2.der") + + key = ECC._import_der(key_file, "secret") + self.assertEqual(self.ref_private, key) + + key = ECC.import_key(key_file, "secret") + self.assertEqual(self.ref_private, key) + def test_import_x509_der(self): key_file = load_file("ecc_p256_x509.der") @@ -626,6 +644,15 @@ def test_import_private_pkcs8_encrypted_2(self): key = ECC.import_key(key_file, "secret") self.assertEqual(self.ref_private, key) + def test_import_private_pkcs8_encrypted_3(self): + key_file = load_file("ecc_p384_private_p8_2.der") + + key = ECC._import_der(key_file, "secret") + self.assertEqual(self.ref_private, key) + + key = ECC.import_key(key_file, "secret") + self.assertEqual(self.ref_private, key) + def test_import_x509_der(self): key_file = load_file("ecc_p384_x509.der") @@ -761,6 +788,15 @@ def test_import_private_pkcs8_encrypted_2(self): key = ECC.import_key(key_file, "secret") self.assertEqual(self.ref_private, key) + def test_import_private_pkcs8_encrypted_3(self): + key_file = load_file("ecc_p521_private_p8_2.der") + + key = ECC._import_der(key_file, "secret") + self.assertEqual(self.ref_private, key) + + key = ECC.import_key(key_file, "secret") + self.assertEqual(self.ref_private, key) + def test_import_x509_der(self): key_file = load_file("ecc_p521_x509.der") @@ -905,6 +941,15 @@ def test_export_private_pkcs8_encrypted(self): decoded = ECC.import_key(encoded, "secret") self.assertEqual(self.ref_private, decoded) + # --- + + encoded = self.ref_private.export_key(format="DER", + passphrase="secret", + protection="PBKDF2WithHMAC-SHA224AndAES192-CBC", + prot_params={'iteration_count':123}) + decoded = ECC.import_key(encoded, "secret") + self.assertEqual(self.ref_private, decoded) + def test_export_public_pem_uncompressed(self): key_file = load_file("ecc_p192_public.pem", "rt").strip() @@ -1155,7 +1200,8 @@ def test_export_private_pkcs8_encrypted(self): encoded = self.ref_private.export_key(format="DER", passphrase="secret", - protection="PBKDF2WithHMAC-SHA1AndAES128-CBC") + protection="PBKDF2WithHMAC-SHA512-224AndAES128-CBC", + prot_params={'iteration_count':123}) decoded = ECC.import_key(encoded, "secret") self.assertEqual(self.ref_private, decoded) @@ -1413,6 +1459,15 @@ def test_export_private_pkcs8_encrypted(self): decoded = ECC.import_key(encoded, "secret") self.assertEqual(self.ref_private, decoded) + # --- + + encoded = self.ref_private.export_key(format="DER", + passphrase="secret", + protection="PBKDF2WithHMAC-SHA512-256AndAES128-CBC", + prot_params={'iteration_count':123}) + decoded = ECC.import_key(encoded, "secret") + self.assertEqual(self.ref_private, decoded) + def test_export_public_pem_uncompressed(self): key_file = load_file("ecc_p256_public.pem", "rt").strip() @@ -1692,6 +1747,15 @@ def test_export_private_pkcs8_encrypted(self): decoded = ECC.import_key(encoded, "secret") self.assertEqual(self.ref_private, decoded) + # --- + + encoded = self.ref_private.export_key(format="DER", + passphrase="secret", + protection="PBKDF2WithHMAC-SHA384AndAES128-CBC", + prot_params={'iteration_count':123}) + decoded = ECC.import_key(encoded, "secret") + self.assertEqual(self.ref_private, decoded) + def test_export_public_pem_uncompressed(self): key_file = load_file("ecc_p384_public.pem", "rt").strip() @@ -1982,6 +2046,15 @@ def test_export_private_pkcs8_encrypted(self): decoded = ECC.import_key(encoded, "secret") self.assertEqual(self.ref_private, decoded) + # --- + + encoded = self.ref_private.export_key(format="DER", + passphrase="secret", + protection="PBKDF2WithHMAC-SHA1AndAES128-CBC", + prot_params={'iteration_count':123}) + decoded = ECC.import_key(encoded, "secret") + self.assertEqual(self.ref_private, decoded) + def test_export_public_pem_uncompressed(self): key_file = load_file("ecc_p521_public.pem", "rt").strip() @@ -2229,6 +2302,15 @@ def test_import_private_pkcs8_encrypted_2(self): key = ECC.import_key(key_file, "secret") self.assertEqual(self.ref_private, key) + def test_import_private_pkcs8_encrypted_3(self): + key_file = load_file("ecc_ed25519_private_p8_2.der") + + key = ECC._import_der(key_file, "secret") + self.assertEqual(self.ref_private, key) + + key = ECC.import_key(key_file, "secret") + self.assertEqual(self.ref_private, key) + def test_import_x509_der(self): key_file = load_file("ecc_ed25519_x509.der") @@ -2335,6 +2417,15 @@ def test_export_private_pkcs8_encrypted(self): decoded = ECC.import_key(encoded, "secret") self.assertEqual(self.ref_private, decoded) + # --- + + encoded = self.ref_private.export_key(format="DER", + passphrase="secret", + protection="PBKDF2WithHMAC-SHA256AndAES128-CBC", + prot_params={'iteration_count':123}) + decoded = ECC.import_key(encoded, "secret") + self.assertEqual(self.ref_private, decoded) + def test_export_public_pem(self): key_file_ref = load_file("ecc_ed25519_public.pem", "rt").strip() key_file = self.ref_public.export_key(format="PEM").strip() @@ -2458,6 +2549,15 @@ def test_import_private_pkcs8_encrypted_2(self): key = ECC.import_key(key_file, "secret") self.assertEqual(self.ref_private, key) + def test_import_private_pkcs8_encrypted_3(self): + key_file = load_file("ecc_ed448_private_p8_2.der") + + key = ECC._import_der(key_file, "secret") + self.assertEqual(self.ref_private, key) + + key = ECC.import_key(key_file, "secret") + self.assertEqual(self.ref_private, key) + def test_import_x509_der(self): key_file = load_file("ecc_ed448_x509.der") @@ -2549,6 +2649,16 @@ def test_export_private_pkcs8_encrypted(self): decoded = ECC.import_key(encoded, "secret") self.assertEqual(self.ref_private, decoded) + # --- + + encoded = self.ref_private.export_key(format="DER", + passphrase="secret", + protection="PBKDF2WithHMAC-SHA384AndAES128-CBC", + prot_params={'iteration_count':123}) + decoded = ECC.import_key(encoded, "secret") + self.assertEqual(self.ref_private, decoded) + + def test_export_public_pem(self): key_file_ref = load_file("ecc_ed448_public.pem", "rt").strip() key_file = self.ref_public.export_key(format="PEM").strip() diff --git a/lib/Crypto/SelfTest/PublicKey/test_import_RSA.py b/lib/Crypto/SelfTest/PublicKey/test_import_RSA.py index 94999414b..59a9c6d0f 100644 --- a/lib/Crypto/SelfTest/PublicKey/test_import_RSA.py +++ b/lib/Crypto/SelfTest/PublicKey/test_import_RSA.py @@ -28,6 +28,7 @@ from Crypto.PublicKey import RSA from Crypto.SelfTest.st_common import a2b_hex, list_test_cases +from Crypto.IO import PEM from Crypto.Util.py3compat import b, tostr, FileNotFoundError from Crypto.Util.number import inverse, bytes_to_long from Crypto.Util import asn1 @@ -419,6 +420,31 @@ def testExportKey15(self): key = RSA.construct([self.n, self.e, self.d, self.p, self.q, self.pInv]) self.assertRaises(ValueError, key.export_key, 'DER', 'test', 1) + def testExportKey16(self): + # Export and re-import the encrypted key. It must match. + # PEM envelope, PKCS#8, PKCS#8 encryption with parameters + key = RSA.construct([self.n, self.e, self.d, self.p, self.q, self.pInv]) + outkey = key.export_key('PEM', 'test', pkcs=8, + protection='PBKDF2WithHMAC-SHA512AndAES256-CBC', + prot_params={'iteration_count':123} + ) + self.assertTrue(tostr(outkey).find('4,ENCRYPTED')==-1) + self.assertTrue(tostr(outkey).find('BEGIN ENCRYPTED PRIVATE KEY')!=-1) + + # Verify the iteration count + der = PEM.decode(tostr(outkey))[0] + seq1 = asn1.DerSequence().decode(der) + seq2 = asn1.DerSequence().decode(seq1[0]) + seq3 = asn1.DerSequence().decode(seq2[1]) + seq4 = asn1.DerSequence().decode(seq3[0]) + seq5 = asn1.DerSequence().decode(seq4[1]) + self.assertEqual(seq5[1], 123) + + inkey = RSA.importKey(outkey, 'test') + self.assertEqual(key.n, inkey.n) + self.assertEqual(key.e, inkey.e) + self.assertEqual(key.d, inkey.d) + def test_import_key(self): """Verify that import_key is an alias to importKey""" key = RSA.import_key(self.rsaPublicKeyDER) @@ -570,6 +596,19 @@ def test_import_openssh_private_password(self): key_old = RSA.import_key(key_file_old) self.assertEqual(key, key_old) + def test_import_pkcs8_private(self): + key_file_ref = load_file("rsa2048_private.pem") + key_file = load_file("rsa2048_private_p8.der") + + # Skip test if test vectors are not installed + if None in (key_file_ref, key_file): + return + + key_ref = RSA.import_key(key_file_ref) + key = RSA.import_key(key_file, b'secret') + self.assertEqual(key_ref, key) + + if __name__ == '__main__': unittest.main() diff --git a/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/ecc_ed25519_private_p8_2.der b/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/ecc_ed25519_private_p8_2.der new file mode 100644 index 0000000000000000000000000000000000000000..64a733e5b8c29661bc92d1f8564728478b5cded0 GIT binary patch literal 158 zcmXqLoNW-!#;Mij(e|B}k(JlL%Rm#sI%3eU6I*F1aAc5lnMjLw5~E_2Qtp1CpC?7F<=<%u`m@AtQz(>+_~sLJy6`@-wg TQhC|`uzq8=llF4<)U*Hqe>*|Q literal 0 HcmV?d00001 diff --git a/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/ecc_p192_private_p8_2.der b/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/ecc_p192_private_p8_2.der new file mode 100644 index 000000000..e69de29bb diff --git a/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/ecc_p224_private_p8_2.der b/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/ecc_p224_private_p8_2.der new file mode 100644 index 0000000000000000000000000000000000000000..69a40b75be246c92538d481dde6f0df51a6b0384 GIT binary patch literal 223 zcmXqLykii~#;Mij(e|B}k(JlL%Rm#s;+ z16elCgfeRwari`Z+ud?u#?T7a`hPY`?ZBKc{wrc%{0=*|idMfQ=1?_$NBj(l3FuQr( S`Cj7!@0+VPEaQloaRUIe+*-f@ literal 0 HcmV?d00001 diff --git a/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/ecc_p256_private_p8_2.der b/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/ecc_p256_private_p8_2.der new file mode 100644 index 0000000000000000000000000000000000000000..b5c6ca59d99099eea1fd97c756afaeb867c553e1 GIT binary patch literal 239 zcmXqLd}9#K#;Mij(e|B}k(JlL%Rm#sC=Ee{iufu#Wt8wW%!6E`b^ zfh-$mLYoI;Dl-eC7K^~sH|}=-dN%M!C_g%SL%U=(OXGz6Y1Vbi-PSi(Z*f~0{I6d1 z)(7>_Lvxou4Ngj%#CzbS!yO*?Nz_>T6G?xZm|Xs@nTY`EXamTt0~v zs|)3unw}bTFIja}a$$m*d)UO{n(hehDd!4iel@thI#Nw5z^2Kkrps%s(w~sQ@; k^L+c?^kCYHtn4z?Tl8I4}|Ul5z%U431{ z?1J2^ebWUef8%g|Rl$61)9)v~6)L}f%LIqKOIoU=6e9lXd#z`1y{mL!){8>N2#mVYWTKi^uvnce&rr}x!EvlI&a-f>zwKPh}5U>mk~8^>?AV*Su=;U-q<|i&LNQU&90Uk0%mG9<*$<@x z0x&r+3dlk4&0ohyv3g9{M6CMvPMD2u=G6^yjqs z#3fT=MP{Xkw-piUbJ!g{cnj4gLyovWhzQ-c(>nsi$|&ZOSuZ?~!(-n4$M=PU2xpqx literal 0 HcmV?d00001 diff --git a/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ecc_p224.sh b/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ecc_p224.sh index 2e7909be3..de42f09ab 100755 --- a/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ecc_p224.sh +++ b/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ecc_p224.sh @@ -15,6 +15,7 @@ openssl ec -in ecc_p224_private.pem -text -out ecc_p224.txt # Encrypted private key openssl pkcs8 -in ecc_p224_private.der -inform DER -passout 'pass:secret' -out ecc_p224_private_p8.der -outform DER -topk8 openssl pkcs8 -in ecc_p224_private.der -inform DER -passout 'pass:secret' -out ecc_p224_private_p8.pem -outform PEM -topk8 +openssl pkcs8 -in ecc_p224_private.der -inform DER -passout 'pass:secret' -out ecc_p224_private_p8_2.der -outform DER -topk8 -iter 12345 -v2 aes256 -v2prf hmacWithSHA512 openssl ec -in ecc_p224_private.pem -des3 -out ecc_p224_private_enc_des3.pem -passout 'pass:secret' -outform PEM openssl ec -in ecc_p224_private.pem -aes128 -out ecc_p224_private_enc_aes128.pem -passout 'pass:secret' -outform PEM openssl ec -in ecc_p224_private.pem -aes192 -out ecc_p224_private_enc_aes192.pem -passout 'pass:secret' -outform PEM diff --git a/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ecc_p256.sh b/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ecc_p256.sh index ec08fdd76..7925af11c 100755 --- a/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ecc_p256.sh +++ b/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ecc_p256.sh @@ -15,6 +15,7 @@ openssl ec -in ecc_p256_private.pem -text -out ecc_p256.txt # Encrypted private key openssl pkcs8 -in ecc_p256_private.der -inform DER -passout 'pass:secret' -out ecc_p256_private_p8.der -outform DER -topk8 openssl pkcs8 -in ecc_p256_private.der -inform DER -passout 'pass:secret' -out ecc_p256_private_p8.pem -outform PEM -topk8 +openssl pkcs8 -in ecc_p256_private.der -inform DER -passout 'pass:secret' -out ecc_p256_private_p8_2.der -outform DER -topk8 -iter 12345 -v2 aes256 -v2prf hmacWithSHA512 openssl ec -in ecc_p256_private.pem -des3 -out ecc_p256_private_enc_des3.pem -passout 'pass:secret' -outform PEM openssl ec -in ecc_p256_private.pem -aes128 -out ecc_p256_private_enc_aes128.pem -passout 'pass:secret' -outform PEM openssl ec -in ecc_p256_private.pem -aes192 -out ecc_p256_private_enc_aes192.pem -passout 'pass:secret' -outform PEM diff --git a/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ecc_p384.sh b/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ecc_p384.sh index 61284f0fd..ea09dc913 100755 --- a/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ecc_p384.sh +++ b/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ecc_p384.sh @@ -15,6 +15,7 @@ openssl ec -in ecc_p384_private.pem -text -out ecc_p384.txt # Encrypted private key openssl pkcs8 -in ecc_p384_private.der -inform DER -passout 'pass:secret' -out ecc_p384_private_p8.der -outform DER -topk8 openssl pkcs8 -in ecc_p384_private.der -inform DER -passout 'pass:secret' -out ecc_p384_private_p8.pem -outform PEM -topk8 +openssl pkcs8 -in ecc_p384_private.der -inform DER -passout 'pass:secret' -out ecc_p384_private_p8_2.der -outform DER -topk8 -iter 12345 -v2 aes256 -v2prf hmacWithSHA512 openssl ec -in ecc_p384_private.pem -des3 -out ecc_p384_private_enc_des3.pem -passout 'pass:secret' -outform PEM openssl ec -in ecc_p384_private.pem -aes128 -out ecc_p384_private_enc_aes128.pem -passout 'pass:secret' -outform PEM openssl ec -in ecc_p384_private.pem -aes192 -out ecc_p384_private_enc_aes192.pem -passout 'pass:secret' -outform PEM diff --git a/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ecc_p521.sh b/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ecc_p521.sh index d03459478..89681b0bd 100755 --- a/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ecc_p521.sh +++ b/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ecc_p521.sh @@ -15,6 +15,7 @@ openssl ec -in ecc_p521_private.pem -text -out ecc_p521.txt # Encrypted private key openssl pkcs8 -in ecc_p521_private.der -inform DER -passout 'pass:secret' -out ecc_p521_private_p8.der -outform DER -topk8 openssl pkcs8 -in ecc_p521_private.der -inform DER -passout 'pass:secret' -out ecc_p521_private_p8.pem -outform PEM -topk8 +openssl pkcs8 -in ecc_p521_private.der -inform DER -passout 'pass:secret' -out ecc_p521_private_p8_2.der -outform DER -topk8 -iter 12345 -v2 aes256 -v2prf hmacWithSHA512 openssl ec -in ecc_p521_private.pem -des3 -out ecc_p521_private_enc_des3.pem -passout 'pass:secret' -outform PEM openssl ec -in ecc_p521_private.pem -aes128 -out ecc_p521_private_enc_aes128.pem -passout 'pass:secret' -outform PEM openssl ec -in ecc_p521_private.pem -aes192 -out ecc_p521_private_enc_aes192.pem -passout 'pass:secret' -outform PEM diff --git a/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ed25519.sh b/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ed25519.sh index 7f018b52b..3630b32be 100755 --- a/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ed25519.sh +++ b/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ed25519.sh @@ -16,6 +16,7 @@ openssl pkey -in ecc_ed25519_private.pem -text -out ecc_ed25519.txt # Traditional format (PEM enveloped) is unsupported for ed25519, so we only use encrypted PKCS#8 openssl pkcs8 -in ecc_ed25519_private.der -inform DER -passout 'pass:secret' -out ecc_ed25519_private_p8.der -outform DER -topk8 openssl pkcs8 -in ecc_ed25519_private.der -inform DER -passout 'pass:secret' -out ecc_ed25519_private_p8.pem -outform PEM -topk8 +openssl pkcs8 -in ecc_ed25519_private.der -inform DER -passout 'pass:secret' -out ecc_ed25519_private_p8_2.der -outform DER -topk8 -iter 12345 -v2 aes256 -v2prf hmacWithSHA512 openssl pkey -in ecc_ed25519_private.pem -des3 -out ecc_ed25519_private_enc_des3.pem -passout 'pass:secret' -outform PEM openssl pkey -in ecc_ed25519_private.pem -aes128 -out ecc_ed25519_private_enc_aes128.pem -passout 'pass:secret' -outform PEM openssl pkey -in ecc_ed25519_private.pem -aes192 -out ecc_ed25519_private_enc_aes192.pem -passout 'pass:secret' -outform PEM diff --git a/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ed448.sh b/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ed448.sh index d50df290c..cb165d426 100755 --- a/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ed448.sh +++ b/test_vectors/pycryptodome_test_vectors/PublicKey/ECC/gen_ed448.sh @@ -16,6 +16,7 @@ openssl pkey -in ecc_ed448_private.pem -text -out ecc_ed448.txt # Traditional format (PEM enveloped) is unsupported for ed448, so we only use encrypted PKCS#8 openssl pkcs8 -in ecc_ed448_private.der -inform DER -passout 'pass:secret' -out ecc_ed448_private_p8.der -outform DER -topk8 openssl pkcs8 -in ecc_ed448_private.der -inform DER -passout 'pass:secret' -out ecc_ed448_private_p8.pem -outform PEM -topk8 +openssl pkcs8 -in ecc_ed448_private.der -inform DER -passout 'pass:secret' -out ecc_ed448_private_p8_2.der -outform DER -topk8 -iter 12345 -v2 aes256 -v2prf hmacWithSHA512 openssl pkey -in ecc_ed448_private.pem -des3 -out ecc_ed448_private_enc_des3.pem -passout 'pass:secret' -outform PEM openssl pkey -in ecc_ed448_private.pem -aes128 -out ecc_ed448_private_enc_aes128.pem -passout 'pass:secret' -outform PEM openssl pkey -in ecc_ed448_private.pem -aes192 -out ecc_ed448_private_enc_aes192.pem -passout 'pass:secret' -outform PEM diff --git a/test_vectors/pycryptodome_test_vectors/PublicKey/RSA/gen_rsa_2048.sh b/test_vectors/pycryptodome_test_vectors/PublicKey/RSA/gen_rsa_2048.sh index 72bae671f..9732ca02e 100755 --- a/test_vectors/pycryptodome_test_vectors/PublicKey/RSA/gen_rsa_2048.sh +++ b/test_vectors/pycryptodome_test_vectors/PublicKey/RSA/gen_rsa_2048.sh @@ -7,6 +7,7 @@ openssl version | tee openssl_version.txt # Private key openssl genrsa -out rsa2048_private.pem 2048 +openssl pkcs8 -in rsa2048_private.pem -topk8 -iter 12345 -out rsa2048_private_p8.der -outform DER -v2 aes256 -v2prf hmacWithSHA512 -passout pass:secret # OpenSSH chmod 600 rsa2048_private.pem diff --git a/test_vectors/pycryptodome_test_vectors/PublicKey/RSA/rsa2048_private_p8.der b/test_vectors/pycryptodome_test_vectors/PublicKey/RSA/rsa2048_private_p8.der new file mode 100644 index 0000000000000000000000000000000000000000..410b582542428b565d050d5b2342ada916387a9f GIT binary patch literal 1329 zcmV-11&LNQU&90Uko_M9guZ$jq+ z0x&r+3Cx{V09muC z&Mpt(qr#Rb-4_dcu+l<;Q?&wFuqTvpumXgOip{wKVLmca0QW^BOn3 z>pglY{wR5a_6VNv#<(xa!1n$8{N#gHyFZh#{9VP0o=h}S0eLx);9CNBrFPC-(JyiB z!f|N;_?W3swDWloRWD7ESp0m>8N4dBXpGAKZlXfBg?wH?%gFrxgpNO!UMh@c2%i-k zo_F9v4IYzY3ch2e(>udY0=l2xj`U-lOzf=hQv#N^9qG$B5S>kcD6@lMLfHGaY8XIN zJ!5!VrH~ZM#wu_(bjB`#w(@B9xkaVaWw^+X5e-pnB6ttCS7SXs+g-x_zD zdOuJ}efC`Ec7TOUg-_dsuIkelqZo@6Rfd8#cQh#JG5imK^tFAWhxD$Sj=qB#_D^h!gs{o{Qj^}dJ@T3kvC%Fsiqh^`9Tr&4Cf0U=6@nos-eST>-C?2 z0-gv-QDLlvN0a@$bk}rizx0L?$9xRYw{#v^jtSjtlR3GJ)R>6b2gwZ9bQp5+LJ5|e z75sg^@Da_K<_=S=f|vSHA%2}rpX#K^YZlU!`HI_e;#KoULizq2o?Q;EUu!%_eMbG~*U4oY+%iu1US(q)96w;^-+ zC%Md3&mv;)g(XwPXz;(NigyOWoH&XBcIZ!+K9 zeRP}By;|`9Ky?$(CN_rVUS4F0lUN%Zyd^}-BL?dVJa2_`mY?kmBiEde@}roQS~evz zCKb(fF1Q9S?bQ#)WCfKX@1u2jWvwRWXHD$xZvv(|1o5+ZrlX7#dje^RULEOb?PZ#+!GsSBNpR$g6)kE$ z-Ax?K(ZalTboK@^ID@B;i0PhvGBML(*Dk)PpDATCqZwSML7<_d53B_2V-WvgcIEFX +#include +#include +#include +#include + +#define array_size(arr) (sizeof(arr) / sizeof(*(arr))) + +int main() { + try { + const std::string schemes[] = { + "PBE-PKCS5v20(AES-256/GCM,Scrypt)", + "PBE-PKCS5v20(AES-192/CBC,SHA-256)", + }; + std::string password = "your_password"; + + // Generate an ECC key pair + Botan::AutoSeeded_RNG rng; + Botan::EC_Group ec_group("secp256r1"); + Botan::ECDSA_PrivateKey private_key(rng, ec_group); + + for (int i=0; i