From 6f0d8236b8622a68f42284ed1314d8acd86c89ed Mon Sep 17 00:00:00 2001 From: Rob Austein Date: Tue, 4 Jan 2022 09:12:47 -0500 Subject: Replace old PyCrypto with PyCryptodome PyCrypto is no longer present in Debian Bullseye and is abandonware in anycase. PyCryptodome is about 98% of a drop-in replacement (but that last 2% can be tricky), so convert the most critical stuff to use PyCryptodome. A bunch of the test scripts and so forth still need to be converted, for today the goals are just to have the package install properly and to be able to run the unit tests. --- cryptech/libhal.py | 27 ++++++++++++-------- cryptech_backup | 16 ++++++------ unit-tests.py | 72 ++++++++++++++++++++++++++---------------------------- 3 files changed, 60 insertions(+), 55 deletions(-) diff --git a/cryptech/libhal.py b/cryptech/libhal.py index 102e663..105dd02 100644 --- a/cryptech/libhal.py +++ b/cryptech/libhal.py @@ -347,7 +347,8 @@ class LocalDigest(object): """ def __init__(self, hsm, handle, algorithm, key): - from Crypto.Hash import HMAC, SHA, SHA224, SHA256, SHA384, SHA512 + from Cryptodome.Hash import HMAC, SHA1, SHA224, SHA256, SHA384, SHA512 + from Cryptodome.Util.asn1 import DerObjectId from struct import pack self.hsm = hsm self.handle = handle @@ -356,16 +357,22 @@ class LocalDigest(object): h = self._algorithms[algorithm] except AttributeError: self._algorithms = { - HAL_DIGEST_ALGORITHM_SHA1 : SHA.SHA1Hash, - HAL_DIGEST_ALGORITHM_SHA224 : SHA224.SHA224Hash, - HAL_DIGEST_ALGORITHM_SHA256 : SHA256.SHA256Hash, - HAL_DIGEST_ALGORITHM_SHA384 : SHA384.SHA384Hash, - HAL_DIGEST_ALGORITHM_SHA512 : SHA512.SHA512Hash + HAL_DIGEST_ALGORITHM_SHA1 : SHA1, + HAL_DIGEST_ALGORITHM_SHA224 : SHA224, + HAL_DIGEST_ALGORITHM_SHA256 : SHA256, + HAL_DIGEST_ALGORITHM_SHA384 : SHA384, + HAL_DIGEST_ALGORITHM_SHA512 : SHA512 } h = self._algorithms[algorithm] self.digest_length = h.digest_size - self.algorithm_id = pack("BB", 0x30, 2 + len(h.oid)) + h.oid - self._context = HMAC.HMAC(key = key, digestmod = h) if key else h() + if key: + self._context = HMAC.new(key = key, digestmod = h) + oid = h.new().oid + else: + self._context = h.new() + oid = self._context.oid + self.oid = DerObjectId(oid).encode() + self.algorithm_id = pack("BB", 0x30, 2 + len(self.oid)) + self.oid def update(self, data): self._context.update(data) @@ -377,8 +384,8 @@ class LocalDigest(object): if pkey.key_type not in (HAL_KEY_TYPE_RSA_PRIVATE, HAL_KEY_TYPE_RSA_PUBLIC): return self.finalize() # PKCS #1.5 requires the digest to be wrapped up in an ASN.1 DigestInfo object. - from Crypto.Util.asn1 import DerSequence, DerNull, DerOctetString - return DerSequence([DerSequence([self._context.oid, DerNull().encode()]).encode(), + from Cryptodome.Util.asn1 import DerSequence, DerNull, DerOctetString + return DerSequence([DerSequence([self.oid, DerNull().encode()]).encode(), DerOctetString(self.finalize()).encode()]).encode() diff --git a/cryptech_backup b/cryptech_backup index a15c9c0..99d2c38 100755 --- a/cryptech_backup +++ b/cryptech_backup @@ -21,7 +21,7 @@ We also implement a software-based variant on this backup mechanism, for cases where there is no second HSM. The protocol is much the same, but the KEKEK is generated in software and encrypted using a symmetric key derived from a passphrase using PBKDF2. This requires -the PyCrypto library, and is only as secure as memory on the machine +the PyCryptodome library, and is only as secure as memory on the machine where you're running it (so it's theoretically vulnerable to root or anybody with access to /dev/mem). Don't use this mode unless you understand the risks, and see the "NOTE WELL" above. @@ -305,7 +305,7 @@ class AESKeyWrapWithPadding(object): "Something went wrong during unwrap." def __init__(self, key): - from Crypto.Cipher import AES + from Cryptodome.Cipher import AES self.ctx = AES.new(key, AES.MODE_ECB) def _encrypt(self, b1, b2): @@ -391,7 +391,7 @@ class SoftKEKEK(object): time.clock = time.process_time def parse_EncryptedPrivateKeyInfo(self, der): - from Crypto.Util.asn1 import DerObject, DerSequence, DerOctetString, DerObjectId + from Cryptodome.Util.asn1 import DerObject, DerSequence, DerOctetString, DerObjectId encryptedPrivateKeyInfo = DerSequence() encryptedPrivateKeyInfo.decode(der) encryptionAlgorithm = DerSequence() @@ -405,7 +405,7 @@ class SoftKEKEK(object): return encryptedData.payload def encode_EncryptedPrivateKeyInfo(self, der): - from Crypto.Util.asn1 import DerSequence, DerOctetString + from Cryptodome.Util.asn1 import DerSequence, DerOctetString return DerSequence([ DerSequence([ struct.pack("BB", 0x06, len(self.oid_aesKeyWrap)) + self.oid_aesKeyWrap @@ -414,12 +414,12 @@ class SoftKEKEK(object): ]).encode() def gen_salt(self, bytes = 16): - from Crypto import Random + from Cryptodome import Random return Random.new().read(bytes) def wrapper(self, salt, keylen = 256, iterations = 8000): - from Crypto.Protocol.KDF import PBKDF2 - from Crypto.Hash import SHA256, HMAC + from Cryptodome.Protocol.KDF import PBKDF2 + from Cryptodome.Hash import SHA256, HMAC return AESKeyWrapWithPadding(PBKDF2( password = getpass.getpass("KEKEK Passphrase: "), salt = salt, @@ -433,7 +433,7 @@ class SoftKEKEK(object): @classmethod def generate(cls, args, result): - from Crypto.PublicKey import RSA + from Cryptodome.PublicKey import RSA self = cls() k = RSA.generate(args.keylen) salt = self.gen_salt() diff --git a/unit-tests.py b/unit-tests.py index 526379c..bdb6f77 100644 --- a/unit-tests.py +++ b/unit-tests.py @@ -49,19 +49,17 @@ from struct import pack, unpack from cryptech.libhal import * try: - from Crypto.Util.number import inverse - from Crypto.PublicKey import RSA - from Crypto.Cipher import AES - from Crypto.Cipher.PKCS1_v1_5 import PKCS115_Cipher - from Crypto.Signature.PKCS1_v1_5 import PKCS115_SigScheme - from Crypto.Hash.SHA256 import SHA256Hash as SHA256 - from Crypto.Hash.SHA384 import SHA384Hash as SHA384 - from Crypto.Hash.SHA512 import SHA512Hash as SHA512 + from Cryptodome.Util.number import inverse + from Cryptodome.PublicKey import RSA + from Cryptodome.Cipher import AES, PKCS1_v1_5 + from Cryptodome.Signature import pkcs1_15 + from Cryptodome.Hash.SHA256 import new as SHA256 + from Cryptodome.Hash.SHA384 import new as SHA384 + from Cryptodome.Hash.SHA512 import new as SHA512 pycrypto_loaded = True except ImportError: pycrypto_loaded = False - try: from ecdsa import der as ECDSA_DER from ecdsa.keys import SigningKey as ECDSA_SigningKey @@ -998,15 +996,15 @@ class TestPKeyHashing(TestCaseLoggedIn): k1.verify(signature = sig, hash = self.h(alg, mixed_mode = True)) k2.verify(signature = sig, hash = self.h(alg, mixed_mode = True)) - @unittest.skipUnless(pycrypto_loaded, "Requires Python Crypto package") + @unittest.skipUnless(pycrypto_loaded, "Requires Python Cryptodome package") def test_load_sign_verify_rsa_1024_sha256_data(self): self.load_sign_verify_rsa(HAL_DIGEST_ALGORITHM_SHA256, 1024, self.sign_verify_data) - @unittest.skipUnless(pycrypto_loaded, "Requires Python Crypto package") + @unittest.skipUnless(pycrypto_loaded, "Requires Python Cryptodome package") def test_load_sign_verify_rsa_2048_sha384_data(self): self.load_sign_verify_rsa(HAL_DIGEST_ALGORITHM_SHA384, 2048, self.sign_verify_data) - @unittest.skipUnless(pycrypto_loaded, "Requires Python Crypto package") + @unittest.skipUnless(pycrypto_loaded, "Requires Python Cryptodome package") def test_load_sign_verify_rsa_4096_sha512_data(self): self.load_sign_verify_rsa(HAL_DIGEST_ALGORITHM_SHA512, 4096, self.sign_verify_data) @@ -1022,15 +1020,15 @@ class TestPKeyHashing(TestCaseLoggedIn): def test_load_sign_verify_ecdsa_p521_sha512_data(self): self.load_sign_verify_ecdsa(HAL_DIGEST_ALGORITHM_SHA512, HAL_CURVE_P521, self.sign_verify_data) - @unittest.skipUnless(pycrypto_loaded, "Requires Python Crypto package") + @unittest.skipUnless(pycrypto_loaded, "Requires Python Cryptodome package") def test_load_sign_verify_rsa_1024_sha256_remote_remote(self): self.load_sign_verify_rsa(HAL_DIGEST_ALGORITHM_SHA256, 1024, self.sign_verify_remote_remote) - @unittest.skipUnless(pycrypto_loaded, "Requires Python Crypto package") + @unittest.skipUnless(pycrypto_loaded, "Requires Python Cryptodome package") def test_load_sign_verify_rsa_2048_sha384_remote_remote(self): self.load_sign_verify_rsa(HAL_DIGEST_ALGORITHM_SHA384, 2048, self.sign_verify_remote_remote) - @unittest.skipUnless(pycrypto_loaded, "Requires Python Crypto package") + @unittest.skipUnless(pycrypto_loaded, "Requires Python Cryptodome package") def test_load_sign_verify_rsa_4096_sha512_remote_remote(self): self.load_sign_verify_rsa(HAL_DIGEST_ALGORITHM_SHA512, 4096, self.sign_verify_remote_remote) @@ -1046,15 +1044,15 @@ class TestPKeyHashing(TestCaseLoggedIn): def test_load_sign_verify_ecdsa_p521_sha512_remote_remote(self): self.load_sign_verify_ecdsa(HAL_DIGEST_ALGORITHM_SHA512, HAL_CURVE_P521, self.sign_verify_remote_remote) - @unittest.skipUnless(pycrypto_loaded, "Requires Python Crypto package") + @unittest.skipUnless(pycrypto_loaded, "Requires Python Cryptodome package") def test_load_sign_verify_rsa_1024_sha256_remote_local(self): self.load_sign_verify_rsa(HAL_DIGEST_ALGORITHM_SHA256, 1024, self.sign_verify_remote_local) - @unittest.skipUnless(pycrypto_loaded, "Requires Python Crypto package") + @unittest.skipUnless(pycrypto_loaded, "Requires Python Cryptodome package") def test_load_sign_verify_rsa_2048_sha384_remote_local(self): self.load_sign_verify_rsa(HAL_DIGEST_ALGORITHM_SHA384, 2048, self.sign_verify_remote_local) - @unittest.skipUnless(pycrypto_loaded, "Requires Python Crypto package") + @unittest.skipUnless(pycrypto_loaded, "Requires Python Cryptodome package") def test_load_sign_verify_rsa_4096_sha512_remote_local(self): self.load_sign_verify_rsa(HAL_DIGEST_ALGORITHM_SHA512, 4096, self.sign_verify_remote_local) @@ -1070,15 +1068,15 @@ class TestPKeyHashing(TestCaseLoggedIn): def test_load_sign_verify_ecdsa_p521_sha512_remote_local(self): self.load_sign_verify_ecdsa(HAL_DIGEST_ALGORITHM_SHA512, HAL_CURVE_P521, self.sign_verify_remote_local) - @unittest.skipUnless(pycrypto_loaded, "Requires Python Crypto package") + @unittest.skipUnless(pycrypto_loaded, "Requires Python Cryptodome package") def test_load_sign_verify_rsa_1024_sha256_local_remote(self): self.load_sign_verify_rsa(HAL_DIGEST_ALGORITHM_SHA256, 1024, self.sign_verify_local_remote) - @unittest.skipUnless(pycrypto_loaded, "Requires Python Crypto package") + @unittest.skipUnless(pycrypto_loaded, "Requires Python Cryptodome package") def test_load_sign_verify_rsa_2048_sha384_local_remote(self): self.load_sign_verify_rsa(HAL_DIGEST_ALGORITHM_SHA384, 2048, self.sign_verify_local_remote) - @unittest.skipUnless(pycrypto_loaded, "Requires Python Crypto package") + @unittest.skipUnless(pycrypto_loaded, "Requires Python Cryptodome package") def test_load_sign_verify_rsa_4096_sha512_local_remote(self): self.load_sign_verify_rsa(HAL_DIGEST_ALGORITHM_SHA512, 4096, self.sign_verify_local_remote) @@ -1094,15 +1092,15 @@ class TestPKeyHashing(TestCaseLoggedIn): def test_load_sign_verify_ecdsa_p521_sha512_local_remote(self): self.load_sign_verify_ecdsa(HAL_DIGEST_ALGORITHM_SHA512, HAL_CURVE_P521, self.sign_verify_local_remote) - @unittest.skipUnless(pycrypto_loaded, "Requires Python Crypto package") + @unittest.skipUnless(pycrypto_loaded, "Requires Python Cryptodome package") def test_load_sign_verify_rsa_1024_sha256_local_local(self): self.load_sign_verify_rsa(HAL_DIGEST_ALGORITHM_SHA256, 1024, self.sign_verify_local_local) - @unittest.skipUnless(pycrypto_loaded, "Requires Python Crypto package") + @unittest.skipUnless(pycrypto_loaded, "Requires Python Cryptodome package") def test_load_sign_verify_rsa_2048_sha384_local_local(self): self.load_sign_verify_rsa(HAL_DIGEST_ALGORITHM_SHA384, 2048, self.sign_verify_local_local) - @unittest.skipUnless(pycrypto_loaded, "Requires Python Crypto package") + @unittest.skipUnless(pycrypto_loaded, "Requires Python Cryptodome package") def test_load_sign_verify_rsa_4096_sha512_local_local(self): self.load_sign_verify_rsa(HAL_DIGEST_ALGORITHM_SHA512, 4096, self.sign_verify_local_local) @@ -1131,7 +1129,7 @@ class TestPKeyHashing(TestCaseLoggedIn): self.gen_sign_verify_hashsig(1, HAL_LMS_SHA256_N32_H5, HAL_LMOTS_SHA256_N32_W4, 2352, self.sign_verify_local_local) -@unittest.skipUnless(pycrypto_loaded, "Requires Python Crypto package") +@unittest.skipUnless(pycrypto_loaded, "Requires Python Cryptodome package") class TestPKeyRSAInterop(TestCaseLoggedIn): @staticmethod @@ -1575,7 +1573,7 @@ class TestPkeyECDSAVerificationNIST(TestCaseLoggedIn): py_hash = SHA384) -@unittest.skipUnless(pycrypto_loaded, "Requires Python Crypto package") +@unittest.skipUnless(pycrypto_loaded, "Requires Python Cryptodome package") class TestPKeyBackup(TestCaseLoggedIn): oid_rsaEncryption = b"\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01" @@ -1583,7 +1581,7 @@ class TestPKeyBackup(TestCaseLoggedIn): @staticmethod def parse_EncryptedPrivateKeyInfo(der, oid): - from Crypto.Util.asn1 import DerObject, DerSequence, DerOctetString, DerObjectId + from Cryptodome.Util.asn1 import DerObject, DerSequence, DerOctetString, DerObjectId encryptedPrivateKeyInfo = DerSequence() encryptedPrivateKeyInfo.decode(der) encryptionAlgorithm = DerSequence() @@ -1606,7 +1604,7 @@ class TestPKeyBackup(TestCaseLoggedIn): @staticmethod def encode_EncryptedPrivateKeyInfo(der, oid): - from Crypto.Util.asn1 import DerSequence, DerOctetString + from Cryptodome.Util.asn1 import DerSequence, DerOctetString return DerSequence([ DerSequence([pack("BB", 0x06, len(oid)) + oid]).encode(), DerOctetString(der).encode() @@ -1614,15 +1612,15 @@ class TestPKeyBackup(TestCaseLoggedIn): @staticmethod def make_kek(): - import Crypto.Random - return Crypto.Random.new().read(256//8) + import Cryptodome.Random + return Cryptodome.Random.new().read(256//8) def sig_check(self, pkey, der): - from Crypto.Util.asn1 import DerSequence, DerNull, DerOctetString - p115 = PKCS115_SigScheme(RSA.importKey(der)) + from Cryptodome.Util.asn1 import DerSequence, DerNull, DerOctetString, DerObjectId + p115 = pkcs1_15.new(RSA.importKey(der)) hash = SHA256("Your mother was a hamster".encode()) data = DerSequence([ - DerSequence([hash.oid, DerNull().encode()]).encode(), + DerSequence([DerObjectId(hash.oid).encode(), DerNull().encode()]).encode(), DerOctetString(hash.digest()).encode() ]).encode() sig1 = p115.sign(hash) @@ -1643,7 +1641,7 @@ class TestPKeyBackup(TestCaseLoggedIn): flags = HAL_KEY_FLAG_USAGE_DIGITALSIGNATURE | HAL_KEY_FLAG_EXPORTABLE) self.addCleanup(pkey.delete) pkcs8_der, kek_der = kekek.export_pkey(pkey) - kek = PKCS115_Cipher(PreloadedKey.db[HAL_KEY_TYPE_RSA_PRIVATE, 1024].obj).decrypt( + kek = PKCS1_v1_5.new(PreloadedKey.db[HAL_KEY_TYPE_RSA_PRIVATE, 1024].obj).decrypt( self.parse_EncryptedPrivateKeyInfo(kek_der, self.oid_rsaEncryption), self.make_kek()) der = AESKeyWrapWithPadding(kek).unwrap( @@ -1662,7 +1660,7 @@ class TestPKeyBackup(TestCaseLoggedIn): AESKeyWrapWithPadding(kek).wrap(der), self.oid_aesKeyWrap), kek = self.encode_EncryptedPrivateKeyInfo( - PKCS115_Cipher(RSA.importKey(kekek.public_key)).encrypt(kek), + PKCS1_v1_5.new(RSA.importKey(kekek.public_key)).encrypt(kek), self.oid_rsaEncryption), flags = HAL_KEY_FLAG_USAGE_DIGITALSIGNATURE) self.addCleanup(pkey.delete) @@ -1800,10 +1798,10 @@ class PreloadedRSAKey(PreloadedKey): k2, k2.exportKey(format = "DER" ), keylen = keylen) def sign(self, text, hash): - return PKCS115_SigScheme(self.obj).sign(hash(text)) + return pkcs1_15.new(self.obj).sign(hash(text)) def verify(self, text, hash, signature): - return PKCS115_SigScheme(self.obj).verify(hash(text), signature) + return pkcs1_15.new(self.obj).verify(hash(text), signature) class PreloadedECKey(PreloadedKey): -- cgit v1.2.3