Spaces:
Paused
Paused
| # =================================================================== | |
| # | |
| # Copyright (c) 2015, Legrandin <helderijs@gmail.com> | |
| # 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 COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| # "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 | |
| # COPYRIGHT HOLDER OR CONTRIBUTORS 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. | |
| # =================================================================== | |
| from __future__ import print_function | |
| import re | |
| import struct | |
| import binascii | |
| from collections import namedtuple | |
| from Crypto.Util.py3compat import bord, tobytes, tostr, bchr, is_string | |
| from Crypto.Util.number import bytes_to_long, long_to_bytes | |
| from Crypto.Math.Numbers import Integer | |
| from Crypto.Util.asn1 import (DerObjectId, DerOctetString, DerSequence, | |
| DerBitString) | |
| from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer, | |
| SmartPointer, c_size_t, c_uint8_ptr, | |
| c_ulonglong, null_pointer) | |
| from Crypto.PublicKey import (_expand_subject_public_key_info, | |
| _create_subject_public_key_info, | |
| _extract_subject_public_key_info) | |
| from Crypto.Hash import SHA512, SHAKE256 | |
| from Crypto.Random import get_random_bytes | |
| from Crypto.Random.random import getrandbits | |
| _ec_lib = load_pycryptodome_raw_lib("Crypto.PublicKey._ec_ws", """ | |
| typedef void EcContext; | |
| typedef void EcPoint; | |
| int ec_ws_new_context(EcContext **pec_ctx, | |
| const uint8_t *modulus, | |
| const uint8_t *b, | |
| const uint8_t *order, | |
| size_t len, | |
| uint64_t seed); | |
| void ec_free_context(EcContext *ec_ctx); | |
| int ec_ws_new_point(EcPoint **pecp, | |
| const uint8_t *x, | |
| const uint8_t *y, | |
| size_t len, | |
| const EcContext *ec_ctx); | |
| void ec_ws_free_point(EcPoint *ecp); | |
| int ec_ws_get_xy(uint8_t *x, | |
| uint8_t *y, | |
| size_t len, | |
| const EcPoint *ecp); | |
| int ec_ws_double(EcPoint *p); | |
| int ec_ws_add(EcPoint *ecpa, EcPoint *ecpb); | |
| int ec_ws_scalar(EcPoint *ecp, | |
| const uint8_t *k, | |
| size_t len, | |
| uint64_t seed); | |
| int ec_ws_clone(EcPoint **pecp2, const EcPoint *ecp); | |
| int ec_ws_cmp(const EcPoint *ecp1, const EcPoint *ecp2); | |
| int ec_ws_neg(EcPoint *p); | |
| """) | |
| _ed25519_lib = load_pycryptodome_raw_lib("Crypto.PublicKey._ed25519", """ | |
| typedef void Point; | |
| int ed25519_new_point(Point **out, | |
| const uint8_t x[32], | |
| const uint8_t y[32], | |
| size_t modsize, | |
| const void *context); | |
| int ed25519_clone(Point **P, const Point *Q); | |
| void ed25519_free_point(Point *p); | |
| int ed25519_cmp(const Point *p1, const Point *p2); | |
| int ed25519_neg(Point *p); | |
| int ed25519_get_xy(uint8_t *xb, uint8_t *yb, size_t modsize, Point *p); | |
| int ed25519_double(Point *p); | |
| int ed25519_add(Point *P1, const Point *P2); | |
| int ed25519_scalar(Point *P, const uint8_t *scalar, size_t scalar_len, uint64_t seed); | |
| """) | |
| _ed448_lib = load_pycryptodome_raw_lib("Crypto.PublicKey._ed448", """ | |
| typedef void EcContext; | |
| typedef void PointEd448; | |
| int ed448_new_context(EcContext **pec_ctx); | |
| void ed448_context(EcContext *ec_ctx); | |
| void ed448_free_context(EcContext *ec_ctx); | |
| int ed448_new_point(PointEd448 **out, | |
| const uint8_t x[56], | |
| const uint8_t y[56], | |
| size_t len, | |
| const EcContext *context); | |
| int ed448_clone(PointEd448 **P, const PointEd448 *Q); | |
| void ed448_free_point(PointEd448 *p); | |
| int ed448_cmp(const PointEd448 *p1, const PointEd448 *p2); | |
| int ed448_neg(PointEd448 *p); | |
| int ed448_get_xy(uint8_t *xb, uint8_t *yb, size_t len, const PointEd448 *p); | |
| int ed448_double(PointEd448 *p); | |
| int ed448_add(PointEd448 *P1, const PointEd448 *P2); | |
| int ed448_scalar(PointEd448 *P, const uint8_t *scalar, size_t scalar_len, uint64_t seed); | |
| """) | |
| def lib_func(ecc_obj, func_name): | |
| if ecc_obj._curve.desc == "Ed25519": | |
| result = getattr(_ed25519_lib, "ed25519_" + func_name) | |
| elif ecc_obj._curve.desc == "Ed448": | |
| result = getattr(_ed448_lib, "ed448_" + func_name) | |
| else: | |
| result = getattr(_ec_lib, "ec_ws_" + func_name) | |
| return result | |
| # | |
| # _curves is a database of curve parameters. Items are indexed by their | |
| # human-friendly name, suchas "P-256". Each item has the following fields: | |
| # - p: the prime number that defines the finite field for all modulo operations | |
| # - b: the constant in the Short Weierstrass curve equation | |
| # - order: the number of elements in the group with the generator below | |
| # - Gx the affine coordinate X of the generator point | |
| # - Gy the affine coordinate Y of the generator point | |
| # - G the generator, as an EccPoint object | |
| # - modulus_bits the minimum number of bits for encoding the modulus p | |
| # - oid an ASCII string with the registered ASN.1 Object ID | |
| # - context a raw pointer to memory holding a context for all curve operations (can be NULL) | |
| # - desc an ASCII string describing the curve | |
| # - openssh the ASCII string used in OpenSSH id files for public keys on this curve | |
| # - name the ASCII string which is also a valid key in _curves | |
| _Curve = namedtuple("_Curve", "p b order Gx Gy G modulus_bits oid context desc openssh name") | |
| _curves = {} | |
| p192_names = ["p192", "NIST P-192", "P-192", "prime192v1", "secp192r1", | |
| "nistp192"] | |
| def init_p192(): | |
| p = 0xfffffffffffffffffffffffffffffffeffffffffffffffff | |
| b = 0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1 | |
| order = 0xffffffffffffffffffffffff99def836146bc9b1b4d22831 | |
| Gx = 0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012 | |
| Gy = 0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811 | |
| p192_modulus = long_to_bytes(p, 24) | |
| p192_b = long_to_bytes(b, 24) | |
| p192_order = long_to_bytes(order, 24) | |
| ec_p192_context = VoidPointer() | |
| result = _ec_lib.ec_ws_new_context(ec_p192_context.address_of(), | |
| c_uint8_ptr(p192_modulus), | |
| c_uint8_ptr(p192_b), | |
| c_uint8_ptr(p192_order), | |
| c_size_t(len(p192_modulus)), | |
| c_ulonglong(getrandbits(64)) | |
| ) | |
| if result: | |
| raise ImportError("Error %d initializing P-192 context" % result) | |
| context = SmartPointer(ec_p192_context.get(), _ec_lib.ec_free_context) | |
| p192 = _Curve(Integer(p), | |
| Integer(b), | |
| Integer(order), | |
| Integer(Gx), | |
| Integer(Gy), | |
| None, | |
| 192, | |
| "1.2.840.10045.3.1.1", # ANSI X9.62 / SEC2 | |
| context, | |
| "NIST P-192", | |
| "ecdsa-sha2-nistp192", | |
| "p192") | |
| global p192_names | |
| _curves.update(dict.fromkeys(p192_names, p192)) | |
| init_p192() | |
| del init_p192 | |
| p224_names = ["p224", "NIST P-224", "P-224", "prime224v1", "secp224r1", | |
| "nistp224"] | |
| def init_p224(): | |
| p = 0xffffffffffffffffffffffffffffffff000000000000000000000001 | |
| b = 0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4 | |
| order = 0xffffffffffffffffffffffffffff16a2e0b8f03e13dd29455c5c2a3d | |
| Gx = 0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21 | |
| Gy = 0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34 | |
| p224_modulus = long_to_bytes(p, 28) | |
| p224_b = long_to_bytes(b, 28) | |
| p224_order = long_to_bytes(order, 28) | |
| ec_p224_context = VoidPointer() | |
| result = _ec_lib.ec_ws_new_context(ec_p224_context.address_of(), | |
| c_uint8_ptr(p224_modulus), | |
| c_uint8_ptr(p224_b), | |
| c_uint8_ptr(p224_order), | |
| c_size_t(len(p224_modulus)), | |
| c_ulonglong(getrandbits(64)) | |
| ) | |
| if result: | |
| raise ImportError("Error %d initializing P-224 context" % result) | |
| context = SmartPointer(ec_p224_context.get(), _ec_lib.ec_free_context) | |
| p224 = _Curve(Integer(p), | |
| Integer(b), | |
| Integer(order), | |
| Integer(Gx), | |
| Integer(Gy), | |
| None, | |
| 224, | |
| "1.3.132.0.33", # SEC 2 | |
| context, | |
| "NIST P-224", | |
| "ecdsa-sha2-nistp224", | |
| "p224") | |
| global p224_names | |
| _curves.update(dict.fromkeys(p224_names, p224)) | |
| init_p224() | |
| del init_p224 | |
| p256_names = ["p256", "NIST P-256", "P-256", "prime256v1", "secp256r1", | |
| "nistp256"] | |
| def init_p256(): | |
| p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff | |
| b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b | |
| order = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551 | |
| Gx = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296 | |
| Gy = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5 | |
| p256_modulus = long_to_bytes(p, 32) | |
| p256_b = long_to_bytes(b, 32) | |
| p256_order = long_to_bytes(order, 32) | |
| ec_p256_context = VoidPointer() | |
| result = _ec_lib.ec_ws_new_context(ec_p256_context.address_of(), | |
| c_uint8_ptr(p256_modulus), | |
| c_uint8_ptr(p256_b), | |
| c_uint8_ptr(p256_order), | |
| c_size_t(len(p256_modulus)), | |
| c_ulonglong(getrandbits(64)) | |
| ) | |
| if result: | |
| raise ImportError("Error %d initializing P-256 context" % result) | |
| context = SmartPointer(ec_p256_context.get(), _ec_lib.ec_free_context) | |
| p256 = _Curve(Integer(p), | |
| Integer(b), | |
| Integer(order), | |
| Integer(Gx), | |
| Integer(Gy), | |
| None, | |
| 256, | |
| "1.2.840.10045.3.1.7", # ANSI X9.62 / SEC2 | |
| context, | |
| "NIST P-256", | |
| "ecdsa-sha2-nistp256", | |
| "p256") | |
| global p256_names | |
| _curves.update(dict.fromkeys(p256_names, p256)) | |
| init_p256() | |
| del init_p256 | |
| p384_names = ["p384", "NIST P-384", "P-384", "prime384v1", "secp384r1", | |
| "nistp384"] | |
| def init_p384(): | |
| p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff | |
| b = 0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef | |
| order = 0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973 | |
| Gx = 0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760aB7 | |
| Gy = 0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5F | |
| p384_modulus = long_to_bytes(p, 48) | |
| p384_b = long_to_bytes(b, 48) | |
| p384_order = long_to_bytes(order, 48) | |
| ec_p384_context = VoidPointer() | |
| result = _ec_lib.ec_ws_new_context(ec_p384_context.address_of(), | |
| c_uint8_ptr(p384_modulus), | |
| c_uint8_ptr(p384_b), | |
| c_uint8_ptr(p384_order), | |
| c_size_t(len(p384_modulus)), | |
| c_ulonglong(getrandbits(64)) | |
| ) | |
| if result: | |
| raise ImportError("Error %d initializing P-384 context" % result) | |
| context = SmartPointer(ec_p384_context.get(), _ec_lib.ec_free_context) | |
| p384 = _Curve(Integer(p), | |
| Integer(b), | |
| Integer(order), | |
| Integer(Gx), | |
| Integer(Gy), | |
| None, | |
| 384, | |
| "1.3.132.0.34", # SEC 2 | |
| context, | |
| "NIST P-384", | |
| "ecdsa-sha2-nistp384", | |
| "p384") | |
| global p384_names | |
| _curves.update(dict.fromkeys(p384_names, p384)) | |
| init_p384() | |
| del init_p384 | |
| p521_names = ["p521", "NIST P-521", "P-521", "prime521v1", "secp521r1", | |
| "nistp521"] | |
| def init_p521(): | |
| p = 0x000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff | |
| b = 0x00000051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00 | |
| order = 0x000001fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409 | |
| Gx = 0x000000c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66 | |
| Gy = 0x0000011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650 | |
| p521_modulus = long_to_bytes(p, 66) | |
| p521_b = long_to_bytes(b, 66) | |
| p521_order = long_to_bytes(order, 66) | |
| ec_p521_context = VoidPointer() | |
| result = _ec_lib.ec_ws_new_context(ec_p521_context.address_of(), | |
| c_uint8_ptr(p521_modulus), | |
| c_uint8_ptr(p521_b), | |
| c_uint8_ptr(p521_order), | |
| c_size_t(len(p521_modulus)), | |
| c_ulonglong(getrandbits(64)) | |
| ) | |
| if result: | |
| raise ImportError("Error %d initializing P-521 context" % result) | |
| context = SmartPointer(ec_p521_context.get(), _ec_lib.ec_free_context) | |
| p521 = _Curve(Integer(p), | |
| Integer(b), | |
| Integer(order), | |
| Integer(Gx), | |
| Integer(Gy), | |
| None, | |
| 521, | |
| "1.3.132.0.35", # SEC 2 | |
| context, | |
| "NIST P-521", | |
| "ecdsa-sha2-nistp521", | |
| "p521") | |
| global p521_names | |
| _curves.update(dict.fromkeys(p521_names, p521)) | |
| init_p521() | |
| del init_p521 | |
| ed25519_names = ["ed25519", "Ed25519"] | |
| def init_ed25519(): | |
| p = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed # 2**255 - 19 | |
| order = 0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed | |
| Gx = 0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a | |
| Gy = 0x6666666666666666666666666666666666666666666666666666666666666658 | |
| ed25519 = _Curve(Integer(p), | |
| None, | |
| Integer(order), | |
| Integer(Gx), | |
| Integer(Gy), | |
| None, | |
| 255, | |
| "1.3.101.112", # RFC8410 | |
| None, | |
| "Ed25519", # Used throughout; do not change | |
| "ssh-ed25519", | |
| "ed25519") | |
| global ed25519_names | |
| _curves.update(dict.fromkeys(ed25519_names, ed25519)) | |
| init_ed25519() | |
| del init_ed25519 | |
| ed448_names = ["ed448", "Ed448"] | |
| def init_ed448(): | |
| p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff # 2**448 - 2**224 - 1 | |
| order = 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffff7cca23e9c44edb49aed63690216cc2728dc58f552378c292ab5844f3 | |
| Gx = 0x4f1970c66bed0ded221d15a622bf36da9e146570470f1767ea6de324a3d3a46412ae1af72ab66511433b80e18b00938e2626a82bc70cc05e | |
| Gy = 0x693f46716eb6bc248876203756c9c7624bea73736ca3984087789c1e05a0c2d73ad3ff1ce67c39c4fdbd132c4ed7c8ad9808795bf230fa14 | |
| ed448_context = VoidPointer() | |
| result = _ed448_lib.ed448_new_context(ed448_context.address_of()) | |
| if result: | |
| raise ImportError("Error %d initializing Ed448 context" % result) | |
| context = SmartPointer(ed448_context.get(), _ed448_lib.ed448_free_context) | |
| ed448 = _Curve(Integer(p), | |
| None, | |
| Integer(order), | |
| Integer(Gx), | |
| Integer(Gy), | |
| None, | |
| 448, | |
| "1.3.101.113", # RFC8410 | |
| context, | |
| "Ed448", # Used throughout; do not change | |
| None, | |
| "ed448") | |
| global ed448_names | |
| _curves.update(dict.fromkeys(ed448_names, ed448)) | |
| init_ed448() | |
| del init_ed448 | |
| class UnsupportedEccFeature(ValueError): | |
| pass | |
| class EccPoint(object): | |
| """A class to model a point on an Elliptic Curve. | |
| The class supports operators for: | |
| * Adding two points: ``R = S + T`` | |
| * In-place addition: ``S += T`` | |
| * Negating a point: ``R = -T`` | |
| * Comparing two points: ``if S == T: ...`` or ``if S != T: ...`` | |
| * Multiplying a point by a scalar: ``R = S*k`` | |
| * In-place multiplication by a scalar: ``T *= k`` | |
| :ivar x: The affine X-coordinate of the ECC point | |
| :vartype x: integer | |
| :ivar y: The affine Y-coordinate of the ECC point | |
| :vartype y: integer | |
| :ivar xy: The tuple with affine X- and Y- coordinates | |
| """ | |
| def __init__(self, x, y, curve="p256"): | |
| try: | |
| self._curve = _curves[curve] | |
| except KeyError: | |
| raise ValueError("Unknown curve name %s" % str(curve)) | |
| self._curve_name = curve | |
| modulus_bytes = self.size_in_bytes() | |
| xb = long_to_bytes(x, modulus_bytes) | |
| yb = long_to_bytes(y, modulus_bytes) | |
| if len(xb) != modulus_bytes or len(yb) != modulus_bytes: | |
| raise ValueError("Incorrect coordinate length") | |
| new_point = lib_func(self, "new_point") | |
| free_func = lib_func(self, "free_point") | |
| self._point = VoidPointer() | |
| try: | |
| context = self._curve.context.get() | |
| except AttributeError: | |
| context = null_pointer | |
| result = new_point(self._point.address_of(), | |
| c_uint8_ptr(xb), | |
| c_uint8_ptr(yb), | |
| c_size_t(modulus_bytes), | |
| context) | |
| if result: | |
| if result == 15: | |
| raise ValueError("The EC point does not belong to the curve") | |
| raise ValueError("Error %d while instantiating an EC point" % result) | |
| # Ensure that object disposal of this Python object will (eventually) | |
| # free the memory allocated by the raw library for the EC point | |
| self._point = SmartPointer(self._point.get(), free_func) | |
| def set(self, point): | |
| clone = lib_func(self, "clone") | |
| free_func = lib_func(self, "free_point") | |
| self._point = VoidPointer() | |
| result = clone(self._point.address_of(), | |
| point._point.get()) | |
| if result: | |
| raise ValueError("Error %d while cloning an EC point" % result) | |
| self._point = SmartPointer(self._point.get(), free_func) | |
| return self | |
| def __eq__(self, point): | |
| if not isinstance(point, EccPoint): | |
| return False | |
| cmp_func = lib_func(self, "cmp") | |
| return 0 == cmp_func(self._point.get(), point._point.get()) | |
| # Only needed for Python 2 | |
| def __ne__(self, point): | |
| return not self == point | |
| def __neg__(self): | |
| neg_func = lib_func(self, "neg") | |
| np = self.copy() | |
| result = neg_func(np._point.get()) | |
| if result: | |
| raise ValueError("Error %d while inverting an EC point" % result) | |
| return np | |
| def copy(self): | |
| """Return a copy of this point.""" | |
| x, y = self.xy | |
| np = EccPoint(x, y, self._curve_name) | |
| return np | |
| def _is_eddsa(self): | |
| return self._curve.name in ("ed25519", "ed448") | |
| def is_point_at_infinity(self): | |
| """``True`` if this is the *point-at-infinity*.""" | |
| if self._is_eddsa(): | |
| return self.x == 0 | |
| else: | |
| return self.xy == (0, 0) | |
| def point_at_infinity(self): | |
| """Return the *point-at-infinity* for the curve.""" | |
| if self._is_eddsa(): | |
| return EccPoint(0, 1, self._curve_name) | |
| else: | |
| return EccPoint(0, 0, self._curve_name) | |
| def x(self): | |
| return self.xy[0] | |
| def y(self): | |
| return self.xy[1] | |
| def xy(self): | |
| modulus_bytes = self.size_in_bytes() | |
| xb = bytearray(modulus_bytes) | |
| yb = bytearray(modulus_bytes) | |
| get_xy = lib_func(self, "get_xy") | |
| result = get_xy(c_uint8_ptr(xb), | |
| c_uint8_ptr(yb), | |
| c_size_t(modulus_bytes), | |
| self._point.get()) | |
| if result: | |
| raise ValueError("Error %d while encoding an EC point" % result) | |
| return (Integer(bytes_to_long(xb)), Integer(bytes_to_long(yb))) | |
| def size_in_bytes(self): | |
| """Size of each coordinate, in bytes.""" | |
| return (self.size_in_bits() + 7) // 8 | |
| def size_in_bits(self): | |
| """Size of each coordinate, in bits.""" | |
| return self._curve.modulus_bits | |
| def double(self): | |
| """Double this point (in-place operation). | |
| Returns: | |
| This same object (to enable chaining). | |
| """ | |
| double_func = lib_func(self, "double") | |
| result = double_func(self._point.get()) | |
| if result: | |
| raise ValueError("Error %d while doubling an EC point" % result) | |
| return self | |
| def __iadd__(self, point): | |
| """Add a second point to this one""" | |
| add_func = lib_func(self, "add") | |
| result = add_func(self._point.get(), point._point.get()) | |
| if result: | |
| if result == 16: | |
| raise ValueError("EC points are not on the same curve") | |
| raise ValueError("Error %d while adding two EC points" % result) | |
| return self | |
| def __add__(self, point): | |
| """Return a new point, the addition of this one and another""" | |
| np = self.copy() | |
| np += point | |
| return np | |
| def __imul__(self, scalar): | |
| """Multiply this point by a scalar""" | |
| scalar_func = lib_func(self, "scalar") | |
| if scalar < 0: | |
| raise ValueError("Scalar multiplication is only defined for non-negative integers") | |
| sb = long_to_bytes(scalar) | |
| result = scalar_func(self._point.get(), | |
| c_uint8_ptr(sb), | |
| c_size_t(len(sb)), | |
| c_ulonglong(getrandbits(64))) | |
| if result: | |
| raise ValueError("Error %d during scalar multiplication" % result) | |
| return self | |
| def __mul__(self, scalar): | |
| """Return a new point, the scalar product of this one""" | |
| np = self.copy() | |
| np *= scalar | |
| return np | |
| def __rmul__(self, left_hand): | |
| return self.__mul__(left_hand) | |
| # Last piece of initialization | |
| p192_G = EccPoint(_curves['p192'].Gx, _curves['p192'].Gy, "p192") | |
| p192 = _curves['p192']._replace(G=p192_G) | |
| _curves.update(dict.fromkeys(p192_names, p192)) | |
| del p192_G, p192, p192_names | |
| p224_G = EccPoint(_curves['p224'].Gx, _curves['p224'].Gy, "p224") | |
| p224 = _curves['p224']._replace(G=p224_G) | |
| _curves.update(dict.fromkeys(p224_names, p224)) | |
| del p224_G, p224, p224_names | |
| p256_G = EccPoint(_curves['p256'].Gx, _curves['p256'].Gy, "p256") | |
| p256 = _curves['p256']._replace(G=p256_G) | |
| _curves.update(dict.fromkeys(p256_names, p256)) | |
| del p256_G, p256, p256_names | |
| p384_G = EccPoint(_curves['p384'].Gx, _curves['p384'].Gy, "p384") | |
| p384 = _curves['p384']._replace(G=p384_G) | |
| _curves.update(dict.fromkeys(p384_names, p384)) | |
| del p384_G, p384, p384_names | |
| p521_G = EccPoint(_curves['p521'].Gx, _curves['p521'].Gy, "p521") | |
| p521 = _curves['p521']._replace(G=p521_G) | |
| _curves.update(dict.fromkeys(p521_names, p521)) | |
| del p521_G, p521, p521_names | |
| ed25519_G = EccPoint(_curves['Ed25519'].Gx, _curves['Ed25519'].Gy, "Ed25519") | |
| ed25519 = _curves['Ed25519']._replace(G=ed25519_G) | |
| _curves.update(dict.fromkeys(ed25519_names, ed25519)) | |
| del ed25519_G, ed25519, ed25519_names | |
| ed448_G = EccPoint(_curves['Ed448'].Gx, _curves['Ed448'].Gy, "Ed448") | |
| ed448 = _curves['Ed448']._replace(G=ed448_G) | |
| _curves.update(dict.fromkeys(ed448_names, ed448)) | |
| del ed448_G, ed448, ed448_names | |
| class EccKey(object): | |
| r"""Class defining an ECC key. | |
| Do not instantiate directly. | |
| Use :func:`generate`, :func:`construct` or :func:`import_key` instead. | |
| :ivar curve: The name of the curve as defined in the `ECC table`_. | |
| :vartype curve: string | |
| :ivar pointQ: an ECC point representating the public component. | |
| :vartype pointQ: :class:`EccPoint` | |
| :ivar d: A scalar that represents the private component | |
| in NIST P curves. It is smaller than the | |
| order of the generator point. | |
| :vartype d: integer | |
| :ivar seed: A seed that representats the private component | |
| in EdDSA curves | |
| (Ed25519, 32 bytes; Ed448, 57 bytes). | |
| :vartype seed: bytes | |
| """ | |
| def __init__(self, **kwargs): | |
| """Create a new ECC key | |
| Keywords: | |
| curve : string | |
| The name of the curve. | |
| d : integer | |
| Mandatory for a private key one NIST P curves. | |
| It must be in the range ``[1..order-1]``. | |
| seed : bytes | |
| Mandatory for a private key on the Ed25519 (32 bytes) | |
| or Ed448 (57 bytes) curve. | |
| point : EccPoint | |
| Mandatory for a public key. If provided for a private key, | |
| the implementation will NOT check whether it matches ``d``. | |
| Only one parameter among ``d``, ``seed`` or ``point`` may be used. | |
| """ | |
| kwargs_ = dict(kwargs) | |
| curve_name = kwargs_.pop("curve", None) | |
| self._d = kwargs_.pop("d", None) | |
| self._seed = kwargs_.pop("seed", None) | |
| self._point = kwargs_.pop("point", None) | |
| if curve_name is None and self._point: | |
| curve_name = self._point._curve_name | |
| if kwargs_: | |
| raise TypeError("Unknown parameters: " + str(kwargs_)) | |
| if curve_name not in _curves: | |
| raise ValueError("Unsupported curve (%s)" % curve_name) | |
| self._curve = _curves[curve_name] | |
| self.curve = self._curve.desc | |
| count = int(self._d is not None) + int(self._seed is not None) | |
| if count == 0: | |
| if self._point is None: | |
| raise ValueError("At lest one between parameters 'point', 'd' or 'seed' must be specified") | |
| return | |
| if count == 2: | |
| raise ValueError("Parameters d and seed are mutually exclusive") | |
| # NIST P curves work with d, EdDSA works with seed | |
| if not self._is_eddsa(): | |
| if self._seed is not None: | |
| raise ValueError("Parameter 'seed' can only be used with Ed25519 or Ed448") | |
| self._d = Integer(self._d) | |
| if not 1 <= self._d < self._curve.order: | |
| raise ValueError("Parameter d must be an integer smaller than the curve order") | |
| else: | |
| if self._d is not None: | |
| raise ValueError("Parameter d can only be used with NIST P curves") | |
| # RFC 8032, 5.1.5 | |
| if self._curve.name == "ed25519": | |
| if len(self._seed) != 32: | |
| raise ValueError("Parameter seed must be 32 bytes long for Ed25519") | |
| seed_hash = SHA512.new(self._seed).digest() # h | |
| self._prefix = seed_hash[32:] | |
| tmp = bytearray(seed_hash[:32]) | |
| tmp[0] &= 0xF8 | |
| tmp[31] = (tmp[31] & 0x7F) | 0x40 | |
| # RFC 8032, 5.2.5 | |
| elif self._curve.name == "ed448": | |
| if len(self._seed) != 57: | |
| raise ValueError("Parameter seed must be 57 bytes long for Ed448") | |
| seed_hash = SHAKE256.new(self._seed).read(114) # h | |
| self._prefix = seed_hash[57:] | |
| tmp = bytearray(seed_hash[:57]) | |
| tmp[0] &= 0xFC | |
| tmp[55] |= 0x80 | |
| tmp[56] = 0 | |
| self._d = Integer.from_bytes(tmp, byteorder='little') | |
| def _is_eddsa(self): | |
| return self._curve.desc in ("Ed25519", "Ed448") | |
| def __eq__(self, other): | |
| if not isinstance(other, EccKey): | |
| return False | |
| if other.has_private() != self.has_private(): | |
| return False | |
| return other.pointQ == self.pointQ | |
| def __repr__(self): | |
| if self.has_private(): | |
| if self._is_eddsa(): | |
| extra = ", seed=%s" % tostr(binascii.hexlify(self._seed)) | |
| else: | |
| extra = ", d=%d" % int(self._d) | |
| else: | |
| extra = "" | |
| x, y = self.pointQ.xy | |
| return "EccKey(curve='%s', point_x=%d, point_y=%d%s)" % (self._curve.desc, x, y, extra) | |
| def has_private(self): | |
| """``True`` if this key can be used for making signatures or decrypting data.""" | |
| return self._d is not None | |
| # ECDSA | |
| def _sign(self, z, k): | |
| assert 0 < k < self._curve.order | |
| order = self._curve.order | |
| blind = Integer.random_range(min_inclusive=1, | |
| max_exclusive=order) | |
| blind_d = self._d * blind | |
| inv_blind_k = (blind * k).inverse(order) | |
| r = (self._curve.G * k).x % order | |
| s = inv_blind_k * (blind * z + blind_d * r) % order | |
| return (r, s) | |
| # ECDSA | |
| def _verify(self, z, rs): | |
| order = self._curve.order | |
| sinv = rs[1].inverse(order) | |
| point1 = self._curve.G * ((sinv * z) % order) | |
| point2 = self.pointQ * ((sinv * rs[0]) % order) | |
| return (point1 + point2).x == rs[0] | |
| def d(self): | |
| if not self.has_private(): | |
| raise ValueError("This is not a private ECC key") | |
| return self._d | |
| def seed(self): | |
| if not self.has_private(): | |
| raise ValueError("This is not a private ECC key") | |
| return self._seed | |
| def pointQ(self): | |
| if self._point is None: | |
| self._point = self._curve.G * self._d | |
| return self._point | |
| def public_key(self): | |
| """A matching ECC public key. | |
| Returns: | |
| a new :class:`EccKey` object | |
| """ | |
| return EccKey(curve=self._curve.desc, point=self.pointQ) | |
| def _export_SEC1(self, compress): | |
| if self._is_eddsa(): | |
| raise ValueError("SEC1 format is unsupported for EdDSA curves") | |
| # See 2.2 in RFC5480 and 2.3.3 in SEC1 | |
| # | |
| # The first byte is: | |
| # - 0x02: compressed, only X-coordinate, Y-coordinate is even | |
| # - 0x03: compressed, only X-coordinate, Y-coordinate is odd | |
| # - 0x04: uncompressed, X-coordinate is followed by Y-coordinate | |
| # | |
| # PAI is in theory encoded as 0x00. | |
| modulus_bytes = self.pointQ.size_in_bytes() | |
| if compress: | |
| if self.pointQ.y.is_odd(): | |
| first_byte = b'\x03' | |
| else: | |
| first_byte = b'\x02' | |
| public_key = (first_byte + | |
| self.pointQ.x.to_bytes(modulus_bytes)) | |
| else: | |
| public_key = (b'\x04' + | |
| self.pointQ.x.to_bytes(modulus_bytes) + | |
| self.pointQ.y.to_bytes(modulus_bytes)) | |
| return public_key | |
| def _export_eddsa(self): | |
| x, y = self.pointQ.xy | |
| if self._curve.name == "ed25519": | |
| result = bytearray(y.to_bytes(32, byteorder='little')) | |
| result[31] = ((x & 1) << 7) | result[31] | |
| elif self._curve.name == "ed448": | |
| result = bytearray(y.to_bytes(57, byteorder='little')) | |
| result[56] = (x & 1) << 7 | |
| else: | |
| raise ValueError("Not an EdDSA key to export") | |
| return bytes(result) | |
| def _export_subjectPublicKeyInfo(self, compress): | |
| if self._is_eddsa(): | |
| oid = self._curve.oid | |
| public_key = self._export_eddsa() | |
| params = None | |
| else: | |
| oid = "1.2.840.10045.2.1" # unrestricted | |
| public_key = self._export_SEC1(compress) | |
| params = DerObjectId(self._curve.oid) | |
| return _create_subject_public_key_info(oid, | |
| public_key, | |
| params) | |
| def _export_rfc5915_private_der(self, include_ec_params=True): | |
| assert self.has_private() | |
| # ECPrivateKey ::= SEQUENCE { | |
| # version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), | |
| # privateKey OCTET STRING, | |
| # parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, | |
| # publicKey [1] BIT STRING OPTIONAL | |
| # } | |
| # Public key - uncompressed form | |
| modulus_bytes = self.pointQ.size_in_bytes() | |
| public_key = (b'\x04' + | |
| self.pointQ.x.to_bytes(modulus_bytes) + | |
| self.pointQ.y.to_bytes(modulus_bytes)) | |
| seq = [1, | |
| DerOctetString(self.d.to_bytes(modulus_bytes)), | |
| DerObjectId(self._curve.oid, explicit=0), | |
| DerBitString(public_key, explicit=1)] | |
| if not include_ec_params: | |
| del seq[2] | |
| return DerSequence(seq).encode() | |
| def _export_pkcs8(self, **kwargs): | |
| from Crypto.IO import PKCS8 | |
| if kwargs.get('passphrase', None) is not None and 'protection' not in kwargs: | |
| raise ValueError("At least the 'protection' parameter must be present") | |
| if self._is_eddsa(): | |
| oid = self._curve.oid | |
| private_key = DerOctetString(self._seed).encode() | |
| params = None | |
| else: | |
| oid = "1.2.840.10045.2.1" # unrestricted | |
| private_key = self._export_rfc5915_private_der(include_ec_params=False) | |
| params = DerObjectId(self._curve.oid) | |
| result = PKCS8.wrap(private_key, | |
| oid, | |
| key_params=params, | |
| **kwargs) | |
| return result | |
| def _export_public_pem(self, compress): | |
| from Crypto.IO import PEM | |
| encoded_der = self._export_subjectPublicKeyInfo(compress) | |
| return PEM.encode(encoded_der, "PUBLIC KEY") | |
| def _export_private_pem(self, passphrase, **kwargs): | |
| from Crypto.IO import PEM | |
| encoded_der = self._export_rfc5915_private_der() | |
| return PEM.encode(encoded_der, "EC PRIVATE KEY", passphrase, **kwargs) | |
| def _export_private_clear_pkcs8_in_clear_pem(self): | |
| from Crypto.IO import PEM | |
| encoded_der = self._export_pkcs8() | |
| return PEM.encode(encoded_der, "PRIVATE KEY") | |
| def _export_private_encrypted_pkcs8_in_clear_pem(self, passphrase, **kwargs): | |
| from Crypto.IO import PEM | |
| assert passphrase | |
| if 'protection' not in kwargs: | |
| raise ValueError("At least the 'protection' parameter should be present") | |
| encoded_der = self._export_pkcs8(passphrase=passphrase, **kwargs) | |
| return PEM.encode(encoded_der, "ENCRYPTED PRIVATE KEY") | |
| def _export_openssh(self, compress): | |
| if self.has_private(): | |
| raise ValueError("Cannot export OpenSSH private keys") | |
| desc = self._curve.openssh | |
| if desc is None: | |
| raise ValueError("Cannot export %s keys as OpenSSH" % self._curve.name) | |
| elif desc == "ssh-ed25519": | |
| public_key = self._export_eddsa() | |
| comps = (tobytes(desc), tobytes(public_key)) | |
| else: | |
| modulus_bytes = self.pointQ.size_in_bytes() | |
| if compress: | |
| first_byte = 2 + self.pointQ.y.is_odd() | |
| public_key = (bchr(first_byte) + | |
| self.pointQ.x.to_bytes(modulus_bytes)) | |
| else: | |
| public_key = (b'\x04' + | |
| self.pointQ.x.to_bytes(modulus_bytes) + | |
| self.pointQ.y.to_bytes(modulus_bytes)) | |
| middle = desc.split("-")[2] | |
| comps = (tobytes(desc), tobytes(middle), public_key) | |
| blob = b"".join([struct.pack(">I", len(x)) + x for x in comps]) | |
| return desc + " " + tostr(binascii.b2a_base64(blob)) | |
| def export_key(self, **kwargs): | |
| """Export this ECC key. | |
| Args: | |
| format (string): | |
| The output format: | |
| - ``'DER'``. The key will be encoded in ASN.1 DER format (binary). | |
| For a public key, the ASN.1 ``subjectPublicKeyInfo`` structure | |
| defined in `RFC5480`_ will be used. | |
| For a private key, the ASN.1 ``ECPrivateKey`` structure defined | |
| in `RFC5915`_ is used instead (possibly within a PKCS#8 envelope, | |
| see the ``use_pkcs8`` flag below). | |
| - ``'PEM'``. The key will be encoded in a PEM_ envelope (ASCII). | |
| - ``'OpenSSH'``. The key will be encoded in the OpenSSH_ format | |
| (ASCII, public keys only). | |
| - ``'SEC1'``. The public key (i.e., the EC point) will be encoded | |
| into ``bytes`` according to Section 2.3.3 of `SEC1`_ | |
| (which is a subset of the older X9.62 ITU standard). | |
| Only for NIST P-curves. | |
| - ``'raw'``. The public key will be encoded as ``bytes``, | |
| without any metadata. | |
| * For NIST P-curves: equivalent to ``'SEC1'``. | |
| * For EdDSA curves: ``bytes`` in the format defined in `RFC8032`_. | |
| passphrase (bytes or string): | |
| (*Private keys only*) The passphrase to protect the | |
| private key. | |
| use_pkcs8 (boolean): | |
| (*Private keys only*) | |
| If ``True`` (default and recommended), the `PKCS#8`_ representation | |
| will be used. It must be ``True`` for EdDSA curves. | |
| If ``False`` and a passphrase is present, the obsolete PEM | |
| encryption will be used. | |
| protection (string): | |
| When a private key is exported with password-protection | |
| and PKCS#8 (both ``DER`` and ``PEM`` formats), this parameter MUST be | |
| present, | |
| For all possible protection schemes, | |
| refer to :ref:`the encryption parameters of PKCS#8<enc_params>`. | |
| It is recommended to use ``'PBKDF2WithHMAC-SHA5126AndAES128-CBC'``. | |
| compress (boolean): | |
| If ``True``, the method returns a more compact representation | |
| of the public key, with the X-coordinate only. | |
| If ``False`` (default), the method returns the full public key. | |
| This parameter is ignored for EdDSA curves, as compression is | |
| mandatory. | |
| prot_params (dict): | |
| When a private key is exported with password-protection | |
| and PKCS#8 (both ``DER`` and ``PEM`` formats), this dictionary | |
| contains the parameters to use to derive the encryption key | |
| from the passphrase. | |
| For all possible values, | |
| refer to :ref:`the encryption parameters of PKCS#8<enc_params>`. | |
| The recommendation is to use ``{'iteration_count':21000}`` for PBKDF2, | |
| and ``{'iteration_count':131072}`` for scrypt. | |
| .. warning:: | |
| If you don't provide a passphrase, the private key will be | |
| exported in the clear! | |
| .. note:: | |
| When exporting a private key with password-protection and `PKCS#8`_ | |
| (both ``DER`` and ``PEM`` formats), any extra parameters | |
| to ``export_key()`` will be passed to :mod:`Crypto.IO.PKCS8`. | |
| .. _PEM: http://www.ietf.org/rfc/rfc1421.txt | |
| .. _`PEM encryption`: http://www.ietf.org/rfc/rfc1423.txt | |
| .. _OpenSSH: http://www.openssh.com/txt/rfc5656.txt | |
| .. _RFC5480: https://tools.ietf.org/html/rfc5480 | |
| .. _SEC1: https://www.secg.org/sec1-v2.pdf | |
| Returns: | |
| A multi-line string (for ``'PEM'`` and ``'OpenSSH'``) or | |
| ``bytes`` (for ``'DER'``, ``'SEC1'``, and ``'raw'``) with the encoded key. | |
| """ | |
| args = kwargs.copy() | |
| ext_format = args.pop("format") | |
| if ext_format not in ("PEM", "DER", "OpenSSH", "SEC1", "raw"): | |
| raise ValueError("Unknown format '%s'" % ext_format) | |
| compress = args.pop("compress", False) | |
| if self.has_private(): | |
| passphrase = args.pop("passphrase", None) | |
| if is_string(passphrase): | |
| passphrase = tobytes(passphrase) | |
| if not passphrase: | |
| raise ValueError("Empty passphrase") | |
| use_pkcs8 = args.pop("use_pkcs8", True) | |
| if not use_pkcs8: | |
| if self._is_eddsa(): | |
| raise ValueError("'pkcs8' must be True for EdDSA curves") | |
| if 'protection' in args: | |
| raise ValueError("'protection' is only supported for PKCS#8") | |
| if ext_format == "PEM": | |
| if use_pkcs8: | |
| if passphrase: | |
| return self._export_private_encrypted_pkcs8_in_clear_pem(passphrase, **args) | |
| else: | |
| return self._export_private_clear_pkcs8_in_clear_pem() | |
| else: | |
| return self._export_private_pem(passphrase, **args) | |
| elif ext_format == "DER": | |
| # DER | |
| if passphrase and not use_pkcs8: | |
| raise ValueError("Private keys can only be encrpyted with DER using PKCS#8") | |
| if use_pkcs8: | |
| return self._export_pkcs8(passphrase=passphrase, **args) | |
| else: | |
| return self._export_rfc5915_private_der() | |
| else: | |
| raise ValueError("Private keys cannot be exported " | |
| "in the '%s' format" % ext_format) | |
| else: # Public key | |
| if args: | |
| raise ValueError("Unexpected parameters: '%s'" % args) | |
| if ext_format == "PEM": | |
| return self._export_public_pem(compress) | |
| elif ext_format == "DER": | |
| return self._export_subjectPublicKeyInfo(compress) | |
| elif ext_format == "SEC1": | |
| return self._export_SEC1(compress) | |
| elif ext_format == "raw": | |
| if self._curve.name in ('ed25519', 'ed448'): | |
| return self._export_eddsa() | |
| else: | |
| return self._export_SEC1(compress) | |
| else: | |
| return self._export_openssh(compress) | |
| def generate(**kwargs): | |
| """Generate a new private key on the given curve. | |
| Args: | |
| curve (string): | |
| Mandatory. It must be a curve name defined in the `ECC table`_. | |
| randfunc (callable): | |
| Optional. The RNG to read randomness from. | |
| If ``None``, :func:`Crypto.Random.get_random_bytes` is used. | |
| """ | |
| curve_name = kwargs.pop("curve") | |
| curve = _curves[curve_name] | |
| randfunc = kwargs.pop("randfunc", get_random_bytes) | |
| if kwargs: | |
| raise TypeError("Unknown parameters: " + str(kwargs)) | |
| if _curves[curve_name].name == "ed25519": | |
| seed = randfunc(32) | |
| new_key = EccKey(curve=curve_name, seed=seed) | |
| elif _curves[curve_name].name == "ed448": | |
| seed = randfunc(57) | |
| new_key = EccKey(curve=curve_name, seed=seed) | |
| else: | |
| d = Integer.random_range(min_inclusive=1, | |
| max_exclusive=curve.order, | |
| randfunc=randfunc) | |
| new_key = EccKey(curve=curve_name, d=d) | |
| return new_key | |
| def construct(**kwargs): | |
| """Build a new ECC key (private or public) starting | |
| from some base components. | |
| In most cases, you will already have an existing key | |
| which you can read in with :func:`import_key` instead | |
| of this function. | |
| Args: | |
| curve (string): | |
| Mandatory. The name of the elliptic curve, as defined in the `ECC table`_. | |
| d (integer): | |
| Mandatory for a private key and a NIST P-curve (e.g., P-256): | |
| the integer in the range ``[1..order-1]`` that represents the key. | |
| seed (bytes): | |
| Mandatory for a private key and an EdDSA curve. | |
| It must be 32 bytes for Ed25519, and 57 bytes for Ed448. | |
| point_x (integer): | |
| Mandatory for a public key: the X coordinate (affine) of the ECC point. | |
| point_y (integer): | |
| Mandatory for a public key: the Y coordinate (affine) of the ECC point. | |
| Returns: | |
| :class:`EccKey` : a new ECC key object | |
| """ | |
| curve_name = kwargs["curve"] | |
| curve = _curves[curve_name] | |
| point_x = kwargs.pop("point_x", None) | |
| point_y = kwargs.pop("point_y", None) | |
| if "point" in kwargs: | |
| raise TypeError("Unknown keyword: point") | |
| if None not in (point_x, point_y): | |
| # ValueError is raised if the point is not on the curve | |
| kwargs["point"] = EccPoint(point_x, point_y, curve_name) | |
| new_key = EccKey(**kwargs) | |
| # Validate that the private key matches the public one | |
| # because EccKey will not do that automatically | |
| if new_key.has_private() and 'point' in kwargs: | |
| pub_key = curve.G * new_key.d | |
| if pub_key.xy != (point_x, point_y): | |
| raise ValueError("Private and public ECC keys do not match") | |
| return new_key | |
| def _import_public_der(ec_point, curve_oid=None, curve_name=None): | |
| """Convert an encoded EC point into an EccKey object | |
| ec_point: byte string with the EC point (SEC1-encoded) | |
| curve_oid: string with the name the curve | |
| curve_name: string with the OID of the curve | |
| Either curve_id or curve_name must be specified | |
| """ | |
| for _curve_name, curve in _curves.items(): | |
| if curve_oid and curve.oid == curve_oid: | |
| break | |
| if curve_name == _curve_name: | |
| break | |
| else: | |
| if curve_oid: | |
| raise UnsupportedEccFeature("Unsupported ECC curve (OID: %s)" % curve_oid) | |
| else: | |
| raise UnsupportedEccFeature("Unsupported ECC curve (%s)" % curve_name) | |
| # See 2.2 in RFC5480 and 2.3.3 in SEC1 | |
| # The first byte is: | |
| # - 0x02: compressed, only X-coordinate, Y-coordinate is even | |
| # - 0x03: compressed, only X-coordinate, Y-coordinate is odd | |
| # - 0x04: uncompressed, X-coordinate is followed by Y-coordinate | |
| # | |
| # PAI is in theory encoded as 0x00. | |
| modulus_bytes = curve.p.size_in_bytes() | |
| point_type = bord(ec_point[0]) | |
| # Uncompressed point | |
| if point_type == 0x04: | |
| if len(ec_point) != (1 + 2 * modulus_bytes): | |
| raise ValueError("Incorrect EC point length") | |
| x = Integer.from_bytes(ec_point[1:modulus_bytes+1]) | |
| y = Integer.from_bytes(ec_point[modulus_bytes+1:]) | |
| # Compressed point | |
| elif point_type in (0x02, 0x03): | |
| if len(ec_point) != (1 + modulus_bytes): | |
| raise ValueError("Incorrect EC point length") | |
| x = Integer.from_bytes(ec_point[1:]) | |
| # Right now, we only support Short Weierstrass curves | |
| y = (x**3 - x*3 + curve.b).sqrt(curve.p) | |
| if point_type == 0x02 and y.is_odd(): | |
| y = curve.p - y | |
| if point_type == 0x03 and y.is_even(): | |
| y = curve.p - y | |
| else: | |
| raise ValueError("Incorrect EC point encoding") | |
| return construct(curve=_curve_name, point_x=x, point_y=y) | |
| def _import_subjectPublicKeyInfo(encoded, *kwargs): | |
| """Convert a subjectPublicKeyInfo into an EccKey object""" | |
| # See RFC5480 | |
| # Parse the generic subjectPublicKeyInfo structure | |
| oid, ec_point, params = _expand_subject_public_key_info(encoded) | |
| nist_p_oids = ( | |
| "1.2.840.10045.2.1", # id-ecPublicKey (unrestricted) | |
| "1.3.132.1.12", # id-ecDH | |
| "1.3.132.1.13" # id-ecMQV | |
| ) | |
| eddsa_oids = { | |
| "1.3.101.112": ("Ed25519", _import_ed25519_public_key), # id-Ed25519 | |
| "1.3.101.113": ("Ed448", _import_ed448_public_key) # id-Ed448 | |
| } | |
| if oid in nist_p_oids: | |
| # See RFC5480 | |
| # Parameters are mandatory and encoded as ECParameters | |
| # ECParameters ::= CHOICE { | |
| # namedCurve OBJECT IDENTIFIER | |
| # -- implicitCurve NULL | |
| # -- specifiedCurve SpecifiedECDomain | |
| # } | |
| # implicitCurve and specifiedCurve are not supported (as per RFC) | |
| if not params: | |
| raise ValueError("Missing ECC parameters for ECC OID %s" % oid) | |
| try: | |
| curve_oid = DerObjectId().decode(params).value | |
| except ValueError: | |
| raise ValueError("Error decoding namedCurve") | |
| # ECPoint ::= OCTET STRING | |
| return _import_public_der(ec_point, curve_oid=curve_oid) | |
| elif oid in eddsa_oids: | |
| # See RFC8410 | |
| curve_name, import_eddsa_public_key = eddsa_oids[oid] | |
| # Parameters must be absent | |
| if params: | |
| raise ValueError("Unexpected ECC parameters for ECC OID %s" % oid) | |
| x, y = import_eddsa_public_key(ec_point) | |
| return construct(point_x=x, point_y=y, curve=curve_name) | |
| else: | |
| raise UnsupportedEccFeature("Unsupported ECC OID: %s" % oid) | |
| def _import_rfc5915_der(encoded, passphrase, curve_oid=None): | |
| # See RFC5915 https://tools.ietf.org/html/rfc5915 | |
| # | |
| # ECPrivateKey ::= SEQUENCE { | |
| # version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1), | |
| # privateKey OCTET STRING, | |
| # parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, | |
| # publicKey [1] BIT STRING OPTIONAL | |
| # } | |
| private_key = DerSequence().decode(encoded, nr_elements=(3, 4)) | |
| if private_key[0] != 1: | |
| raise ValueError("Incorrect ECC private key version") | |
| try: | |
| parameters = DerObjectId(explicit=0).decode(private_key[2]).value | |
| if curve_oid is not None and parameters != curve_oid: | |
| raise ValueError("Curve mismatch") | |
| curve_oid = parameters | |
| except ValueError: | |
| pass | |
| if curve_oid is None: | |
| raise ValueError("No curve found") | |
| for curve_name, curve in _curves.items(): | |
| if curve.oid == curve_oid: | |
| break | |
| else: | |
| raise UnsupportedEccFeature("Unsupported ECC curve (OID: %s)" % curve_oid) | |
| scalar_bytes = DerOctetString().decode(private_key[1]).payload | |
| modulus_bytes = curve.p.size_in_bytes() | |
| if len(scalar_bytes) != modulus_bytes: | |
| raise ValueError("Private key is too small") | |
| d = Integer.from_bytes(scalar_bytes) | |
| # Decode public key (if any) | |
| if len(private_key) > 2: | |
| public_key_enc = DerBitString(explicit=1).decode(private_key[-1]).value | |
| public_key = _import_public_der(public_key_enc, curve_oid=curve_oid) | |
| point_x = public_key.pointQ.x | |
| point_y = public_key.pointQ.y | |
| else: | |
| point_x = point_y = None | |
| return construct(curve=curve_name, d=d, point_x=point_x, point_y=point_y) | |
| def _import_pkcs8(encoded, passphrase): | |
| from Crypto.IO import PKCS8 | |
| algo_oid, private_key, params = PKCS8.unwrap(encoded, passphrase) | |
| nist_p_oids = ( | |
| "1.2.840.10045.2.1", # id-ecPublicKey (unrestricted) | |
| "1.3.132.1.12", # id-ecDH | |
| "1.3.132.1.13" # id-ecMQV | |
| ) | |
| eddsa_oids = { | |
| "1.3.101.112": "Ed25519", # id-Ed25519 | |
| "1.3.101.113": "Ed448", # id-Ed448 | |
| } | |
| if algo_oid in nist_p_oids: | |
| curve_oid = DerObjectId().decode(params).value | |
| return _import_rfc5915_der(private_key, passphrase, curve_oid) | |
| elif algo_oid in eddsa_oids: | |
| if params is not None: | |
| raise ValueError("EdDSA ECC private key must not have parameters") | |
| curve_oid = None | |
| seed = DerOctetString().decode(private_key).payload | |
| return construct(curve=eddsa_oids[algo_oid], seed=seed) | |
| else: | |
| raise UnsupportedEccFeature("Unsupported ECC purpose (OID: %s)" % algo_oid) | |
| def _import_x509_cert(encoded, *kwargs): | |
| sp_info = _extract_subject_public_key_info(encoded) | |
| return _import_subjectPublicKeyInfo(sp_info) | |
| def _import_der(encoded, passphrase): | |
| try: | |
| return _import_subjectPublicKeyInfo(encoded, passphrase) | |
| except UnsupportedEccFeature as err: | |
| raise err | |
| except (ValueError, TypeError, IndexError): | |
| pass | |
| try: | |
| return _import_x509_cert(encoded, passphrase) | |
| except UnsupportedEccFeature as err: | |
| raise err | |
| except (ValueError, TypeError, IndexError): | |
| pass | |
| try: | |
| return _import_rfc5915_der(encoded, passphrase) | |
| except UnsupportedEccFeature as err: | |
| raise err | |
| except (ValueError, TypeError, IndexError): | |
| pass | |
| try: | |
| return _import_pkcs8(encoded, passphrase) | |
| except UnsupportedEccFeature as err: | |
| raise err | |
| except (ValueError, TypeError, IndexError): | |
| pass | |
| raise ValueError("Not an ECC DER key") | |
| def _import_openssh_public(encoded): | |
| parts = encoded.split(b' ') | |
| if len(parts) not in (2, 3): | |
| raise ValueError("Not an openssh public key") | |
| try: | |
| keystring = binascii.a2b_base64(parts[1]) | |
| keyparts = [] | |
| while len(keystring) > 4: | |
| lk = struct.unpack(">I", keystring[:4])[0] | |
| keyparts.append(keystring[4:4 + lk]) | |
| keystring = keystring[4 + lk:] | |
| if parts[0] != keyparts[0]: | |
| raise ValueError("Mismatch in openssh public key") | |
| # NIST P curves | |
| if parts[0].startswith(b"ecdsa-sha2-"): | |
| for curve_name, curve in _curves.items(): | |
| if curve.openssh is None: | |
| continue | |
| if not curve.openssh.startswith("ecdsa-sha2"): | |
| continue | |
| middle = tobytes(curve.openssh.split("-")[2]) | |
| if keyparts[1] == middle: | |
| break | |
| else: | |
| raise ValueError("Unsupported ECC curve: " + middle) | |
| ecc_key = _import_public_der(keyparts[2], curve_oid=curve.oid) | |
| # EdDSA | |
| elif parts[0] == b"ssh-ed25519": | |
| x, y = _import_ed25519_public_key(keyparts[1]) | |
| ecc_key = construct(curve="Ed25519", point_x=x, point_y=y) | |
| else: | |
| raise ValueError("Unsupported SSH key type: " + parts[0]) | |
| except (IndexError, TypeError, binascii.Error): | |
| raise ValueError("Error parsing SSH key type: " + parts[0]) | |
| return ecc_key | |
| def _import_openssh_private_ecc(data, password): | |
| from ._openssh import (import_openssh_private_generic, | |
| read_bytes, read_string, check_padding) | |
| key_type, decrypted = import_openssh_private_generic(data, password) | |
| eddsa_keys = { | |
| "ssh-ed25519": ("Ed25519", _import_ed25519_public_key, 32), | |
| } | |
| # https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent-04 | |
| if key_type.startswith("ecdsa-sha2"): | |
| ecdsa_curve_name, decrypted = read_string(decrypted) | |
| if ecdsa_curve_name not in _curves: | |
| raise UnsupportedEccFeature("Unsupported ECC curve %s" % ecdsa_curve_name) | |
| curve = _curves[ecdsa_curve_name] | |
| modulus_bytes = (curve.modulus_bits + 7) // 8 | |
| public_key, decrypted = read_bytes(decrypted) | |
| if bord(public_key[0]) != 4: | |
| raise ValueError("Only uncompressed OpenSSH EC keys are supported") | |
| if len(public_key) != 2 * modulus_bytes + 1: | |
| raise ValueError("Incorrect public key length") | |
| point_x = Integer.from_bytes(public_key[1:1+modulus_bytes]) | |
| point_y = Integer.from_bytes(public_key[1+modulus_bytes:]) | |
| private_key, decrypted = read_bytes(decrypted) | |
| d = Integer.from_bytes(private_key) | |
| params = {'d': d, 'curve': ecdsa_curve_name} | |
| elif key_type in eddsa_keys: | |
| curve_name, import_eddsa_public_key, seed_len = eddsa_keys[key_type] | |
| public_key, decrypted = read_bytes(decrypted) | |
| point_x, point_y = import_eddsa_public_key(public_key) | |
| private_public_key, decrypted = read_bytes(decrypted) | |
| seed = private_public_key[:seed_len] | |
| params = {'seed': seed, 'curve': curve_name} | |
| else: | |
| raise ValueError("Unsupport SSH agent key type:" + key_type) | |
| _, padded = read_string(decrypted) # Comment | |
| check_padding(padded) | |
| return construct(point_x=point_x, point_y=point_y, **params) | |
| def _import_ed25519_public_key(encoded): | |
| """Import an Ed25519 ECC public key, encoded as raw bytes as described | |
| in RFC8032_. | |
| Args: | |
| encoded (bytes): | |
| The Ed25519 public key to import. It must be 32 bytes long. | |
| Returns: | |
| :class:`EccKey` : a new ECC key object | |
| Raises: | |
| ValueError: when the given key cannot be parsed. | |
| .. _RFC8032: https://datatracker.ietf.org/doc/html/rfc8032 | |
| """ | |
| if len(encoded) != 32: | |
| raise ValueError("Incorrect length. Only Ed25519 public keys are supported.") | |
| p = Integer(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed) # 2**255 - 19 | |
| d = 37095705934669439343138083508754565189542113879843219016388785533085940283555 | |
| y = bytearray(encoded) | |
| x_lsb = y[31] >> 7 | |
| y[31] &= 0x7F | |
| point_y = Integer.from_bytes(y, byteorder='little') | |
| if point_y >= p: | |
| raise ValueError("Invalid Ed25519 key (y)") | |
| if point_y == 1: | |
| return 0, 1 | |
| u = (point_y**2 - 1) % p | |
| v = ((point_y**2 % p) * d + 1) % p | |
| try: | |
| v_inv = v.inverse(p) | |
| x2 = (u * v_inv) % p | |
| point_x = Integer._tonelli_shanks(x2, p) | |
| if (point_x & 1) != x_lsb: | |
| point_x = p - point_x | |
| except ValueError: | |
| raise ValueError("Invalid Ed25519 public key") | |
| return point_x, point_y | |
| def _import_ed448_public_key(encoded): | |
| """Import an Ed448 ECC public key, encoded as raw bytes as described | |
| in RFC8032_. | |
| Args: | |
| encoded (bytes): | |
| The Ed448 public key to import. It must be 57 bytes long. | |
| Returns: | |
| :class:`EccKey` : a new ECC key object | |
| Raises: | |
| ValueError: when the given key cannot be parsed. | |
| .. _RFC8032: https://datatracker.ietf.org/doc/html/rfc8032 | |
| """ | |
| if len(encoded) != 57: | |
| raise ValueError("Incorrect length. Only Ed448 public keys are supported.") | |
| p = Integer(0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffff) # 2**448 - 2**224 - 1 | |
| d = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffffffffffffffffffffffffffffffffffffffffffffffff6756 | |
| y = encoded[:56] | |
| x_lsb = bord(encoded[56]) >> 7 | |
| point_y = Integer.from_bytes(y, byteorder='little') | |
| if point_y >= p: | |
| raise ValueError("Invalid Ed448 key (y)") | |
| if point_y == 1: | |
| return 0, 1 | |
| u = (point_y**2 - 1) % p | |
| v = ((point_y**2 % p) * d - 1) % p | |
| try: | |
| v_inv = v.inverse(p) | |
| x2 = (u * v_inv) % p | |
| point_x = Integer._tonelli_shanks(x2, p) | |
| if (point_x & 1) != x_lsb: | |
| point_x = p - point_x | |
| except ValueError: | |
| raise ValueError("Invalid Ed448 public key") | |
| return point_x, point_y | |
| def import_key(encoded, passphrase=None, curve_name=None): | |
| """Import an ECC key (public or private). | |
| Args: | |
| encoded (bytes or multi-line string): | |
| The ECC key to import. | |
| The function will try to automatically detect the right format. | |
| Supported formats for an ECC **public** key: | |
| * X.509 certificate: binary (DER) or ASCII (PEM). | |
| * X.509 ``subjectPublicKeyInfo``: binary (DER) or ASCII (PEM). | |
| * SEC1_ (or X9.62), as ``bytes``. NIST P curves only. | |
| You must also provide the ``curve_name`` (with a value from the `ECC table`_) | |
| * OpenSSH line, defined in RFC5656_ and RFC8709_ (ASCII). | |
| This is normally the content of files like ``~/.ssh/id_ecdsa.pub``. | |
| Supported formats for an ECC **private** key: | |
| * A binary ``ECPrivateKey`` structure, as defined in `RFC5915`_ (DER). | |
| NIST P curves only. | |
| * A `PKCS#8`_ structure (or the more recent Asymmetric Key Package, RFC5958_): binary (DER) or ASCII (PEM). | |
| * `OpenSSH 6.5`_ and newer versions (ASCII). | |
| Private keys can be in the clear or password-protected. | |
| For details about the PEM encoding, see `RFC1421`_/`RFC1423`_. | |
| passphrase (byte string): | |
| The passphrase to use for decrypting a private key. | |
| Encryption may be applied protected at the PEM level (not recommended) | |
| or at the PKCS#8 level (recommended). | |
| This parameter is ignored if the key in input is not encrypted. | |
| curve_name (string): | |
| For a SEC1 encoding only. This is the name of the curve, | |
| as defined in the `ECC table`_. | |
| .. note:: | |
| To import EdDSA private and public keys, when encoded as raw ``bytes``, use: | |
| * :func:`Crypto.Signature.eddsa.import_public_key`, or | |
| * :func:`Crypto.Signature.eddsa.import_private_key`. | |
| Returns: | |
| :class:`EccKey` : a new ECC key object | |
| Raises: | |
| ValueError: when the given key cannot be parsed (possibly because | |
| the pass phrase is wrong). | |
| .. _RFC1421: https://datatracker.ietf.org/doc/html/rfc1421 | |
| .. _RFC1423: https://datatracker.ietf.org/doc/html/rfc1423 | |
| .. _RFC5915: https://datatracker.ietf.org/doc/html/rfc5915 | |
| .. _RFC5656: https://datatracker.ietf.org/doc/html/rfc5656 | |
| .. _RFC8709: https://datatracker.ietf.org/doc/html/rfc8709 | |
| .. _RFC5958: https://datatracker.ietf.org/doc/html/rfc5958 | |
| .. _`PKCS#8`: https://datatracker.ietf.org/doc/html/rfc5208 | |
| .. _`OpenSSH 6.5`: https://flak.tedunangst.com/post/new-openssh-key-format-and-bcrypt-pbkdf | |
| .. _SEC1: https://www.secg.org/sec1-v2.pdf | |
| """ | |
| from Crypto.IO import PEM | |
| encoded = tobytes(encoded) | |
| if passphrase is not None: | |
| passphrase = tobytes(passphrase) | |
| # PEM | |
| if encoded.startswith(b'-----BEGIN OPENSSH PRIVATE KEY'): | |
| text_encoded = tostr(encoded) | |
| openssh_encoded, marker, enc_flag = PEM.decode(text_encoded, passphrase) | |
| result = _import_openssh_private_ecc(openssh_encoded, passphrase) | |
| return result | |
| elif encoded.startswith(b'-----'): | |
| text_encoded = tostr(encoded) | |
| # Remove any EC PARAMETERS section | |
| # Ignore its content because the curve type must be already given in the key | |
| ecparams_start = "-----BEGIN EC PARAMETERS-----" | |
| ecparams_end = "-----END EC PARAMETERS-----" | |
| text_encoded = re.sub(ecparams_start + ".*?" + ecparams_end, "", | |
| text_encoded, | |
| flags=re.DOTALL) | |
| der_encoded, marker, enc_flag = PEM.decode(text_encoded, passphrase) | |
| if enc_flag: | |
| passphrase = None | |
| try: | |
| result = _import_der(der_encoded, passphrase) | |
| except UnsupportedEccFeature as uef: | |
| raise uef | |
| except ValueError: | |
| raise ValueError("Invalid DER encoding inside the PEM file") | |
| return result | |
| # OpenSSH | |
| if encoded.startswith((b'ecdsa-sha2-', b'ssh-ed25519')): | |
| return _import_openssh_public(encoded) | |
| # DER | |
| if len(encoded) > 0 and bord(encoded[0]) == 0x30: | |
| return _import_der(encoded, passphrase) | |
| # SEC1 | |
| if len(encoded) > 0 and bord(encoded[0]) in (0x02, 0x03, 0x04): | |
| if curve_name is None: | |
| raise ValueError("No curve name was provided") | |
| return _import_public_der(encoded, curve_name=curve_name) | |
| raise ValueError("ECC key format is not supported") | |
| if __name__ == "__main__": | |
| import time | |
| d = 0xc51e4753afdec1e6b6c6a5b992f43f8dd0c7a8933072708b6522468b2ffb06fd | |
| point = _curves['p256'].G.copy() | |
| count = 3000 | |
| start = time.time() | |
| for x in range(count): | |
| pointX = point * d | |
| print("(P-256 G)", (time.time() - start) / count * 1000, "ms") | |
| start = time.time() | |
| for x in range(count): | |
| pointX = pointX * d | |
| print("(P-256 arbitrary point)", (time.time() - start) / count * 1000, "ms") | |