You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

85 lines
2.7 KiB

#!/usr/bin/python
import coincurve as secp256k1
import base64
import hmac
import hashlib
import pyaes
import os
import jmbitcoin as btc
ECIES_MAGIC_BYTES = b'BIE1'
class ECIESDecryptionError(Exception):
pass
# AES primitives. See BIP-SNICKER for specification.
def aes_encrypt(key, data, iv):
encrypter = pyaes.Encrypter(
pyaes.AESModeOfOperationCBC(key, iv=iv))
enc_data = encrypter.feed(data)
enc_data += encrypter.feed()
return enc_data
def aes_decrypt(key, data, iv):
decrypter = pyaes.Decrypter(
pyaes.AESModeOfOperationCBC(key, iv=iv))
try:
dec_data = decrypter.feed(data)
dec_data += decrypter.feed()
except ValueError:
# note decryption errors can come from PKCS7 padding errors
raise ECIESDecryptionError()
return dec_data
def ecies_encrypt(message, pubkey):
""" Take a message in bytes and a secp256k1 public key
in compressed byte serialization, and output the
ECIES encryption, using magic bytes as defined in this module,
sha512 for the key expansion, and AES-CBC for the encryption;
these choices are aligned with that used by Electrum.
"""
# create an ephemeral pubkey for this encryption:
while True:
r = os.urandom(32)
# use compressed serialization of the pubkey R:
try:
R = btc.privkey_to_pubkey(r + b"\x01")
break
except:
# accounts for improbable overflow:
continue
# note that this is *not* ECDH as in the secp256k1_ecdh module,
# since it uses sha512:
ecdh_key = btc.multiply(r, pubkey)
key = hashlib.sha512(ecdh_key).digest()
iv, key_e, key_m = key[0:16], key[16:32], key[32:]
ciphertext = aes_encrypt(key_e, message, iv=iv)
encrypted = ECIES_MAGIC_BYTES + R + ciphertext
mac = hmac.new(key_m, encrypted, hashlib.sha256).digest()
return base64.b64encode(encrypted + mac)
def ecies_decrypt(privkey, encrypted):
if len(privkey) == 33 and privkey[-1] == 1:
privkey = privkey[:32]
encrypted = base64.b64decode(encrypted)
if len(encrypted) < 85:
raise Exception('invalid ciphertext: length')
magic = encrypted[:4]
if magic != ECIES_MAGIC_BYTES:
raise ECIESDecryptionError()
ephemeral_pubkey = encrypted[4:37]
try:
testR = secp256k1.PublicKey(ephemeral_pubkey)
except:
raise ECIESDecryptionError()
ciphertext = encrypted[37:-32]
mac = encrypted[-32:]
ecdh_key = btc.multiply(privkey, ephemeral_pubkey)
key = hashlib.sha512(ecdh_key).digest()
iv, key_e, key_m = key[0:16], key[16:32], key[32:]
if mac != hmac.new(key_m, encrypted[:-32], hashlib.sha256).digest():
raise ECIESDecryptionError()
return aes_decrypt(key_e, ciphertext, iv=iv)