Switch to sslcrypto for cryptography tasks (#2338)

* Use sslcrypto instead of pyelliptic and pybitcointools

* Fix CryptMessage

* Support Python 3.4

* Fix user creation

* Get rid of pyelliptic and pybitcointools

* Fix typo

* Delete test file

* Add sslcrypto to tree

* Update sslcrypto

* Add pyaes to src/lib

* Fix typo in tests

* Update sslcrypto version

* Use privatekey_bin instead of privatekey for bytes objects

* Fix sslcrypto

* Fix Benchmark plugin

* Don't calculate the same thing twice

* Only import sslcrypto once

* Handle fallback sslcrypto implementation during tests

* Fix sslcrypto fallback implementation selection
This commit is contained in:
Ivanq 2019-12-15 14:46:06 +03:00 committed by ZeroNet
parent 28fcf3c1ea
commit fbc7b6fc4f
55 changed files with 3748 additions and 7287 deletions

27
src/lib/sslcrypto/LICENSE Normal file
View file

@ -0,0 +1,27 @@
MIT License
Copyright (c) 2019 Ivan Machugovskiy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Additionally, the following licenses must be preserved:
- ripemd implementation is licensed under BSD-3 by Markus Friedl, see `_ripemd.py`;
- jacobian curve implementation is dual-licensed under MIT or public domain license, see `_jacobian.py`.

View file

@ -0,0 +1,6 @@
__all__ = ["aes", "ecc", "rsa"]
try:
from .openssl import aes, ecc, rsa
except OSError:
from .fallback import aes, ecc, rsa

53
src/lib/sslcrypto/_aes.py Normal file
View file

@ -0,0 +1,53 @@
# pylint: disable=import-outside-toplevel
class AES:
def __init__(self, backend, fallback=None):
self._backend = backend
self._fallback = fallback
def get_algo_key_length(self, algo):
if algo.count("-") != 2:
raise ValueError("Invalid algorithm name")
try:
return int(algo.split("-")[1]) // 8
except ValueError:
raise ValueError("Invalid algorithm name") from None
def new_key(self, algo="aes-256-cbc"):
if not self._backend.is_algo_supported(algo):
if self._fallback is None:
raise ValueError("This algorithm is not supported")
return self._fallback.new_key(algo)
return self._backend.random(self.get_algo_key_length(algo))
def encrypt(self, data, key, algo="aes-256-cbc"):
if not self._backend.is_algo_supported(algo):
if self._fallback is None:
raise ValueError("This algorithm is not supported")
return self._fallback.encrypt(data, key, algo)
key_length = self.get_algo_key_length(algo)
if len(key) != key_length:
raise ValueError("Expected key to be {} bytes, got {} bytes".format(key_length, len(key)))
return self._backend.encrypt(data, key, algo)
def decrypt(self, ciphertext, iv, key, algo="aes-256-cbc"):
if not self._backend.is_algo_supported(algo):
if self._fallback is None:
raise ValueError("This algorithm is not supported")
return self._fallback.decrypt(ciphertext, iv, key, algo)
key_length = self.get_algo_key_length(algo)
if len(key) != key_length:
raise ValueError("Expected key to be {} bytes, got {} bytes".format(key_length, len(key)))
return self._backend.decrypt(ciphertext, iv, key, algo)
def get_backend(self):
return self._backend.get_backend()

334
src/lib/sslcrypto/_ecc.py Normal file
View file

@ -0,0 +1,334 @@
import hashlib
import struct
import hmac
import base58
try:
hashlib.new("ripemd160")
except ValueError:
# No native implementation
from . import _ripemd
def ripemd160(*args):
return _ripemd.new(*args)
else:
# Use OpenSSL
def ripemd160(*args):
return hashlib.new("ripemd160", *args)
class ECC:
CURVES = {
"secp112r1": 704,
"secp112r2": 705,
"secp128r1": 706,
"secp128r2": 707,
"secp160k1": 708,
"secp160r1": 709,
"secp160r2": 710,
"secp192k1": 711,
"prime192v1": 409,
"secp224k1": 712,
"secp224r1": 713,
"secp256k1": 714,
"prime256v1": 415,
"secp384r1": 715,
"secp521r1": 716
}
def __init__(self, backend, aes):
self._backend = backend
self._aes = aes
def get_curve(self, name):
if name not in self.CURVES:
raise ValueError("Unknown curve {}".format(name))
nid = self.CURVES[name]
return EllipticCurve(self._backend(nid), self._aes, nid)
def get_backend(self):
return self._backend.get_backend()
class EllipticCurve:
def __init__(self, backend, aes, nid):
self._backend = backend
self._aes = aes
self.nid = nid
def _encode_public_key(self, x, y, is_compressed=True, raw=True):
if raw:
if is_compressed:
return bytes([0x02 + (y[-1] % 2)]) + x
else:
return bytes([0x04]) + x + y
else:
return struct.pack("!HH", self.nid, len(x)) + x + struct.pack("!H", len(y)) + y
def _decode_public_key(self, public_key, partial=False):
if not public_key:
raise ValueError("No public key")
if public_key[0] == 0x04:
# Uncompressed
expected_length = 1 + 2 * self._backend.public_key_length
if partial:
if len(public_key) < expected_length:
raise ValueError("Invalid uncompressed public key length")
else:
if len(public_key) != expected_length:
raise ValueError("Invalid uncompressed public key length")
x = public_key[1:1 + self._backend.public_key_length]
y = public_key[1 + self._backend.public_key_length:expected_length]
if partial:
return (x, y), expected_length
else:
return x, y
elif public_key[0] in (0x02, 0x03):
# Compressed
expected_length = 1 + self._backend.public_key_length
if partial:
if len(public_key) < expected_length:
raise ValueError("Invalid compressed public key length")
else:
if len(public_key) != expected_length:
raise ValueError("Invalid compressed public key length")
x, y = self._backend.decompress_point(public_key[:expected_length])
# Sanity check
if x != public_key[1:expected_length]:
raise ValueError("Incorrect compressed public key")
if partial:
return (x, y), expected_length
else:
return x, y
else:
raise ValueError("Invalid public key prefix")
def _decode_public_key_openssl(self, public_key, partial=False):
if not public_key:
raise ValueError("No public key")
i = 0
nid, = struct.unpack("!H", public_key[i:i + 2])
i += 2
if nid != self.nid:
raise ValueError("Wrong curve")
xlen, = struct.unpack("!H", public_key[i:i + 2])
i += 2
if len(public_key) - i < xlen:
raise ValueError("Too short public key")
x = public_key[i:i + xlen]
i += xlen
ylen, = struct.unpack("!H", public_key[i:i + 2])
i += 2
if len(public_key) - i < ylen:
raise ValueError("Too short public key")
y = public_key[i:i + ylen]
i += ylen
if partial:
return (x, y), i
else:
if i < len(public_key):
raise ValueError("Too long public key")
return x, y
def new_private_key(self):
return self._backend.new_private_key()
def private_to_public(self, private_key, is_compressed=True):
x, y = self._backend.private_to_public(private_key)
return self._encode_public_key(x, y, is_compressed=is_compressed)
def private_to_wif(self, private_key):
return base58.b58encode_check(b"\x80" + private_key)
def wif_to_private(self, wif):
dec = base58.b58decode_check(wif)
if dec[0] != 0x80:
raise ValueError("Invalid network (expected mainnet)")
return dec[1:]
def public_to_address(self, public_key):
h = hashlib.sha256(public_key).digest()
hash160 = ripemd160(h).digest()
return base58.b58encode_check(b"\x00" + hash160)
def private_to_address(self, private_key, is_compressed=True):
# Kinda useless but left for quick migration from pybitcointools
return self.public_to_address(self.private_to_public(private_key, is_compressed=is_compressed))
def derive(self, private_key, public_key):
if not isinstance(public_key, tuple):
public_key = self._decode_public_key(public_key)
return self._backend.ecdh(private_key, public_key)
def _digest(self, data, hash):
if hash is None:
return data
elif callable(hash):
return hash(data)
elif hash == "sha1":
return hashlib.sha1(data).digest()
elif hash == "sha256":
return hashlib.sha256(data).digest()
elif hash == "sha512":
return hashlib.sha512(data).digest()
else:
raise ValueError("Unknown hash/derivation method")
# High-level functions
def encrypt(self, data, public_key, algo="aes-256-cbc", derivation="sha256", mac="hmac-sha256", return_aes_key=False):
# Generate ephemeral private key
private_key = self.new_private_key()
# Derive key
ecdh = self.derive(private_key, public_key)
key = self._digest(ecdh, derivation)
k_enc_len = self._aes.get_algo_key_length(algo)
if len(key) < k_enc_len:
raise ValueError("Too short digest")
k_enc, k_mac = key[:k_enc_len], key[k_enc_len:]
# Encrypt
ciphertext, iv = self._aes.encrypt(data, k_enc, algo=algo)
ephem_public_key = self.private_to_public(private_key)
ephem_public_key = self._decode_public_key(ephem_public_key)
ephem_public_key = self._encode_public_key(*ephem_public_key, raw=False)
ciphertext = iv + ephem_public_key + ciphertext
# Add MAC tag
if callable(mac):
tag = mac(k_mac, ciphertext)
elif mac == "hmac-sha256":
h = hmac.new(k_mac, digestmod="sha256")
h.update(ciphertext)
tag = h.digest()
elif mac == "hmac-sha512":
h = hmac.new(k_mac, digestmod="sha512")
h.update(ciphertext)
tag = h.digest()
elif mac is None:
tag = b""
else:
raise ValueError("Unsupported MAC")
if return_aes_key:
return ciphertext + tag, k_enc
else:
return ciphertext + tag
def decrypt(self, ciphertext, private_key, algo="aes-256-cbc", derivation="sha256", mac="hmac-sha256"):
# Get MAC tag
if callable(mac):
tag_length = mac.digest_size
elif mac == "hmac-sha256":
tag_length = hmac.new(b"", digestmod="sha256").digest_size
elif mac == "hmac-sha512":
tag_length = hmac.new(b"", digestmod="sha512").digest_size
elif mac is None:
tag_length = 0
else:
raise ValueError("Unsupported MAC")
if len(ciphertext) < tag_length:
raise ValueError("Ciphertext is too small to contain MAC tag")
if tag_length == 0:
tag = b""
else:
ciphertext, tag = ciphertext[:-tag_length], ciphertext[-tag_length:]
orig_ciphertext = ciphertext
if len(ciphertext) < 16:
raise ValueError("Ciphertext is too small to contain IV")
iv, ciphertext = ciphertext[:16], ciphertext[16:]
public_key, pos = self._decode_public_key_openssl(ciphertext, partial=True)
ciphertext = ciphertext[pos:]
# Derive key
ecdh = self.derive(private_key, public_key)
key = self._digest(ecdh, derivation)
k_enc_len = self._aes.get_algo_key_length(algo)
if len(key) < k_enc_len:
raise ValueError("Too short digest")
k_enc, k_mac = key[:k_enc_len], key[k_enc_len:]
# Verify MAC tag
if callable(mac):
expected_tag = mac(k_mac, orig_ciphertext)
elif mac == "hmac-sha256":
h = hmac.new(k_mac, digestmod="sha256")
h.update(orig_ciphertext)
expected_tag = h.digest()
elif mac == "hmac-sha512":
h = hmac.new(k_mac, digestmod="sha512")
h.update(orig_ciphertext)
expected_tag = h.digest()
elif mac is None:
expected_tag = b""
if not hmac.compare_digest(tag, expected_tag):
raise ValueError("Invalid MAC tag")
return self._aes.decrypt(ciphertext, iv, k_enc, algo=algo)
def sign(self, data, private_key, hash="sha256", recoverable=False, is_compressed=True, entropy=None):
data = self._digest(data, hash)
if not entropy:
v = b"\x01" * len(data)
k = b"\x00" * len(data)
k = hmac.new(k, v + b"\x00" + private_key + data, "sha256").digest()
v = hmac.new(k, v, "sha256").digest()
k = hmac.new(k, v + b"\x01" + private_key + data, "sha256").digest()
v = hmac.new(k, v, "sha256").digest()
entropy = hmac.new(k, v, "sha256").digest()
return self._backend.sign(data, private_key, recoverable, is_compressed, entropy=entropy)
def recover(self, signature, data, hash="sha256"):
# Sanity check: is this signature recoverable?
if len(signature) != 1 + 2 * self._backend.public_key_length:
raise ValueError("Cannot recover an unrecoverable signature")
x, y = self._backend.recover(signature, self._digest(data, hash))
is_compressed = signature[0] >= 31
return self._encode_public_key(x, y, is_compressed=is_compressed)
def verify(self, signature, data, public_key, hash="sha256"):
if len(signature) == 1 + 2 * self._backend.public_key_length:
# Recoverable signature
signature = signature[1:]
if len(signature) != 2 * self._backend.public_key_length:
raise ValueError("Invalid signature format")
if not isinstance(public_key, tuple):
public_key = self._decode_public_key(public_key)
return self._backend.verify(signature, self._digest(data, hash), public_key)
def derive_child(self, seed, child):
# Based on BIP32
if not 0 <= child < 2 ** 31:
raise ValueError("Invalid child index")
return self._backend.derive_child(seed, child)

View file

@ -0,0 +1,375 @@
# Copyright (c) 2001 Markus Friedl. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# pylint: skip-file
import sys
digest_size = 20
digestsize = 20
class RIPEMD160:
"""
Return a new RIPEMD160 object. An optional string argument
may be provided; if present, this string will be automatically
hashed.
"""
def __init__(self, arg=None):
self.ctx = RMDContext()
if arg:
self.update(arg)
self.dig = None
def update(self, arg):
RMD160Update(self.ctx, arg, len(arg))
self.dig = None
def digest(self):
if self.dig:
return self.dig
ctx = self.ctx.copy()
self.dig = RMD160Final(self.ctx)
self.ctx = ctx
return self.dig
def hexdigest(self):
dig = self.digest()
hex_digest = ""
for d in dig:
hex_digest += "%02x" % d
return hex_digest
def copy(self):
import copy
return copy.deepcopy(self)
def new(arg=None):
"""
Return a new RIPEMD160 object. An optional string argument
may be provided; if present, this string will be automatically
hashed.
"""
return RIPEMD160(arg)
#
# Private.
#
class RMDContext:
def __init__(self):
self.state = [0x67452301, 0xEFCDAB89, 0x98BADCFE,
0x10325476, 0xC3D2E1F0] # uint32
self.count = 0 # uint64
self.buffer = [0] * 64 # uchar
def copy(self):
ctx = RMDContext()
ctx.state = self.state[:]
ctx.count = self.count
ctx.buffer = self.buffer[:]
return ctx
K0 = 0x00000000
K1 = 0x5A827999
K2 = 0x6ED9EBA1
K3 = 0x8F1BBCDC
K4 = 0xA953FD4E
KK0 = 0x50A28BE6
KK1 = 0x5C4DD124
KK2 = 0x6D703EF3
KK3 = 0x7A6D76E9
KK4 = 0x00000000
def ROL(n, x):
return ((x << n) & 0xffffffff) | (x >> (32 - n))
def F0(x, y, z):
return x ^ y ^ z
def F1(x, y, z):
return (x & y) | (((~x) % 0x100000000) & z)
def F2(x, y, z):
return (x | ((~y) % 0x100000000)) ^ z
def F3(x, y, z):
return (x & z) | (((~z) % 0x100000000) & y)
def F4(x, y, z):
return x ^ (y | ((~z) % 0x100000000))
def R(a, b, c, d, e, Fj, Kj, sj, rj, X):
a = ROL(sj, (a + Fj(b, c, d) + X[rj] + Kj) % 0x100000000) + e
c = ROL(10, c)
return a % 0x100000000, c
PADDING = [0x80] + [0] * 63
import sys
import struct
def RMD160Transform(state, block): # uint32 state[5], uchar block[64]
x = [0] * 16
if sys.byteorder == "little":
x = struct.unpack("<16L", bytes(block[0:64]))
else:
raise ValueError("Big-endian platforms are not supported")
a = state[0]
b = state[1]
c = state[2]
d = state[3]
e = state[4]
# Round 1
a, c = R(a, b, c, d, e, F0, K0, 11, 0, x)
e, b = R(e, a, b, c, d, F0, K0, 14, 1, x)
d, a = R(d, e, a, b, c, F0, K0, 15, 2, x)
c, e = R(c, d, e, a, b, F0, K0, 12, 3, x)
b, d = R(b, c, d, e, a, F0, K0, 5, 4, x)
a, c = R(a, b, c, d, e, F0, K0, 8, 5, x)
e, b = R(e, a, b, c, d, F0, K0, 7, 6, x)
d, a = R(d, e, a, b, c, F0, K0, 9, 7, x)
c, e = R(c, d, e, a, b, F0, K0, 11, 8, x)
b, d = R(b, c, d, e, a, F0, K0, 13, 9, x)
a, c = R(a, b, c, d, e, F0, K0, 14, 10, x)
e, b = R(e, a, b, c, d, F0, K0, 15, 11, x)
d, a = R(d, e, a, b, c, F0, K0, 6, 12, x)
c, e = R(c, d, e, a, b, F0, K0, 7, 13, x)
b, d = R(b, c, d, e, a, F0, K0, 9, 14, x)
a, c = R(a, b, c, d, e, F0, K0, 8, 15, x) # #15
# Round 2
e, b = R(e, a, b, c, d, F1, K1, 7, 7, x)
d, a = R(d, e, a, b, c, F1, K1, 6, 4, x)
c, e = R(c, d, e, a, b, F1, K1, 8, 13, x)
b, d = R(b, c, d, e, a, F1, K1, 13, 1, x)
a, c = R(a, b, c, d, e, F1, K1, 11, 10, x)
e, b = R(e, a, b, c, d, F1, K1, 9, 6, x)
d, a = R(d, e, a, b, c, F1, K1, 7, 15, x)
c, e = R(c, d, e, a, b, F1, K1, 15, 3, x)
b, d = R(b, c, d, e, a, F1, K1, 7, 12, x)
a, c = R(a, b, c, d, e, F1, K1, 12, 0, x)
e, b = R(e, a, b, c, d, F1, K1, 15, 9, x)
d, a = R(d, e, a, b, c, F1, K1, 9, 5, x)
c, e = R(c, d, e, a, b, F1, K1, 11, 2, x)
b, d = R(b, c, d, e, a, F1, K1, 7, 14, x)
a, c = R(a, b, c, d, e, F1, K1, 13, 11, x)
e, b = R(e, a, b, c, d, F1, K1, 12, 8, x) # #31
# Round 3
d, a = R(d, e, a, b, c, F2, K2, 11, 3, x)
c, e = R(c, d, e, a, b, F2, K2, 13, 10, x)
b, d = R(b, c, d, e, a, F2, K2, 6, 14, x)
a, c = R(a, b, c, d, e, F2, K2, 7, 4, x)
e, b = R(e, a, b, c, d, F2, K2, 14, 9, x)
d, a = R(d, e, a, b, c, F2, K2, 9, 15, x)
c, e = R(c, d, e, a, b, F2, K2, 13, 8, x)
b, d = R(b, c, d, e, a, F2, K2, 15, 1, x)
a, c = R(a, b, c, d, e, F2, K2, 14, 2, x)
e, b = R(e, a, b, c, d, F2, K2, 8, 7, x)
d, a = R(d, e, a, b, c, F2, K2, 13, 0, x)
c, e = R(c, d, e, a, b, F2, K2, 6, 6, x)
b, d = R(b, c, d, e, a, F2, K2, 5, 13, x)
a, c = R(a, b, c, d, e, F2, K2, 12, 11, x)
e, b = R(e, a, b, c, d, F2, K2, 7, 5, x)
d, a = R(d, e, a, b, c, F2, K2, 5, 12, x) # #47
# Round 4
c, e = R(c, d, e, a, b, F3, K3, 11, 1, x)
b, d = R(b, c, d, e, a, F3, K3, 12, 9, x)
a, c = R(a, b, c, d, e, F3, K3, 14, 11, x)
e, b = R(e, a, b, c, d, F3, K3, 15, 10, x)
d, a = R(d, e, a, b, c, F3, K3, 14, 0, x)
c, e = R(c, d, e, a, b, F3, K3, 15, 8, x)
b, d = R(b, c, d, e, a, F3, K3, 9, 12, x)
a, c = R(a, b, c, d, e, F3, K3, 8, 4, x)
e, b = R(e, a, b, c, d, F3, K3, 9, 13, x)
d, a = R(d, e, a, b, c, F3, K3, 14, 3, x)
c, e = R(c, d, e, a, b, F3, K3, 5, 7, x)
b, d = R(b, c, d, e, a, F3, K3, 6, 15, x)
a, c = R(a, b, c, d, e, F3, K3, 8, 14, x)
e, b = R(e, a, b, c, d, F3, K3, 6, 5, x)
d, a = R(d, e, a, b, c, F3, K3, 5, 6, x)
c, e = R(c, d, e, a, b, F3, K3, 12, 2, x) # #63
# Round 5
b, d = R(b, c, d, e, a, F4, K4, 9, 4, x)
a, c = R(a, b, c, d, e, F4, K4, 15, 0, x)
e, b = R(e, a, b, c, d, F4, K4, 5, 5, x)
d, a = R(d, e, a, b, c, F4, K4, 11, 9, x)
c, e = R(c, d, e, a, b, F4, K4, 6, 7, x)
b, d = R(b, c, d, e, a, F4, K4, 8, 12, x)
a, c = R(a, b, c, d, e, F4, K4, 13, 2, x)
e, b = R(e, a, b, c, d, F4, K4, 12, 10, x)
d, a = R(d, e, a, b, c, F4, K4, 5, 14, x)
c, e = R(c, d, e, a, b, F4, K4, 12, 1, x)
b, d = R(b, c, d, e, a, F4, K4, 13, 3, x)
a, c = R(a, b, c, d, e, F4, K4, 14, 8, x)
e, b = R(e, a, b, c, d, F4, K4, 11, 11, x)
d, a = R(d, e, a, b, c, F4, K4, 8, 6, x)
c, e = R(c, d, e, a, b, F4, K4, 5, 15, x)
b, d = R(b, c, d, e, a, F4, K4, 6, 13, x) # #79
aa = a
bb = b
cc = c
dd = d
ee = e
a = state[0]
b = state[1]
c = state[2]
d = state[3]
e = state[4]
# Parallel round 1
a, c = R(a, b, c, d, e, F4, KK0, 8, 5, x)
e, b = R(e, a, b, c, d, F4, KK0, 9, 14, x)
d, a = R(d, e, a, b, c, F4, KK0, 9, 7, x)
c, e = R(c, d, e, a, b, F4, KK0, 11, 0, x)
b, d = R(b, c, d, e, a, F4, KK0, 13, 9, x)
a, c = R(a, b, c, d, e, F4, KK0, 15, 2, x)
e, b = R(e, a, b, c, d, F4, KK0, 15, 11, x)
d, a = R(d, e, a, b, c, F4, KK0, 5, 4, x)
c, e = R(c, d, e, a, b, F4, KK0, 7, 13, x)
b, d = R(b, c, d, e, a, F4, KK0, 7, 6, x)
a, c = R(a, b, c, d, e, F4, KK0, 8, 15, x)
e, b = R(e, a, b, c, d, F4, KK0, 11, 8, x)
d, a = R(d, e, a, b, c, F4, KK0, 14, 1, x)
c, e = R(c, d, e, a, b, F4, KK0, 14, 10, x)
b, d = R(b, c, d, e, a, F4, KK0, 12, 3, x)
a, c = R(a, b, c, d, e, F4, KK0, 6, 12, x) # #15
# Parallel round 2
e, b = R(e, a, b, c, d, F3, KK1, 9, 6, x)
d, a = R(d, e, a, b, c, F3, KK1, 13, 11, x)
c, e = R(c, d, e, a, b, F3, KK1, 15, 3, x)
b, d = R(b, c, d, e, a, F3, KK1, 7, 7, x)
a, c = R(a, b, c, d, e, F3, KK1, 12, 0, x)
e, b = R(e, a, b, c, d, F3, KK1, 8, 13, x)
d, a = R(d, e, a, b, c, F3, KK1, 9, 5, x)
c, e = R(c, d, e, a, b, F3, KK1, 11, 10, x)
b, d = R(b, c, d, e, a, F3, KK1, 7, 14, x)
a, c = R(a, b, c, d, e, F3, KK1, 7, 15, x)
e, b = R(e, a, b, c, d, F3, KK1, 12, 8, x)
d, a = R(d, e, a, b, c, F3, KK1, 7, 12, x)
c, e = R(c, d, e, a, b, F3, KK1, 6, 4, x)
b, d = R(b, c, d, e, a, F3, KK1, 15, 9, x)
a, c = R(a, b, c, d, e, F3, KK1, 13, 1, x)
e, b = R(e, a, b, c, d, F3, KK1, 11, 2, x) # #31
# Parallel round 3
d, a = R(d, e, a, b, c, F2, KK2, 9, 15, x)
c, e = R(c, d, e, a, b, F2, KK2, 7, 5, x)
b, d = R(b, c, d, e, a, F2, KK2, 15, 1, x)
a, c = R(a, b, c, d, e, F2, KK2, 11, 3, x)
e, b = R(e, a, b, c, d, F2, KK2, 8, 7, x)
d, a = R(d, e, a, b, c, F2, KK2, 6, 14, x)
c, e = R(c, d, e, a, b, F2, KK2, 6, 6, x)
b, d = R(b, c, d, e, a, F2, KK2, 14, 9, x)
a, c = R(a, b, c, d, e, F2, KK2, 12, 11, x)
e, b = R(e, a, b, c, d, F2, KK2, 13, 8, x)
d, a = R(d, e, a, b, c, F2, KK2, 5, 12, x)
c, e = R(c, d, e, a, b, F2, KK2, 14, 2, x)
b, d = R(b, c, d, e, a, F2, KK2, 13, 10, x)
a, c = R(a, b, c, d, e, F2, KK2, 13, 0, x)
e, b = R(e, a, b, c, d, F2, KK2, 7, 4, x)
d, a = R(d, e, a, b, c, F2, KK2, 5, 13, x) # #47
# Parallel round 4
c, e = R(c, d, e, a, b, F1, KK3, 15, 8, x)
b, d = R(b, c, d, e, a, F1, KK3, 5, 6, x)
a, c = R(a, b, c, d, e, F1, KK3, 8, 4, x)
e, b = R(e, a, b, c, d, F1, KK3, 11, 1, x)
d, a = R(d, e, a, b, c, F1, KK3, 14, 3, x)
c, e = R(c, d, e, a, b, F1, KK3, 14, 11, x)
b, d = R(b, c, d, e, a, F1, KK3, 6, 15, x)
a, c = R(a, b, c, d, e, F1, KK3, 14, 0, x)
e, b = R(e, a, b, c, d, F1, KK3, 6, 5, x)
d, a = R(d, e, a, b, c, F1, KK3, 9, 12, x)
c, e = R(c, d, e, a, b, F1, KK3, 12, 2, x)
b, d = R(b, c, d, e, a, F1, KK3, 9, 13, x)
a, c = R(a, b, c, d, e, F1, KK3, 12, 9, x)
e, b = R(e, a, b, c, d, F1, KK3, 5, 7, x)
d, a = R(d, e, a, b, c, F1, KK3, 15, 10, x)
c, e = R(c, d, e, a, b, F1, KK3, 8, 14, x) # #63
# Parallel round 5
b, d = R(b, c, d, e, a, F0, KK4, 8, 12, x)
a, c = R(a, b, c, d, e, F0, KK4, 5, 15, x)
e, b = R(e, a, b, c, d, F0, KK4, 12, 10, x)
d, a = R(d, e, a, b, c, F0, KK4, 9, 4, x)
c, e = R(c, d, e, a, b, F0, KK4, 12, 1, x)
b, d = R(b, c, d, e, a, F0, KK4, 5, 5, x)
a, c = R(a, b, c, d, e, F0, KK4, 14, 8, x)
e, b = R(e, a, b, c, d, F0, KK4, 6, 7, x)
d, a = R(d, e, a, b, c, F0, KK4, 8, 6, x)
c, e = R(c, d, e, a, b, F0, KK4, 13, 2, x)
b, d = R(b, c, d, e, a, F0, KK4, 6, 13, x)
a, c = R(a, b, c, d, e, F0, KK4, 5, 14, x)
e, b = R(e, a, b, c, d, F0, KK4, 15, 0, x)
d, a = R(d, e, a, b, c, F0, KK4, 13, 3, x)
c, e = R(c, d, e, a, b, F0, KK4, 11, 9, x)
b, d = R(b, c, d, e, a, F0, KK4, 11, 11, x) # #79
t = (state[1] + cc + d) % 0x100000000
state[1] = (state[2] + dd + e) % 0x100000000
state[2] = (state[3] + ee + a) % 0x100000000
state[3] = (state[4] + aa + b) % 0x100000000
state[4] = (state[0] + bb + c) % 0x100000000
state[0] = t % 0x100000000
def RMD160Update(ctx, inp, inplen):
if type(inp) == str:
inp = [ord(i)&0xff for i in inp]
have = int((ctx.count // 8) % 64)
inplen = int(inplen)
need = 64 - have
ctx.count += 8 * inplen
off = 0
if inplen >= need:
if have:
for i in range(need):
ctx.buffer[have + i] = inp[i]
RMD160Transform(ctx.state, ctx.buffer)
off = need
have = 0
while off + 64 <= inplen:
RMD160Transform(ctx.state, inp[off:]) #<---
off += 64
if off < inplen:
# memcpy(ctx->buffer + have, input+off, len-off)
for i in range(inplen - off):
ctx.buffer[have + i] = inp[off + i]
def RMD160Final(ctx):
size = struct.pack("<Q", ctx.count)
padlen = 64 - ((ctx.count // 8) % 64)
if padlen < 1 + 8:
padlen += 64
RMD160Update(ctx, PADDING, padlen - 8)
RMD160Update(ctx, size, 8)
return struct.pack("<5L", *ctx.state)
assert "37f332f68db77bd9d7edd4969571ad671cf9dd3b" == new("The quick brown fox jumps over the lazy dog").hexdigest()
assert "132072df690933835eb8b6ad0b77e7b6f14acad7" == new("The quick brown fox jumps over the lazy cog").hexdigest()
assert "9c1185a5c5e9fc54612808977ee8f548b2258d31" == new("").hexdigest()

View file

@ -0,0 +1,3 @@
from .aes import aes
from .ecc import ecc
from .rsa import rsa

View file

@ -0,0 +1,159 @@
"""
This code is public domain. Everyone has the right to do whatever they want
with it for any purpose.
In case your jurisdiction does not consider the above disclaimer valid or
enforceable, here's an MIT license for you:
The MIT License (MIT)
Copyright (c) 2013 Vitalik Buterin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""
from ._util import inverse
class JacobianCurve:
def __init__(self, p, n, a, b, g):
self.p = p
self.n = n
self.a = a
self.b = b
self.g = g
self.n_length = len(bin(self.n).replace("0b", ""))
def isinf(self, p):
return p[0] == 0 and p[1] == 0
def to_jacobian(self, p):
return p[0], p[1], 1
def jacobian_double(self, p):
if not p[1]:
return 0, 0, 0
ysq = (p[1] ** 2) % self.p
s = (4 * p[0] * ysq) % self.p
m = (3 * p[0] ** 2 + self.a * p[2] ** 4) % self.p
nx = (m ** 2 - 2 * s) % self.p
ny = (m * (s - nx) - 8 * ysq ** 2) % self.p
nz = (2 * p[1] * p[2]) % self.p
return nx, ny, nz
def jacobian_add(self, p, q):
if not p[1]:
return q
if not q[1]:
return p
u1 = (p[0] * q[2] ** 2) % self.p
u2 = (q[0] * p[2] ** 2) % self.p
s1 = (p[1] * q[2] ** 3) % self.p
s2 = (q[1] * p[2] ** 3) % self.p
if u1 == u2:
if s1 != s2:
return (0, 0, 1)
return self.jacobian_double(p)
h = u2 - u1
r = s2 - s1
h2 = (h * h) % self.p
h3 = (h * h2) % self.p
u1h2 = (u1 * h2) % self.p
nx = (r ** 2 - h3 - 2 * u1h2) % self.p
ny = (r * (u1h2 - nx) - s1 * h3) % self.p
nz = (h * p[2] * q[2]) % self.p
return (nx, ny, nz)
def from_jacobian(self, p):
z = inverse(p[2], self.p)
return (p[0] * z ** 2) % self.p, (p[1] * z ** 3) % self.p
def jacobian_multiply(self, a, n, secret=False):
if a[1] == 0 or n == 0:
return 0, 0, 1
if n == 1:
return a
if n < 0 or n >= self.n:
return self.jacobian_multiply(a, n % self.n, secret)
half = self.jacobian_multiply(a, n // 2, secret)
half_sq = self.jacobian_double(half)
if secret:
# A constant-time implementation
half_sq_a = self.jacobian_add(half_sq, a)
if n % 2 == 0:
result = half_sq
if n % 2 == 1:
result = half_sq_a
return result
else:
if n % 2 == 0:
return half_sq
return self.jacobian_add(half_sq, a)
def jacobian_shamir(self, a, n, b, m):
ab = self.jacobian_add(a, b)
if n < 0 or n >= self.n:
n %= self.n
if m < 0 or m >= self.n:
m %= self.n
res = 0, 0, 1 # point on infinity
for i in range(self.n_length - 1, -1, -1):
res = self.jacobian_double(res)
has_n = n & (1 << i)
has_m = m & (1 << i)
if has_n:
if has_m == 0:
res = self.jacobian_add(res, a)
if has_m != 0:
res = self.jacobian_add(res, ab)
else:
if has_m == 0:
res = self.jacobian_add(res, (0, 0, 1)) # Try not to leak
if has_m != 0:
res = self.jacobian_add(res, b)
return res
def fast_multiply(self, a, n, secret=False):
return self.from_jacobian(self.jacobian_multiply(self.to_jacobian(a), n, secret))
def fast_add(self, a, b):
return self.from_jacobian(self.jacobian_add(self.to_jacobian(a), self.to_jacobian(b)))
def fast_shamir(self, a, n, b, m):
return self.from_jacobian(self.jacobian_shamir(self.to_jacobian(a), n, self.to_jacobian(b), m))
def is_on_curve(self, a):
x, y = a
# Simple arithmetic check
if (pow(x, 3, self.p) + self.a * x + self.b) % self.p != y * y % self.p:
return False
# nP = point-at-infinity
return self.isinf(self.jacobian_multiply(self.to_jacobian(a), self.n))

View file

@ -0,0 +1,79 @@
def int_to_bytes(raw, length):
data = []
for _ in range(length):
data.append(raw % 256)
raw //= 256
return bytes(data[::-1])
def bytes_to_int(data):
raw = 0
for byte in data:
raw = raw * 256 + byte
return raw
def legendre(a, p):
res = pow(a, (p - 1) // 2, p)
if res == p - 1:
return -1
else:
return res
def inverse(a, n):
if a == 0:
return 0
lm, hm = 1, 0
low, high = a % n, n
while low > 1:
r = high // low
nm, new = hm - lm * r, high - low * r
lm, low, hm, high = nm, new, lm, low
return lm % n
def square_root_mod_prime(n, p):
if n == 0:
return 0
if p == 2:
return n # We should never get here but it might be useful
if legendre(n, p) != 1:
raise ValueError("No square root")
# Optimizations
if p % 4 == 3:
return pow(n, (p + 1) // 4, p)
# 1. By factoring out powers of 2, find Q and S such that p - 1 =
# Q * 2 ** S with Q odd
q = p - 1
s = 0
while q % 2 == 0:
q //= 2
s += 1
# 2. Search for z in Z/pZ which is a quadratic non-residue
z = 1
while legendre(z, p) != -1:
z += 1
m, c, t, r = s, pow(z, q, p), pow(n, q, p), pow(n, (q + 1) // 2, p)
while True:
if t == 0:
return 0
elif t == 1:
return r
# Use repeated squaring to find the least i, 0 < i < M, such
# that t ** (2 ** i) = 1
t_sq = t
i = 0
for i in range(1, m):
t_sq = t_sq * t_sq % p
if t_sq == 1:
break
else:
raise ValueError("Should never get here")
# Let b = c ** (2 ** (m - i - 1))
b = pow(c, 2 ** (m - i - 1), p)
m = i
c = b * b % p
t = t * b * b % p
r = r * b % p
return r

View file

@ -0,0 +1,101 @@
import os
import pyaes
from .._aes import AES
__all__ = ["aes"]
class AESBackend:
def _get_algo_cipher_type(self, algo):
if not algo.startswith("aes-") or algo.count("-") != 2:
raise ValueError("Unknown cipher algorithm {}".format(algo))
key_length, cipher_type = algo[4:].split("-")
if key_length not in ("128", "192", "256"):
raise ValueError("Unknown cipher algorithm {}".format(algo))
if cipher_type not in ("cbc", "ctr", "cfb", "ofb"):
raise ValueError("Unknown cipher algorithm {}".format(algo))
return cipher_type
def is_algo_supported(self, algo):
try:
self._get_algo_cipher_type(algo)
return True
except ValueError:
return False
def random(self, length):
return os.urandom(length)
def encrypt(self, data, key, algo="aes-256-cbc"):
cipher_type = self._get_algo_cipher_type(algo)
# Generate random IV
iv = os.urandom(16)
if cipher_type == "cbc":
cipher = pyaes.AESModeOfOperationCBC(key, iv=iv)
elif cipher_type == "ctr":
# The IV is actually a counter, not an IV but it does almost the
# same. Notice: pyaes always uses 1 as initial counter! Make sure
# not to call pyaes directly.
# We kinda do two conversions here: from byte array to int here, and
# from int to byte array in pyaes internals. It's possible to fix that
# but I didn't notice any performance changes so I'm keeping clean code.
iv_int = 0
for byte in iv:
iv_int = (iv_int * 256) + byte
counter = pyaes.Counter(iv_int)
cipher = pyaes.AESModeOfOperationCTR(key, counter=counter)
elif cipher_type == "cfb":
# Change segment size from default 8 bytes to 16 bytes for OpenSSL
# compatibility
cipher = pyaes.AESModeOfOperationCFB(key, iv, segment_size=16)
elif cipher_type == "ofb":
cipher = pyaes.AESModeOfOperationOFB(key, iv)
encrypter = pyaes.Encrypter(cipher)
ciphertext = encrypter.feed(data)
ciphertext += encrypter.feed()
return ciphertext, iv
def decrypt(self, ciphertext, iv, key, algo="aes-256-cbc"):
cipher_type = self._get_algo_cipher_type(algo)
if cipher_type == "cbc":
cipher = pyaes.AESModeOfOperationCBC(key, iv=iv)
elif cipher_type == "ctr":
# The IV is actually a counter, not an IV but it does almost the
# same. Notice: pyaes always uses 1 as initial counter! Make sure
# not to call pyaes directly.
# We kinda do two conversions here: from byte array to int here, and
# from int to byte array in pyaes internals. It's possible to fix that
# but I didn't notice any performance changes so I'm keeping clean code.
iv_int = 0
for byte in iv:
iv_int = (iv_int * 256) + byte
counter = pyaes.Counter(iv_int)
cipher = pyaes.AESModeOfOperationCTR(key, counter=counter)
elif cipher_type == "cfb":
# Change segment size from default 8 bytes to 16 bytes for OpenSSL
# compatibility
cipher = pyaes.AESModeOfOperationCFB(key, iv, segment_size=16)
elif cipher_type == "ofb":
cipher = pyaes.AESModeOfOperationOFB(key, iv)
decrypter = pyaes.Decrypter(cipher)
data = decrypter.feed(ciphertext)
data += decrypter.feed()
return data
def get_backend(self):
return "fallback"
aes = AES(AESBackend())

View file

@ -0,0 +1,371 @@
import hmac
import os
from ._jacobian import JacobianCurve
from .._ecc import ECC
from .aes import aes
from ._util import int_to_bytes, bytes_to_int, inverse, square_root_mod_prime
# pylint: disable=line-too-long
CURVES = {
# nid: (p, n, a, b, (Gx, Gy)),
704: (
# secp112r1
0xDB7C2ABF62E35E668076BEAD208B,
0xDB7C2ABF62E35E7628DFAC6561C5,
0xDB7C2ABF62E35E668076BEAD2088,
0x659EF8BA043916EEDE8911702B22,
(
0x09487239995A5EE76B55F9C2F098,
0xA89CE5AF8724C0A23E0E0FF77500
)
),
705: (
# secp112r2
0xDB7C2ABF62E35E668076BEAD208B,
0x36DF0AAFD8B8D7597CA10520D04B,
0x6127C24C05F38A0AAAF65C0EF02C,
0x51DEF1815DB5ED74FCC34C85D709,
(
0x4BA30AB5E892B4E1649DD0928643,
0xADCD46F5882E3747DEF36E956E97
)
),
706: (
# secp128r1
0xFFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFF,
0xFFFFFFFE0000000075A30D1B9038A115,
0xFFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFC,
0xE87579C11079F43DD824993C2CEE5ED3,
(
0x161FF7528B899B2D0C28607CA52C5B86,
0xCF5AC8395BAFEB13C02DA292DDED7A83
)
),
707: (
# secp128r2
0xFFFFFFFDFFFFFFFFFFFFFFFFFFFFFFFF,
0x3FFFFFFF7FFFFFFFBE0024720613B5A3,
0xD6031998D1B3BBFEBF59CC9BBFF9AEE1,
0x5EEEFCA380D02919DC2C6558BB6D8A5D,
(
0x7B6AA5D85E572983E6FB32A7CDEBC140,
0x27B6916A894D3AEE7106FE805FC34B44
)
),
708: (
# secp160k1
0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73,
0x0100000000000000000001B8FA16DFAB9ACA16B6B3,
0,
7,
(
0x3B4C382CE37AA192A4019E763036F4F5DD4D7EBB,
0x938CF935318FDCED6BC28286531733C3F03C4FEE
)
),
709: (
# secp160r1
0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFF,
0x0100000000000000000001F4C8F927AED3CA752257,
0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFC,
0x001C97BEFC54BD7A8B65ACF89F81D4D4ADC565FA45,
(
0x4A96B5688EF573284664698968C38BB913CBFC82,
0x23A628553168947D59DCC912042351377AC5FB32
)
),
710: (
# secp160r2
0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC73,
0x0100000000000000000000351EE786A818F3A1A16B,
0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFAC70,
0x00B4E134D3FB59EB8BAB57274904664D5AF50388BA,
(
0x52DCB034293A117E1F4FF11B30F7199D3144CE6D,
0xFEAFFEF2E331F296E071FA0DF9982CFEA7D43F2E
)
),
711: (
# secp192k1
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37,
0xFFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D,
0,
3,
(
0xDB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D,
0x9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D
)
),
409: (
# prime192v1
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF,
0xFFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831,
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC,
0x64210519E59C80E70FA7E9AB72243049FEB8DEECC146B9B1,
(
0x188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012,
0x07192B95FFC8DA78631011ED6B24CDD573F977A11E794811
)
),
712: (
# secp224k1
0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFE56D,
0x010000000000000000000000000001DCE8D2EC6184CAF0A971769FB1F7,
0,
5,
(
0xA1455B334DF099DF30FC28A169A467E9E47075A90F7E650EB6B7A45C,
0x7E089FED7FBA344282CAFBD6F7E319F7C0B0BD59E2CA4BDB556D61A5
)
),
713: (
# secp224r1
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000001,
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D,
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFE,
0xB4050A850C04B3ABF54132565044B0B7D7BFD8BA270B39432355FFB4,
(
0xB70E0CBD6BB4BF7F321390B94A03C1D356C21122343280D6115C1D21,
0xBD376388B5F723FB4C22DFE6CD4375A05A07476444D5819985007E34
)
),
714: (
# secp256k1
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F,
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141,
0,
7,
(
0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
)
),
415: (
# prime256v1
0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF,
0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551,
0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC,
0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B,
(
0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296,
0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5
)
),
715: (
# secp384r1
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF,
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973,
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC,
0xB3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF,
(
0xAA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7,
0x3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F
)
),
716: (
# secp521r1
0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,
0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409,
0x01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC,
0x0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00,
(
0x00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66,
0x011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650
)
)
}
# pylint: enable=line-too-long
class EllipticCurveBackend:
def __init__(self, nid):
self.p, self.n, self.a, self.b, self.g = CURVES[nid]
self.jacobian = JacobianCurve(*CURVES[nid])
self.public_key_length = (len(bin(self.p).replace("0b", "")) + 7) // 8
self.order_bitlength = len(bin(self.n).replace("0b", ""))
def _int_to_bytes(self, raw, len=None):
return int_to_bytes(raw, len or self.public_key_length)
def decompress_point(self, public_key):
# Parse & load data
x = bytes_to_int(public_key[1:])
# Calculate Y
y_square = (pow(x, 3, self.p) + self.a * x + self.b) % self.p
try:
y = square_root_mod_prime(y_square, self.p)
except Exception:
raise ValueError("Invalid public key") from None
if y % 2 != public_key[0] - 0x02:
y = self.p - y
return self._int_to_bytes(x), self._int_to_bytes(y)
def new_private_key(self):
while True:
private_key = os.urandom(self.public_key_length)
if bytes_to_int(private_key) >= self.n:
continue
return private_key
def private_to_public(self, private_key):
raw = bytes_to_int(private_key)
x, y = self.jacobian.fast_multiply(self.g, raw)
return self._int_to_bytes(x), self._int_to_bytes(y)
def ecdh(self, private_key, public_key):
x, y = public_key
x, y = bytes_to_int(x), bytes_to_int(y)
private_key = bytes_to_int(private_key)
x, _ = self.jacobian.fast_multiply((x, y), private_key, secret=True)
return self._int_to_bytes(x)
def _subject_to_int(self, subject):
return bytes_to_int(subject[:(self.order_bitlength + 7) // 8])
def sign(self, subject, raw_private_key, recoverable, is_compressed, entropy):
z = self._subject_to_int(subject)
private_key = bytes_to_int(raw_private_key)
k = bytes_to_int(entropy)
# Fix k length to prevent Minerva. Increasing multiplier by a
# multiple of order doesn't break anything. This fix was ported
# from python-ecdsa
ks = k + self.n
kt = ks + self.n
ks_len = len(bin(ks).replace("0b", "")) // 8
kt_len = len(bin(kt).replace("0b", "")) // 8
if ks_len == kt_len:
k = kt
else:
k = ks
px, py = self.jacobian.fast_multiply(self.g, k, secret=True)
r = px % self.n
if r == 0:
# Invalid k
raise ValueError("Invalid k")
s = (inverse(k, self.n) * (z + (private_key * r))) % self.n
if s == 0:
# Invalid k
raise ValueError("Invalid k")
inverted = False
if s * 2 >= self.n:
s = self.n - s
inverted = True
rs_buf = self._int_to_bytes(r) + self._int_to_bytes(s)
if recoverable:
recid = (py % 2) ^ inverted
recid += 2 * int(px // self.n)
if is_compressed:
return bytes([31 + recid]) + rs_buf
else:
if recid >= 4:
raise ValueError("Too big recovery ID, use compressed address instead")
return bytes([27 + recid]) + rs_buf
else:
return rs_buf
def recover(self, signature, subject):
z = self._subject_to_int(subject)
recid = signature[0] - 27 if signature[0] < 31 else signature[0] - 31
r = bytes_to_int(signature[1:self.public_key_length + 1])
s = bytes_to_int(signature[self.public_key_length + 1:])
# Verify bounds
if not 0 <= recid < 2 * (self.p // self.n + 1):
raise ValueError("Invalid recovery ID")
if r >= self.n:
raise ValueError("r is out of bounds")
if s >= self.n:
raise ValueError("s is out of bounds")
rinv = inverse(r, self.n)
u1 = (-z * rinv) % self.n
u2 = (s * rinv) % self.n
# Recover R
rx = r + (recid // 2) * self.n
if rx >= self.p:
raise ValueError("Rx is out of bounds")
# Almost copied from decompress_point
ry_square = (pow(rx, 3, self.p) + self.a * rx + self.b) % self.p
try:
ry = square_root_mod_prime(ry_square, self.p)
except Exception:
raise ValueError("Invalid recovered public key") from None
# Ensure the point is correct
if ry % 2 != recid % 2:
# Fix Ry sign
ry = self.p - ry
x, y = self.jacobian.fast_shamir(self.g, u1, (rx, ry), u2)
return self._int_to_bytes(x), self._int_to_bytes(y)
def verify(self, signature, subject, public_key):
z = self._subject_to_int(subject)
r = bytes_to_int(signature[:self.public_key_length])
s = bytes_to_int(signature[self.public_key_length:])
# Verify bounds
if r >= self.n:
raise ValueError("r is out of bounds")
if s >= self.n:
raise ValueError("s is out of bounds")
public_key = [bytes_to_int(c) for c in public_key]
# Ensure that the public key is correct
if not self.jacobian.is_on_curve(public_key):
raise ValueError("Public key is not on curve")
sinv = inverse(s, self.n)
u1 = (z * sinv) % self.n
u2 = (r * sinv) % self.n
x1, _ = self.jacobian.fast_shamir(self.g, u1, public_key, u2)
if r != x1 % self.n:
raise ValueError("Invalid signature")
return True
def derive_child(self, seed, child):
# Round 1
h = hmac.new(key=b"Bitcoin seed", msg=seed, digestmod="sha512").digest()
private_key1 = h[:32]
x, y = self.private_to_public(private_key1)
public_key1 = bytes([0x02 + (y[-1] % 2)]) + x
private_key1 = bytes_to_int(private_key1)
# Round 2
msg = public_key1 + self._int_to_bytes(child, 4)
h = hmac.new(key=h[32:], msg=msg, digestmod="sha512").digest()
private_key2 = bytes_to_int(h[:32])
return self._int_to_bytes((private_key1 + private_key2) % self.n)
@classmethod
def get_backend(cls):
return "fallback"
ecc = ECC(EllipticCurveBackend, aes)

View file

@ -0,0 +1,8 @@
# pylint: disable=too-few-public-methods
class RSA:
def get_backend(self):
return "fallback"
rsa = RSA()

View file

@ -0,0 +1,3 @@
from .aes import aes
from .ecc import ecc
from .rsa import rsa

View file

@ -0,0 +1,156 @@
import ctypes
import threading
from .._aes import AES
from ..fallback.aes import aes as fallback_aes
from .library import lib, openssl_backend
# Initialize functions
try:
lib.EVP_CIPHER_CTX_new.restype = ctypes.POINTER(ctypes.c_char)
except AttributeError:
pass
lib.EVP_get_cipherbyname.restype = ctypes.POINTER(ctypes.c_char)
thread_local = threading.local()
class Context:
def __init__(self, ptr, do_free):
self.lib = lib
self.ptr = ptr
self.do_free = do_free
def __del__(self):
if self.do_free:
self.lib.EVP_CIPHER_CTX_free(self.ptr)
class AESBackend:
ALGOS = (
"aes-128-cbc", "aes-192-cbc", "aes-256-cbc",
"aes-128-ctr", "aes-192-ctr", "aes-256-ctr",
"aes-128-cfb", "aes-192-cfb", "aes-256-cfb",
"aes-128-ofb", "aes-192-ofb", "aes-256-ofb"
)
def __init__(self):
self.is_supported_ctx_new = hasattr(lib, "EVP_CIPHER_CTX_new")
self.is_supported_ctx_reset = hasattr(lib, "EVP_CIPHER_CTX_reset")
def _get_ctx(self):
if not hasattr(thread_local, "ctx"):
if self.is_supported_ctx_new:
thread_local.ctx = Context(lib.EVP_CIPHER_CTX_new(), True)
else:
# 1 KiB ought to be enough for everybody. We don't know the real
# size of the context buffer because we are unsure about padding and
# pointer size
thread_local.ctx = Context(ctypes.create_string_buffer(1024), False)
return thread_local.ctx.ptr
def get_backend(self):
return openssl_backend
def _get_cipher(self, algo):
if algo not in self.ALGOS:
raise ValueError("Unknown cipher algorithm {}".format(algo))
cipher = lib.EVP_get_cipherbyname(algo.encode())
if not cipher:
raise ValueError("Unknown cipher algorithm {}".format(algo))
return cipher
def is_algo_supported(self, algo):
try:
self._get_cipher(algo)
return True
except ValueError:
return False
def random(self, length):
entropy = ctypes.create_string_buffer(length)
lib.RAND_bytes(entropy, length)
return bytes(entropy)
def encrypt(self, data, key, algo="aes-256-cbc"):
# Initialize context
ctx = self._get_ctx()
if not self.is_supported_ctx_new:
lib.EVP_CIPHER_CTX_init(ctx)
try:
lib.EVP_EncryptInit_ex(ctx, self._get_cipher(algo), None, None, None)
# Generate random IV
iv_length = 16
iv = self.random(iv_length)
# Set key and IV
lib.EVP_EncryptInit_ex(ctx, None, None, key, iv)
# Actually encrypt
block_size = 16
output = ctypes.create_string_buffer((len(data) // block_size + 1) * block_size)
output_len = ctypes.c_int()
if not lib.EVP_CipherUpdate(ctx, output, ctypes.byref(output_len), data, len(data)):
raise ValueError("Could not feed cipher with data")
new_output = ctypes.byref(output, output_len.value)
output_len2 = ctypes.c_int()
if not lib.EVP_CipherFinal_ex(ctx, new_output, ctypes.byref(output_len2)):
raise ValueError("Could not finalize cipher")
ciphertext = output[:output_len.value + output_len2.value]
return ciphertext, iv
finally:
if self.is_supported_ctx_reset:
lib.EVP_CIPHER_CTX_reset(ctx)
else:
lib.EVP_CIPHER_CTX_cleanup(ctx)
def decrypt(self, ciphertext, iv, key, algo="aes-256-cbc"):
# Initialize context
ctx = self._get_ctx()
if not self.is_supported_ctx_new:
lib.EVP_CIPHER_CTX_init(ctx)
try:
lib.EVP_DecryptInit_ex(ctx, self._get_cipher(algo), None, None, None)
# Make sure IV length is correct
iv_length = 16
if len(iv) != iv_length:
raise ValueError("Expected IV to be {} bytes, got {} bytes".format(iv_length, len(iv)))
# Set key and IV
lib.EVP_DecryptInit_ex(ctx, None, None, key, iv)
# Actually decrypt
output = ctypes.create_string_buffer(len(ciphertext))
output_len = ctypes.c_int()
if not lib.EVP_DecryptUpdate(ctx, output, ctypes.byref(output_len), ciphertext, len(ciphertext)):
raise ValueError("Could not feed decipher with ciphertext")
new_output = ctypes.byref(output, output_len.value)
output_len2 = ctypes.c_int()
if not lib.EVP_DecryptFinal_ex(ctx, new_output, ctypes.byref(output_len2)):
raise ValueError("Could not finalize decipher")
return output[:output_len.value + output_len2.value]
finally:
if self.is_supported_ctx_reset:
lib.EVP_CIPHER_CTX_reset(ctx)
else:
lib.EVP_CIPHER_CTX_cleanup(ctx)
aes = AES(AESBackend(), fallback_aes)

View file

@ -0,0 +1,551 @@
import ctypes
import hmac
import threading
from .._ecc import ECC
from .aes import aes
from .library import lib, openssl_backend
# Initialize functions
lib.BN_new.restype = ctypes.POINTER(ctypes.c_char)
lib.BN_bin2bn.restype = ctypes.POINTER(ctypes.c_char)
lib.BN_CTX_new.restype = ctypes.POINTER(ctypes.c_char)
lib.EC_GROUP_new_by_curve_name.restype = ctypes.POINTER(ctypes.c_char)
lib.EC_KEY_new_by_curve_name.restype = ctypes.POINTER(ctypes.c_char)
lib.EC_POINT_new.restype = ctypes.POINTER(ctypes.c_char)
lib.EC_KEY_get0_private_key.restype = ctypes.POINTER(ctypes.c_char)
lib.EVP_PKEY_new.restype = ctypes.POINTER(ctypes.c_char)
try:
lib.EVP_PKEY_CTX_new.restype = ctypes.POINTER(ctypes.c_char)
except AttributeError:
pass
thread_local = threading.local()
class BN:
# BN_CTX
class Context:
def __init__(self):
self.ptr = lib.BN_CTX_new()
self.lib = lib # For finalizer
def __del__(self):
self.lib.BN_CTX_free(self.ptr)
@classmethod
def get(cls):
# Get thread-safe contexf
if not hasattr(thread_local, "bn_ctx"):
thread_local.bn_ctx = cls()
return thread_local.bn_ctx.ptr
def __init__(self, value=None, link_only=False):
if link_only:
self.bn = value
self._free = False
else:
if value is None:
self.bn = lib.BN_new()
self._free = True
elif isinstance(value, bytes):
self.bn = lib.BN_bin2bn(value, len(value), None)
self._free = True
else:
self.bn = lib.BN_new()
lib.BN_clear(self.bn)
lib.BN_add_word(self.bn, value)
self._free = True
def __del__(self):
if self._free:
lib.BN_free(self.bn)
def bytes(self, length=None):
buf = ctypes.create_string_buffer((len(self) + 7) // 8)
lib.BN_bn2bin(self.bn, buf)
buf = bytes(buf)
if length is None:
return buf
else:
if length < len(buf):
raise ValueError("Too little space for BN")
return b"\x00" * (length - len(buf)) + buf
def __int__(self):
value = 0
for byte in self.bytes():
value = value * 256 + byte
return value
def __len__(self):
return lib.BN_num_bits(self.bn)
def inverse(self, modulo):
result = BN()
if not lib.BN_mod_inverse(result.bn, self.bn, modulo.bn, BN.Context.get()):
raise ValueError("Could not compute inverse")
return result
def __floordiv__(self, other):
if not isinstance(other, BN):
raise TypeError("Can only divide BN by BN, not {}".format(other))
result = BN()
if not lib.BN_div(result.bn, None, self.bn, other.bn, BN.Context.get()):
raise ZeroDivisionError("Division by zero")
return result
def __mod__(self, other):
if not isinstance(other, BN):
raise TypeError("Can only divide BN by BN, not {}".format(other))
result = BN()
if not lib.BN_div(None, result.bn, self.bn, other.bn, BN.Context.get()):
raise ZeroDivisionError("Division by zero")
return result
def __add__(self, other):
if not isinstance(other, BN):
raise TypeError("Can only sum BN's, not BN and {}".format(other))
result = BN()
if not lib.BN_add(result.bn, self.bn, other.bn):
raise ValueError("Could not sum two BN's")
return result
def __sub__(self, other):
if not isinstance(other, BN):
raise TypeError("Can only subtract BN's, not BN and {}".format(other))
result = BN()
if not lib.BN_sub(result.bn, self.bn, other.bn):
raise ValueError("Could not subtract BN from BN")
return result
def __mul__(self, other):
if not isinstance(other, BN):
raise TypeError("Can only multiply BN by BN, not {}".format(other))
result = BN()
if not lib.BN_mul(result.bn, self.bn, other.bn, BN.Context.get()):
raise ValueError("Could not multiply two BN's")
return result
def __neg__(self):
return BN(0) - self
# A dirty but nice way to update current BN and free old BN at the same time
def __imod__(self, other):
res = self % other
self.bn, res.bn = res.bn, self.bn
return self
def __iadd__(self, other):
res = self + other
self.bn, res.bn = res.bn, self.bn
return self
def __isub__(self, other):
res = self - other
self.bn, res.bn = res.bn, self.bn
return self
def __imul__(self, other):
res = self * other
self.bn, res.bn = res.bn, self.bn
return self
def cmp(self, other):
if not isinstance(other, BN):
raise TypeError("Can only compare BN with BN, not {}".format(other))
return lib.BN_cmp(self.bn, other.bn)
def __eq__(self, other):
return self.cmp(other) == 0
def __lt__(self, other):
return self.cmp(other) < 0
def __gt__(self, other):
return self.cmp(other) > 0
def __ne__(self, other):
return self.cmp(other) != 0
def __le__(self, other):
return self.cmp(other) <= 0
def __ge__(self, other):
return self.cmp(other) >= 0
def __repr__(self):
return "<BN {}>".format(int(self))
def __str__(self):
return str(int(self))
class EllipticCurveBackend:
def __init__(self, nid):
self.lib = lib # For finalizer
self.nid = nid
self.group = lib.EC_GROUP_new_by_curve_name(self.nid)
if not self.group:
raise ValueError("The curve is not supported by OpenSSL")
self.order = BN()
self.p = BN()
bn_ctx = BN.Context.get()
lib.EC_GROUP_get_order(self.group, self.order.bn, bn_ctx)
lib.EC_GROUP_get_curve_GFp(self.group, self.p.bn, None, None, bn_ctx)
self.public_key_length = (len(self.p) + 7) // 8
self.is_supported_evp_pkey_ctx = hasattr(lib, "EVP_PKEY_CTX_new")
def __del__(self):
self.lib.EC_GROUP_free(self.group)
def _private_key_to_ec_key(self, private_key):
eckey = lib.EC_KEY_new_by_curve_name(self.nid)
if not eckey:
raise ValueError("Failed to allocate EC_KEY")
private_key = BN(private_key)
if not lib.EC_KEY_set_private_key(eckey, private_key.bn):
lib.EC_KEY_free(eckey)
raise ValueError("Invalid private key")
return eckey, private_key
def _public_key_to_point(self, public_key):
x = BN(public_key[0])
y = BN(public_key[1])
# EC_KEY_set_public_key_affine_coordinates is not supported by
# OpenSSL 1.0.0 so we can't use it
point = lib.EC_POINT_new(self.group)
if not lib.EC_POINT_set_affine_coordinates_GFp(self.group, point, x.bn, y.bn, BN.Context.get()):
raise ValueError("Could not set public key affine coordinates")
return point
def _public_key_to_ec_key(self, public_key):
eckey = lib.EC_KEY_new_by_curve_name(self.nid)
if not eckey:
raise ValueError("Failed to allocate EC_KEY")
try:
# EC_KEY_set_public_key_affine_coordinates is not supported by
# OpenSSL 1.0.0 so we can't use it
point = self._public_key_to_point(public_key)
if not lib.EC_KEY_set_public_key(eckey, point):
raise ValueError("Could not set point")
lib.EC_POINT_free(point)
return eckey
except Exception as e:
lib.EC_KEY_free(eckey)
raise e from None
def _point_to_affine(self, point):
# Convert to affine coordinates
x = BN()
y = BN()
if lib.EC_POINT_get_affine_coordinates_GFp(self.group, point, x.bn, y.bn, BN.Context.get()) != 1:
raise ValueError("Failed to convert public key to affine coordinates")
# Convert to binary
if (len(x) + 7) // 8 > self.public_key_length:
raise ValueError("Public key X coordinate is too large")
if (len(y) + 7) // 8 > self.public_key_length:
raise ValueError("Public key Y coordinate is too large")
return x.bytes(self.public_key_length), y.bytes(self.public_key_length)
def decompress_point(self, public_key):
point = lib.EC_POINT_new(self.group)
if not point:
raise ValueError("Could not create point")
try:
if not lib.EC_POINT_oct2point(self.group, point, public_key, len(public_key), BN.Context.get()):
raise ValueError("Invalid compressed public key")
return self._point_to_affine(point)
finally:
lib.EC_POINT_free(point)
def new_private_key(self):
# Create random key
eckey = lib.EC_KEY_new_by_curve_name(self.nid)
lib.EC_KEY_generate_key(eckey)
# To big integer
private_key = BN(lib.EC_KEY_get0_private_key(eckey), link_only=True)
# To binary
private_key_buf = private_key.bytes()
# Cleanup
lib.EC_KEY_free(eckey)
return private_key_buf
def private_to_public(self, private_key):
eckey, private_key = self._private_key_to_ec_key(private_key)
try:
# Derive public key
point = lib.EC_POINT_new(self.group)
try:
if not lib.EC_POINT_mul(self.group, point, private_key.bn, None, None, BN.Context.get()):
raise ValueError("Failed to derive public key")
return self._point_to_affine(point)
finally:
lib.EC_POINT_free(point)
finally:
lib.EC_KEY_free(eckey)
def ecdh(self, private_key, public_key):
if not self.is_supported_evp_pkey_ctx:
# Use ECDH_compute_key instead
# Create EC_KEY from private key
eckey, _ = self._private_key_to_ec_key(private_key)
try:
# Create EC_POINT from public key
point = self._public_key_to_point(public_key)
try:
key = ctypes.create_string_buffer(self.public_key_length)
if lib.ECDH_compute_key(key, self.public_key_length, point, eckey, None) == -1:
raise ValueError("Could not compute shared secret")
return bytes(key)
finally:
lib.EC_POINT_free(point)
finally:
lib.EC_KEY_free(eckey)
# Private key:
# Create EC_KEY
eckey, _ = self._private_key_to_ec_key(private_key)
try:
# Convert to EVP_PKEY
pkey = lib.EVP_PKEY_new()
if not pkey:
raise ValueError("Could not create private key object")
try:
lib.EVP_PKEY_set1_EC_KEY(pkey, eckey)
# Public key:
# Create EC_KEY
peer_eckey = self._public_key_to_ec_key(public_key)
try:
# Convert to EVP_PKEY
peer_pkey = lib.EVP_PKEY_new()
if not peer_pkey:
raise ValueError("Could not create public key object")
try:
lib.EVP_PKEY_set1_EC_KEY(peer_pkey, peer_eckey)
# Create context
ctx = lib.EVP_PKEY_CTX_new(pkey, None)
if not ctx:
raise ValueError("Could not create EVP context")
try:
if lib.EVP_PKEY_derive_init(ctx) != 1:
raise ValueError("Could not initialize key derivation")
if not lib.EVP_PKEY_derive_set_peer(ctx, peer_pkey):
raise ValueError("Could not set peer")
# Actually derive
key_len = ctypes.c_int(0)
lib.EVP_PKEY_derive(ctx, None, ctypes.byref(key_len))
key = ctypes.create_string_buffer(key_len.value)
lib.EVP_PKEY_derive(ctx, key, ctypes.byref(key_len))
return bytes(key)
finally:
lib.EVP_PKEY_CTX_free(ctx)
finally:
lib.EVP_PKEY_free(peer_pkey)
finally:
lib.EC_KEY_free(peer_eckey)
finally:
lib.EVP_PKEY_free(pkey)
finally:
lib.EC_KEY_free(eckey)
def _subject_to_bn(self, subject):
return BN(subject[:(len(self.order) + 7) // 8])
def sign(self, subject, private_key, recoverable, is_compressed, entropy):
z = self._subject_to_bn(subject)
private_key = BN(private_key)
k = BN(entropy)
rp = lib.EC_POINT_new(self.group)
bn_ctx = BN.Context.get()
try:
# Fix Minerva
k1 = k + self.order
k2 = k1 + self.order
if len(k1) == len(k2):
k = k2
else:
k = k1
if not lib.EC_POINT_mul(self.group, rp, k.bn, None, None, bn_ctx):
raise ValueError("Could not generate R")
# Convert to affine coordinates
rx = BN()
ry = BN()
if lib.EC_POINT_get_affine_coordinates_GFp(self.group, rp, rx.bn, ry.bn, bn_ctx) != 1:
raise ValueError("Failed to convert R to affine coordinates")
r = rx % self.order
if r == BN(0):
raise ValueError("Invalid k")
# Calculate s = k^-1 * (z + r * private_key) mod n
s = (k.inverse(self.order) * (z + r * private_key)) % self.order
if s == BN(0):
raise ValueError("Invalid k")
inverted = False
if s * BN(2) >= self.order:
s = self.order - s
inverted = True
r_buf = r.bytes(self.public_key_length)
s_buf = s.bytes(self.public_key_length)
if recoverable:
# Generate recid
recid = int(ry % BN(2)) ^ inverted
# The line below is highly unlikely to matter in case of
# secp256k1 but might make sense for other curves
recid += 2 * int(rx // self.order)
if is_compressed:
return bytes([31 + recid]) + r_buf + s_buf
else:
if recid >= 4:
raise ValueError("Too big recovery ID, use compressed address instead")
return bytes([27 + recid]) + r_buf + s_buf
else:
return r_buf + s_buf
finally:
lib.EC_POINT_free(rp)
def recover(self, signature, subject):
recid = signature[0] - 27 if signature[0] < 31 else signature[0] - 31
r = BN(signature[1:self.public_key_length + 1])
s = BN(signature[self.public_key_length + 1:])
# Verify bounds
if r >= self.order:
raise ValueError("r is out of bounds")
if s >= self.order:
raise ValueError("s is out of bounds")
bn_ctx = BN.Context.get()
z = self._subject_to_bn(subject)
rinv = r.inverse(self.order)
u1 = (-z * rinv) % self.order
u2 = (s * rinv) % self.order
# Recover R
rx = r + BN(recid // 2) * self.order
if rx >= self.p:
raise ValueError("Rx is out of bounds")
rp = lib.EC_POINT_new(self.group)
if not rp:
raise ValueError("Could not create R")
try:
init_buf = b"\x02" + rx.bytes(self.public_key_length)
if not lib.EC_POINT_oct2point(self.group, rp, init_buf, len(init_buf), bn_ctx):
raise ValueError("Could not use Rx to initialize point")
ry = BN()
if lib.EC_POINT_get_affine_coordinates_GFp(self.group, rp, None, ry.bn, bn_ctx) != 1:
raise ValueError("Failed to convert R to affine coordinates")
if int(ry % BN(2)) != recid % 2:
# Fix Ry sign
ry = self.p - ry
if lib.EC_POINT_set_affine_coordinates_GFp(self.group, rp, rx.bn, ry.bn, bn_ctx) != 1:
raise ValueError("Failed to update R coordinates")
# Recover public key
result = lib.EC_POINT_new(self.group)
if not result:
raise ValueError("Could not create point")
try:
if not lib.EC_POINT_mul(self.group, result, u1.bn, rp, u2.bn, bn_ctx):
raise ValueError("Could not recover public key")
return self._point_to_affine(result)
finally:
lib.EC_POINT_free(result)
finally:
lib.EC_POINT_free(rp)
def verify(self, signature, subject, public_key):
r_raw = signature[:self.public_key_length]
r = BN(r_raw)
s = BN(signature[self.public_key_length:])
if r >= self.order:
raise ValueError("r is out of bounds")
if s >= self.order:
raise ValueError("s is out of bounds")
bn_ctx = BN.Context.get()
z = self._subject_to_bn(subject)
pub_p = lib.EC_POINT_new(self.group)
if not pub_p:
raise ValueError("Could not create public key point")
try:
init_buf = b"\x04" + public_key[0] + public_key[1]
if not lib.EC_POINT_oct2point(self.group, pub_p, init_buf, len(init_buf), bn_ctx):
raise ValueError("Could initialize point")
sinv = s.inverse(self.order)
u1 = (z * sinv) % self.order
u2 = (r * sinv) % self.order
# Recover public key
result = lib.EC_POINT_new(self.group)
if not result:
raise ValueError("Could not create point")
try:
if not lib.EC_POINT_mul(self.group, result, u1.bn, pub_p, u2.bn, bn_ctx):
raise ValueError("Could not recover public key")
if BN(self._point_to_affine(result)[0]) % self.order != r:
raise ValueError("Invalid signature")
return True
finally:
lib.EC_POINT_free(result)
finally:
lib.EC_POINT_free(pub_p)
def derive_child(self, seed, child):
# Round 1
h = hmac.new(key=b"Bitcoin seed", msg=seed, digestmod="sha512").digest()
private_key1 = h[:32]
x, y = self.private_to_public(private_key1)
public_key1 = bytes([0x02 + (y[-1] % 2)]) + x
private_key1 = BN(private_key1)
# Round 2
child_bytes = []
for _ in range(4):
child_bytes.append(child & 255)
child >>= 8
child_bytes = bytes(child_bytes[::-1])
msg = public_key1 + child_bytes
h = hmac.new(key=h[32:], msg=msg, digestmod="sha512").digest()
private_key2 = BN(h[:32])
return ((private_key1 + private_key2) % self.order).bytes(self.public_key_length)
@classmethod
def get_backend(cls):
return openssl_backend
ecc = ECC(EllipticCurveBackend, aes)

View file

@ -0,0 +1,93 @@
import os
import sys
import ctypes
import ctypes.util
# Disable false-positive _MEIPASS
# pylint: disable=no-member,protected-access
# Discover OpenSSL library
def discover_paths():
# Search local files first
if "win" in sys.platform:
# Windows
names = [
"libeay32.dll"
]
openssl_paths = [os.path.abspath(path) for path in names]
if hasattr(sys, "_MEIPASS"):
openssl_paths += [os.path.join(sys._MEIPASS, path) for path in openssl_paths]
openssl_paths.append(ctypes.util.find_library("libeay32"))
elif "darwin" in sys.platform:
# Mac OS
names = [
"libcrypto.dylib",
"libcrypto.1.1.0.dylib",
"libcrypto.1.0.2.dylib",
"libcrypto.1.0.1.dylib",
"libcrypto.1.0.0.dylib",
"libcrypto.0.9.8.dylib"
]
openssl_paths = [os.path.abspath(path) for path in names]
openssl_paths += names
openssl_paths += [
"/usr/local/opt/openssl/lib/libcrypto.dylib"
]
if hasattr(sys, "_MEIPASS") and "RESOURCEPATH" in os.environ:
openssl_paths += [
os.path.join(os.environ["RESOURCEPATH"], "..", "Frameworks", name)
for name in names
]
openssl_paths.append(ctypes.util.find_library("ssl"))
else:
# Linux, BSD and such
names = [
"libcrypto.so",
"libssl.so",
"libcrypto.so.1.1.0",
"libssl.so.1.1.0",
"libcrypto.so.1.0.2",
"libssl.so.1.0.2",
"libcrypto.so.1.0.1",
"libssl.so.1.0.1",
"libcrypto.so.1.0.0",
"libssl.so.1.0.0",
"libcrypto.so.0.9.8",
"libssl.so.0.9.8"
]
openssl_paths = [os.path.abspath(path) for path in names]
openssl_paths += names
if hasattr(sys, "_MEIPASS"):
openssl_paths += [os.path.join(sys._MEIPASS, path) for path in names]
openssl_paths.append(ctypes.util.find_library("ssl"))
return openssl_paths
def discover_library():
for path in discover_paths():
if path:
try:
return ctypes.CDLL(path)
except OSError:
pass
raise OSError("OpenSSL is unavailable")
lib = discover_library()
# Initialize internal state
try:
lib.OPENSSL_add_all_algorithms_conf()
except AttributeError:
pass
try:
lib.OpenSSL_version.restype = ctypes.c_char_p
openssl_backend = lib.OpenSSL_version(0).decode()
except AttributeError:
lib.SSLeay_version.restype = ctypes.c_char_p
openssl_backend = lib.SSLeay_version(0).decode()
openssl_backend += " at " + lib._name

View file

@ -0,0 +1,11 @@
# pylint: disable=too-few-public-methods
from .library import openssl_backend
class RSA:
def get_backend(self):
return openssl_backend
rsa = RSA()