From e163137893f3cf2cdbc9cbb435650ff922ff70ad Mon Sep 17 00:00:00 2001 From: Dmitri Bogomolov <4glitch@gmail.com> Date: Thu, 31 Jan 2019 17:42:22 +0200 Subject: [PATCH] Added pycrypto fallback for RIPEMD160 hash function --- requirements.txt | 1 + src/class_addressGenerator.py | 37 +++++++++++---------- src/class_objectProcessor.py | 18 ++++------- src/depends.py | 36 ++++----------------- src/fallback/__init__.py | 23 ++++++++++++++ src/protocol.py | 5 ++- src/tests/test_crypto.py | 60 +++++++++++++++++++++++++++++++++++ 7 files changed, 118 insertions(+), 62 deletions(-) create mode 100644 src/tests/test_crypto.py diff --git a/requirements.txt b/requirements.txt index e20650d5..c55e5cf1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ python_prctl psutil +pycrypto diff --git a/src/class_addressGenerator.py b/src/class_addressGenerator.py index a5750813..0893b73a 100644 --- a/src/class_addressGenerator.py +++ b/src/class_addressGenerator.py @@ -15,6 +15,7 @@ import highlevelcrypto from bmconfigparser import BMConfigParser from debug import logger from addresses import decodeAddress, encodeAddress, encodeVarint +from fallback import RIPEMD160Hash from helper_threading import StoppableThread @@ -133,16 +134,17 @@ class addressGenerator(threading.Thread, StoppableThread): potentialPrivEncryptionKey = OpenSSL.rand(32) potentialPubEncryptionKey = highlevelcrypto.pointMult( potentialPrivEncryptionKey) - ripe = hashlib.new('ripemd160') sha = hashlib.new('sha512') sha.update( potentialPubSigningKey + potentialPubEncryptionKey) - ripe.update(sha.digest()) - if ripe.digest()[:numberOfNullBytesDemandedOnFrontOfRipeHash] == '\x00' * numberOfNullBytesDemandedOnFrontOfRipeHash: + ripe = RIPEMD160Hash(sha.digest()).digest() + if ( + ripe[:numberOfNullBytesDemandedOnFrontOfRipeHash] == + '\x00' * numberOfNullBytesDemandedOnFrontOfRipeHash + ): break logger.info( - 'Generated address with ripe digest: %s', - hexlify(ripe.digest())) + 'Generated address with ripe digest: %s', hexlify(ripe)) try: logger.info( 'Address generator calculated %s addresses at %s' @@ -156,7 +158,7 @@ class addressGenerator(threading.Thread, StoppableThread): # time.time() - startTime equaled zero. pass address = encodeAddress( - addressVersionNumber, streamNumber, ripe.digest()) + addressVersionNumber, streamNumber, ripe) # An excellent way for us to store our keys # is in Wallet Import Format. Let us convert now. @@ -203,7 +205,7 @@ class addressGenerator(threading.Thread, StoppableThread): shared.reloadMyAddressHashes() if addressVersionNumber == 3: queues.workerQueue.put(( - 'sendOutOrStoreMyV3Pubkey', ripe.digest())) + 'sendOutOrStoreMyV3Pubkey', ripe)) elif addressVersionNumber == 4: queues.workerQueue.put(( 'sendOutOrStoreMyV4Pubkey', address)) @@ -255,17 +257,18 @@ class addressGenerator(threading.Thread, StoppableThread): potentialPrivEncryptionKey) signingKeyNonce += 2 encryptionKeyNonce += 2 - ripe = hashlib.new('ripemd160') sha = hashlib.new('sha512') sha.update( potentialPubSigningKey + potentialPubEncryptionKey) - ripe.update(sha.digest()) - if ripe.digest()[:numberOfNullBytesDemandedOnFrontOfRipeHash] == '\x00' * numberOfNullBytesDemandedOnFrontOfRipeHash: + ripe = RIPEMD160Hash(sha.digest()).digest() + if ( + ripe[:numberOfNullBytesDemandedOnFrontOfRipeHash] == + '\x00' * numberOfNullBytesDemandedOnFrontOfRipeHash + ): break logger.info( - 'Generated address with ripe digest: %s', - hexlify(ripe.digest())) + 'Generated address with ripe digest: %s', hexlify(ripe)) try: logger.info( 'Address generator calculated %s addresses' @@ -280,7 +283,7 @@ class addressGenerator(threading.Thread, StoppableThread): # time.time() - startTime equaled zero. pass address = encodeAddress( - addressVersionNumber, streamNumber, ripe.digest()) + addressVersionNumber, streamNumber, ripe) saveAddressToDisk = True # If we are joining an existing chan, let us check @@ -357,13 +360,13 @@ class addressGenerator(threading.Thread, StoppableThread): )) listOfNewAddressesToSendOutThroughTheAPI.append( address) - shared.myECCryptorObjects[ripe.digest()] = \ + shared.myECCryptorObjects[ripe] = \ highlevelcrypto.makeCryptor( hexlify(potentialPrivEncryptionKey)) - shared.myAddressesByHash[ripe.digest()] = address + shared.myAddressesByHash[ripe] = address tag = hashlib.sha512(hashlib.sha512( encodeVarint(addressVersionNumber) + - encodeVarint(streamNumber) + ripe.digest() + encodeVarint(streamNumber) + ripe ).digest()).digest()[32:] shared.myAddressesByTag[tag] = address if addressVersionNumber == 3: @@ -371,7 +374,7 @@ class addressGenerator(threading.Thread, StoppableThread): # the worker thread won't send out # the pubkey over the network. queues.workerQueue.put(( - 'sendOutOrStoreMyV3Pubkey', ripe.digest())) + 'sendOutOrStoreMyV3Pubkey', ripe)) elif addressVersionNumber == 4: queues.workerQueue.put(( 'sendOutOrStoreMyV4Pubkey', address)) diff --git a/src/class_objectProcessor.py b/src/class_objectProcessor.py index 87b8eb21..62713b7b 100644 --- a/src/class_objectProcessor.py +++ b/src/class_objectProcessor.py @@ -24,6 +24,7 @@ import queues import state import tr from debug import logger +from fallback import RIPEMD160Hash import l10n @@ -288,9 +289,7 @@ class objectProcessor(threading.Thread): sha = hashlib.new('sha512') sha.update( '\x04' + publicSigningKey + '\x04' + publicEncryptionKey) - ripeHasher = hashlib.new('ripemd160') - ripeHasher.update(sha.digest()) - ripe = ripeHasher.digest() + ripe = RIPEMD160Hash(sha.digest()).digest() logger.debug( 'within recpubkey, addressVersion: %s, streamNumber: %s' @@ -354,9 +353,7 @@ class objectProcessor(threading.Thread): sha = hashlib.new('sha512') sha.update(publicSigningKey + publicEncryptionKey) - ripeHasher = hashlib.new('ripemd160') - ripeHasher.update(sha.digest()) - ripe = ripeHasher.digest() + ripe = RIPEMD160Hash(sha.digest()).digest() logger.debug( 'within recpubkey, addressVersion: %s, streamNumber: %s' @@ -575,10 +572,9 @@ class objectProcessor(threading.Thread): # calculate the fromRipe. sha = hashlib.new('sha512') sha.update(pubSigningKey + pubEncryptionKey) - ripe = hashlib.new('ripemd160') - ripe.update(sha.digest()) + ripe = RIPEMD160Hash(sha.digest()).digest() fromAddress = encodeAddress( - sendersAddressVersionNumber, sendersStreamNumber, ripe.digest()) + sendersAddressVersionNumber, sendersStreamNumber, ripe) # Let's store the public key in case we want to reply to this # person. @@ -897,9 +893,7 @@ class objectProcessor(threading.Thread): sha = hashlib.new('sha512') sha.update(sendersPubSigningKey + sendersPubEncryptionKey) - ripeHasher = hashlib.new('ripemd160') - ripeHasher.update(sha.digest()) - calculatedRipe = ripeHasher.digest() + calculatedRipe = RIPEMD160Hash(sha.digest()).digest() if broadcastVersion == 4: if toRipe != calculatedRipe: diff --git a/src/depends.py b/src/depends.py index 9a2e2158..53bd0184 100755 --- a/src/depends.py +++ b/src/depends.py @@ -162,37 +162,13 @@ def try_import(module, log_extra=False): return False -# We need to check hashlib for RIPEMD-160, as it won't be available -# if OpenSSL is not linked against or the linked OpenSSL has RIPEMD -# disabled. -def check_hashlib(): - """Do hashlib check. - - The hashlib module check with version as if it included or not - in The Python Standard library, it's a module containing an - interface to the most popular hashing algorithms. hashlib - implements some of the algorithms, however if OpenSSL - installed, hashlib is able to use this algorithms as well. - """ - if sys.hexversion < 0x020500F0: - logger.error( - 'The hashlib module is not included in this version of Python.') - return False - import hashlib - if '_hashlib' not in hashlib.__dict__: - logger.error( - 'The RIPEMD-160 hash algorithm is not available.' - ' The hashlib module is not linked against OpenSSL.') - return False +def check_ripemd160(): + """Check availability of the RIPEMD160 hash function""" try: - hashlib.new('ripemd160') - except ValueError: - logger.error( - 'The RIPEMD-160 hash algorithm is not available.' - ' The hashlib module utilizes an OpenSSL library with' - ' RIPEMD disabled.') + from fallback import RIPEMD160Hash + except ImportError: return False - return True + return RIPEMD160Hash is not None def check_sqlite(): @@ -446,7 +422,7 @@ def check_dependencies(verbose=False, optional=False): ' or greater is required.') has_all_dependencies = False - check_functions = [check_hashlib, check_sqlite, check_openssl] + check_functions = [check_ripemd160, check_sqlite, check_openssl] if optional: check_functions.extend([check_msgpack, check_pyqt, check_curses]) diff --git a/src/fallback/__init__.py b/src/fallback/__init__.py index 9ac081b6..d45c754d 100644 --- a/src/fallback/__init__.py +++ b/src/fallback/__init__.py @@ -1,3 +1,26 @@ """ .. todo:: hello world """ + +import hashlib + +# We need to check hashlib for RIPEMD-160, as it won't be available +# if OpenSSL is not linked against or the linked OpenSSL has RIPEMD +# disabled. + +try: + hashlib.new('ripemd160') +except ValueError: + try: + from Crypto.Hash import RIPEMD + except ImportError: + RIPEMD160Hash = None + else: + RIPEMD160Hash = RIPEMD.RIPEMD160Hash +else: + def RIPEMD160Hash(data=None): + """hashlib based RIPEMD160Hash""" + hasher = hashlib.new('ripemd160') + if data: + hasher.update(data) + return hasher diff --git a/src/protocol.py b/src/protocol.py index fd37dc44..6f89c186 100644 --- a/src/protocol.py +++ b/src/protocol.py @@ -25,6 +25,7 @@ from addresses import ( encodeVarint, decodeVarint, decodeAddress, varintDecodeError) from bmconfigparser import BMConfigParser from debug import logger +from fallback import RIPEMD160Hash from helper_sql import sqlExecute from version import softwareVersion @@ -411,9 +412,7 @@ def decryptAndCheckPubkeyPayload(data, address): sha = hashlib.new('sha512') sha.update(publicSigningKey + publicEncryptionKey) - ripeHasher = hashlib.new('ripemd160') - ripeHasher.update(sha.digest()) - embeddedRipe = ripeHasher.digest() + embeddedRipe = RIPEMD160Hash(sha.digest()).digest() if embeddedRipe != ripe: # Although this pubkey object had the tag were were looking for diff --git a/src/tests/test_crypto.py b/src/tests/test_crypto.py new file mode 100644 index 00000000..7410e958 --- /dev/null +++ b/src/tests/test_crypto.py @@ -0,0 +1,60 @@ +""" +Test the alternatives for crypto primitives +""" + +import hashlib +import unittest +from abc import ABCMeta, abstractmethod +from binascii import hexlify, unhexlify + +try: + from Crypto.Hash import RIPEMD +except ImportError: + RIPEMD = None + + +# These keys are from addresses test script +sample_pubsigningkey = unhexlify( + '044a367f049ec16cb6b6118eb734a9962d10b8db59c890cd08f210c43ff08bdf09d' + '16f502ca26cd0713f38988a1237f1fc8fa07b15653c996dc4013af6d15505ce') +sample_pubencryptionkey = unhexlify( + '044597d59177fc1d89555d38915f581b5ff2286b39d022ca0283d2bdd5c36be5d3c' + 'e7b9b97792327851a562752e4b79475d1f51f5a71352482b241227f45ed36a9') + +sample_ripe = '003cd097eb7f35c87b5dc8b4538c22cb55312a9f' + +_sha = hashlib.new('sha512') +_sha.update(sample_pubsigningkey + sample_pubencryptionkey) + +pubkey_sha = _sha.digest() + + +class RIPEMD160TestCase(object): + """Base class for RIPEMD160 test case""" + __metaclass__ = ABCMeta + + @abstractmethod + def _hashdigest(self, data): + """RIPEMD160 digest implementation""" + pass + + def test_hash_string(self): + """Check RIPEMD160 hash function on string""" + self.assertEqual(hexlify(self._hashdigest(pubkey_sha)), sample_ripe) + + +class TestHashlib(RIPEMD160TestCase, unittest.TestCase): + """RIPEMD160 test case for hashlib""" + @staticmethod + def _hashdigest(data): + hasher = hashlib.new('ripemd160') + hasher.update(data) + return hasher.digest() + + +@unittest.skipUnless(RIPEMD, 'pycrypto package not found') +class TestCrypto(RIPEMD160TestCase, unittest.TestCase): + """RIPEMD160 test case for Crypto""" + @staticmethod + def _hashdigest(data): + return RIPEMD.RIPEMD160Hash(data).digest()