highlevelcrypto.py
1 """ 2 High level cryptographic functions based on `.pyelliptic` OpenSSL bindings. 3 4 .. note:: 5 Upstream pyelliptic was upgraded from SHA1 to SHA256 for signing. We must 6 `upgrade PyBitmessage gracefully. <https://github.com/Bitmessage/PyBitmessage/issues/953>`_ 7 `More discussion. <https://github.com/yann2192/pyelliptic/issues/32>`_ 8 """ 9 10 import hashlib 11 import os 12 from binascii import hexlify 13 14 try: 15 import pyelliptic 16 from fallback import RIPEMD160Hash 17 from pyelliptic import OpenSSL 18 from pyelliptic import arithmetic as a 19 except ImportError: 20 from pybitmessage import pyelliptic 21 from pybitmessage.fallback import RIPEMD160Hash 22 from pybitmessage.pyelliptic import OpenSSL 23 from pybitmessage.pyelliptic import arithmetic as a 24 25 26 __all__ = [ 27 'decodeWalletImportFormat', 'deterministic_keys', 28 'double_sha512', 'calculateInventoryHash', 'encodeWalletImportFormat', 29 'encrypt', 'makeCryptor', 'pointMult', 'privToPub', 'randomBytes', 30 'random_keys', 'sign', 'to_ripe', 'verify'] 31 32 33 # WIF (uses arithmetic ): 34 def decodeWalletImportFormat(WIFstring): 35 """ 36 Convert private key from base58 that's used in the config file to 37 8-bit binary string. 38 """ 39 fullString = a.changebase(WIFstring, 58, 256) 40 privkey = fullString[:-4] 41 if fullString[-4:] != \ 42 hashlib.sha256(hashlib.sha256(privkey).digest()).digest()[:4]: 43 raise ValueError('Checksum failed') 44 elif privkey[0:1] == b'\x80': # checksum passed 45 return privkey[1:] 46 47 raise ValueError('No hex 80 prefix') 48 49 50 # An excellent way for us to store our keys 51 # is in Wallet Import Format. Let us convert now. 52 # https://en.bitcoin.it/wiki/Wallet_import_format 53 def encodeWalletImportFormat(privKey): 54 """ 55 Convert private key from binary 8-bit string into base58check WIF string. 56 """ 57 privKey = b'\x80' + privKey 58 checksum = hashlib.sha256(hashlib.sha256(privKey).digest()).digest()[0:4] 59 return a.changebase(privKey + checksum, 256, 58) 60 61 62 # Random 63 64 def randomBytes(n): 65 """Get n random bytes""" 66 try: 67 return os.urandom(n) 68 except NotImplementedError: 69 return OpenSSL.rand(n) 70 71 72 # Hashes 73 74 def _bm160(data): 75 """RIPEME160(SHA512(data)) -> bytes""" 76 return RIPEMD160Hash(hashlib.sha512(data).digest()).digest() 77 78 79 def to_ripe(signing_key, encryption_key): 80 """Convert two public keys to a ripe hash""" 81 return _bm160(signing_key + encryption_key) 82 83 84 def double_sha512(data): 85 """Binary double SHA512 digest""" 86 return hashlib.sha512(hashlib.sha512(data).digest()).digest() 87 88 89 def calculateInventoryHash(data): 90 """Calculate inventory hash from object data""" 91 return double_sha512(data)[:32] 92 93 94 # Keys 95 96 def random_keys(): 97 """Return a pair of keys, private and public""" 98 priv = randomBytes(32) 99 pub = pointMult(priv) 100 return priv, pub 101 102 103 def deterministic_keys(passphrase, nonce): 104 """Generate keys from *passphrase* and *nonce* (encoded as varint)""" 105 priv = hashlib.sha512(passphrase + nonce).digest()[:32] 106 pub = pointMult(priv) 107 return priv, pub 108 109 110 def hexToPubkey(pubkey): 111 """Convert a pubkey from hex to binary""" 112 pubkey_raw = a.changebase(pubkey[2:], 16, 256, minlen=64) 113 pubkey_bin = b'\x02\xca\x00 ' + pubkey_raw[:32] + b'\x00 ' + pubkey_raw[32:] 114 return pubkey_bin 115 116 117 def privToPub(privkey): 118 """Converts hex private key into hex public key""" 119 private_key = a.changebase(privkey, 16, 256, minlen=32) 120 public_key = pointMult(private_key) 121 return hexlify(public_key) 122 123 124 def pointMult(secret): 125 """ 126 Does an EC point multiplication; turns a private key into a public key. 127 128 Evidently, this type of error can occur very rarely: 129 130 >>> File "highlevelcrypto.py", line 54, in pointMult 131 >>> group = OpenSSL.EC_KEY_get0_group(k) 132 >>> WindowsError: exception: access violation reading 0x0000000000000008 133 """ 134 while True: 135 try: 136 k = OpenSSL.EC_KEY_new_by_curve_name( 137 OpenSSL.get_curve('secp256k1')) 138 priv_key = OpenSSL.BN_bin2bn(secret, 32, None) 139 group = OpenSSL.EC_KEY_get0_group(k) 140 pub_key = OpenSSL.EC_POINT_new(group) 141 142 OpenSSL.EC_POINT_mul(group, pub_key, priv_key, None, None, None) 143 OpenSSL.EC_KEY_set_private_key(k, priv_key) 144 OpenSSL.EC_KEY_set_public_key(k, pub_key) 145 146 size = OpenSSL.i2o_ECPublicKey(k, None) 147 mb = OpenSSL.create_string_buffer(size) 148 OpenSSL.i2o_ECPublicKey(k, OpenSSL.byref(OpenSSL.pointer(mb))) 149 150 return mb.raw 151 152 except Exception: 153 import traceback 154 import time 155 traceback.print_exc() 156 time.sleep(0.2) 157 finally: 158 OpenSSL.EC_POINT_free(pub_key) 159 OpenSSL.BN_free(priv_key) 160 OpenSSL.EC_KEY_free(k) 161 162 163 # Encryption 164 165 def makeCryptor(privkey, curve='secp256k1'): 166 """Return a private `.pyelliptic.ECC` instance""" 167 private_key = a.changebase(privkey, 16, 256, minlen=32) 168 public_key = pointMult(private_key) 169 cryptor = pyelliptic.ECC( 170 pubkey_x=public_key[1:-32], pubkey_y=public_key[-32:], 171 raw_privkey=private_key, curve=curve) 172 return cryptor 173 174 175 def makePubCryptor(pubkey): 176 """Return a public `.pyelliptic.ECC` instance""" 177 pubkey_bin = hexToPubkey(pubkey) 178 return pyelliptic.ECC(curve='secp256k1', pubkey=pubkey_bin) 179 180 181 def encrypt(msg, hexPubkey): 182 """Encrypts message with hex public key""" 183 return pyelliptic.ECC(curve='secp256k1').encrypt( 184 msg, hexToPubkey(hexPubkey)) 185 186 187 def decrypt(msg, hexPrivkey): 188 """Decrypts message with hex private key""" 189 return makeCryptor(hexPrivkey).decrypt(msg) 190 191 192 def decryptFast(msg, cryptor): 193 """Decrypts message with an existing `.pyelliptic.ECC` object""" 194 return cryptor.decrypt(msg) 195 196 197 # Signatures 198 199 def _choose_digest_alg(name): 200 """ 201 Choose openssl digest constant by name raises ValueError if not appropriate 202 """ 203 if name not in ("sha1", "sha256"): 204 raise ValueError("Unknown digest algorithm %s" % name) 205 return ( 206 # SHA1, this will eventually be deprecated 207 OpenSSL.digest_ecdsa_sha1 if name == "sha1" else OpenSSL.EVP_sha256) 208 209 210 def sign(msg, hexPrivkey, digestAlg="sha256"): 211 """ 212 Signs with hex private key using SHA1 or SHA256 depending on 213 *digestAlg* keyword. 214 """ 215 return makeCryptor(hexPrivkey).sign( 216 msg, digest_alg=_choose_digest_alg(digestAlg)) 217 218 219 def verify(msg, sig, hexPubkey, digestAlg=None): 220 """Verifies with hex public key using SHA1 or SHA256""" 221 # As mentioned above, we must upgrade gracefully to use SHA256. So 222 # let us check the signature using both SHA1 and SHA256 and if one 223 # of them passes then we will be satisfied. Eventually this can 224 # be simplified and we'll only check with SHA256. 225 if digestAlg is None: 226 # old SHA1 algorithm. 227 sigVerifyPassed = verify(msg, sig, hexPubkey, "sha1") 228 if sigVerifyPassed: 229 # The signature check passed using SHA1 230 return True 231 # The signature check using SHA1 failed. Let us try it with SHA256. 232 return verify(msg, sig, hexPubkey, "sha256") 233 234 try: 235 return makePubCryptor(hexPubkey).verify( 236 sig, msg, digest_alg=_choose_digest_alg(digestAlg)) 237 except: 238 return False