pycryptodome/lib/Crypto/PublicKey/ECC.py

1270 lines
44 KiB
Python
Raw Normal View History

2015-12-28 23:22:56 +01:00
# ===================================================================
#
# 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.
# ===================================================================
2018-10-15 21:58:33 +02:00
from __future__ import print_function
import re
2016-01-26 22:05:02 +01:00
import struct
import binascii
2018-10-15 21:58:33 +02:00
from collections import namedtuple
2015-12-28 23:22:56 +01:00
from Crypto.Util.py3compat import bord, tobytes, tostr, bchr, is_string
2019-01-25 22:04:37 +01:00
from Crypto.Util.number import bytes_to_long, long_to_bytes
2016-01-17 23:04:11 +01:00
2015-12-28 23:22:56 +01:00
from Crypto.Math.Numbers import Integer
2016-01-17 23:04:11 +01:00
from Crypto.Util.asn1 import (DerObjectId, DerOctetString, DerSequence,
DerBitString)
from Crypto.PublicKey import (_expand_subject_public_key_info,
2016-01-27 08:44:43 +01:00
_create_subject_public_key_info,
2016-01-17 23:04:11 +01:00
_extract_subject_public_key_info)
2019-01-25 22:04:37 +01:00
from Crypto.Util._raw_api import (load_pycryptodome_raw_lib, VoidPointer,
2019-02-08 08:39:45 +01:00
SmartPointer, c_size_t, c_uint8_ptr,
c_ulonglong)
2019-02-08 08:39:45 +01:00
2019-08-22 23:16:56 +02:00
from Crypto.Random import get_random_bytes
2019-02-08 08:39:45 +01:00
from Crypto.Random.random import getrandbits
2019-01-25 22:04:37 +01:00
2019-08-22 23:16:56 +02:00
2019-01-25 22:04:37 +01:00
_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,
2019-02-08 22:49:36 +01:00
const uint8_t *order,
size_t len,
uint64_t seed);
2019-01-25 22:04:37 +01:00
void ec_free_context(EcContext *ec_ctx);
int ec_ws_new_point(EcPoint **pecp,
2019-02-27 07:53:02 +01:00
const uint8_t *x,
const uint8_t *y,
size_t len,
2019-02-22 21:11:06 +01:00
const EcContext *ec_ctx);
2019-01-25 22:04:37 +01:00
void ec_free_point(EcPoint *ecp);
2019-02-27 07:53:02 +01:00
int ec_ws_get_xy(uint8_t *x,
uint8_t *y,
size_t len,
const EcPoint *ecp);
2019-01-25 22:04:37 +01:00
int ec_ws_double(EcPoint *p);
int ec_ws_add(EcPoint *ecpa, EcPoint *ecpb);
2019-02-27 07:53:02 +01:00
int ec_ws_scalar(EcPoint *ecp,
const uint8_t *k,
size_t len,
uint64_t seed);
2019-01-25 22:04:37 +01:00
int ec_ws_clone(EcPoint **pecp2, const EcPoint *ecp);
2019-02-27 07:53:02 +01:00
int ec_ws_copy(EcPoint *ecp1, const EcPoint *ecp2);
2019-01-25 22:04:37 +01:00
int ec_ws_cmp(const EcPoint *ecp1, const EcPoint *ecp2);
int ec_ws_neg(EcPoint *p);
2019-02-27 07:53:02 +01:00
int ec_ws_normalize(EcPoint *ecp);
int ec_ws_is_pai(EcPoint *ecp);
2019-01-25 22:04:37 +01:00
""")
_Curve = namedtuple("_Curve", "p b order Gx Gy G modulus_bits oid context desc openssh")
2019-03-01 22:54:31 +01:00
_curves = {}
2022-01-21 23:11:15 +01:00
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")
global p224_names
_curves.update(dict.fromkeys(p224_names, p224))
init_p224()
del init_p224
2019-03-01 22:54:31 +01:00
2019-08-17 09:02:23 +02:00
p256_names = ["p256", "NIST P-256", "P-256", "prime256v1", "secp256r1",
"nistp256"]
2019-04-03 21:24:08 +02:00
2019-03-01 22:54:31 +01:00
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:
2019-03-02 22:29:59 +01:00
raise ImportError("Error %d initializing P-256 context" % result)
2019-03-01 22:54:31 +01:00
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,
2019-03-02 22:29:59 +01:00
"1.2.840.10045.3.1.7", # ANSI X9.62
2019-03-01 22:54:31 +01:00
context,
"NIST P-256",
"ecdsa-sha2-nistp256")
2019-04-03 21:24:08 +02:00
global p256_names
2019-03-01 22:54:31 +01:00
_curves.update(dict.fromkeys(p256_names, p256))
init_p256()
del init_p256
2019-01-25 22:04:37 +01:00
2015-12-28 23:22:56 +01:00
2019-08-17 09:02:23 +02:00
p384_names = ["p384", "NIST P-384", "P-384", "prime384v1", "secp384r1",
"nistp384"]
2019-04-03 21:24:08 +02:00
2019-03-02 22:29:59 +01:00
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,
2019-03-02 22:29:59 +01:00
"1.3.132.0.34", # SEC 2
context,
"NIST P-384",
"ecdsa-sha2-nistp384")
2019-04-03 21:24:08 +02:00
global p384_names
2019-03-02 22:29:59 +01:00
_curves.update(dict.fromkeys(p384_names, p384))
init_p384()
del init_p384
2019-08-17 09:02:23 +02:00
p521_names = ["p521", "NIST P-521", "P-521", "prime521v1", "secp521r1",
"nistp521"]
2019-04-03 21:24:08 +02:00
2019-03-02 22:29:59 +01:00
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,
2019-03-02 22:29:59 +01:00
"1.3.132.0.35", # SEC 2
context,
"NIST P-521",
"ecdsa-sha2-nistp521")
2019-04-03 21:24:08 +02:00
global p521_names
2019-03-02 22:29:59 +01:00
_curves.update(dict.fromkeys(p521_names, p521))
init_p521()
del init_p521
class UnsupportedEccFeature(ValueError):
pass
2016-01-02 15:33:28 -05:00
class EccPoint(object):
"""A class to abstract a point over an Elliptic Curve.
2017-08-15 15:22:35 +02:00
2019-03-22 20:53:12 +01:00
The class support special methods for:
2019-03-21 23:39:47 +01:00
* Adding two points: ``R = S + T``
* In-place addition: ``S += T``
* Negating a point: ``R = -T``
* Comparing two points: ``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
2017-08-15 15:22:35 +02:00
:vartype x: integer
2019-03-21 23:39:47 +01:00
:ivar y: The affine Y-coordinate of the ECC point
2017-08-15 15:22:35 +02:00
:vartype y: integer
2019-03-21 23:39:47 +01:00
:ivar xy: The tuple with X- and Y- coordinates
"""
2015-12-28 23:22:56 +01:00
2019-03-02 22:29:59 +01:00
def __init__(self, x, y, curve="p256"):
2019-01-25 22:04:37 +01:00
2019-03-09 11:12:05 +01:00
try:
self._curve = _curves[curve]
except KeyError:
raise ValueError("Unknown curve name %s" % str(curve))
2019-03-02 23:47:28 +01:00
self._curve_name = curve
2019-03-02 22:29:59 +01:00
modulus_bytes = self.size_in_bytes()
2019-03-02 22:29:59 +01:00
context = self._curve.context
xb = long_to_bytes(x, modulus_bytes)
yb = long_to_bytes(y, modulus_bytes)
if len(xb) != modulus_bytes or len(yb) != modulus_bytes:
2019-03-02 22:29:59 +01:00
raise ValueError("Incorrect coordinate length")
2019-03-01 22:54:31 +01:00
2019-01-25 22:04:37 +01:00
self._point = VoidPointer()
result = _ec_lib.ec_ws_new_point(self._point.address_of(),
c_uint8_ptr(xb),
c_uint8_ptr(yb),
c_size_t(modulus_bytes),
2019-03-01 22:54:31 +01:00
context.get())
2019-01-25 22:04:37 +01:00
if result:
2019-02-01 22:49:07 +01:00
if result == 15:
raise ValueError("The EC point does not belong to the curve")
2019-01-25 22:04:37 +01:00
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(),
_ec_lib.ec_free_point)
2016-01-11 08:08:08 +01:00
def set(self, point):
2019-01-25 22:04:37 +01:00
self._point = VoidPointer()
result = _ec_lib.ec_ws_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(),
_ec_lib.ec_free_point)
2016-01-11 08:08:08 +01:00
return self
2015-12-28 23:22:56 +01:00
def __eq__(self, point):
2019-01-25 22:04:37 +01:00
return 0 == _ec_lib.ec_ws_cmp(self._point.get(), point._point.get())
2015-12-28 23:22:56 +01:00
def __neg__(self):
2019-01-25 22:04:37 +01:00
np = self.copy()
result = _ec_lib.ec_ws_neg(np._point.get())
2019-01-25 22:04:37 +01:00
if result:
raise ValueError("Error %d while inverting an EC point" % result)
2019-01-25 22:04:37 +01:00
return np
2015-12-28 23:22:56 +01:00
def copy(self):
2019-03-21 23:39:47 +01:00
"""Return a copy of this point."""
2019-01-25 22:04:37 +01:00
x, y = self.xy
2019-03-02 23:47:28 +01:00
np = EccPoint(x, y, self._curve_name)
2019-01-25 22:04:37 +01:00
return np
2015-12-28 23:22:56 +01:00
def is_point_at_infinity(self):
2019-03-21 23:39:47 +01:00
"""``True`` if this is the point-at-infinity."""
2019-01-25 22:04:37 +01:00
return self.xy == (0, 0)
2015-12-28 23:22:56 +01:00
2019-03-02 23:47:28 +01:00
def point_at_infinity(self):
2019-03-21 23:39:47 +01:00
"""Return the point-at-infinity for the curve this point is on."""
2019-03-02 23:47:28 +01:00
return EccPoint(0, 0, self._curve_name)
2015-12-28 23:22:56 +01:00
@property
def x(self):
2019-01-25 22:04:37 +01:00
return self.xy[0]
2015-12-28 23:22:56 +01:00
@property
def y(self):
2019-01-25 22:04:37 +01:00
return self.xy[1]
@property
def xy(self):
modulus_bytes = self.size_in_bytes()
xb = bytearray(modulus_bytes)
yb = bytearray(modulus_bytes)
2019-01-25 22:04:37 +01:00
result = _ec_lib.ec_ws_get_xy(c_uint8_ptr(xb),
c_uint8_ptr(yb),
c_size_t(modulus_bytes),
2019-01-25 22:04:37 +01:00
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)))
2019-04-03 21:24:08 +02:00
def size_in_bytes(self):
2019-03-21 23:39:47 +01:00
"""Size of each coordinate, in bytes."""
return (self.size_in_bits() + 7) // 8
def size_in_bits(self):
2019-03-21 23:39:47 +01:00
"""Size of each coordinate, in bits."""
return self._curve.modulus_bits
2015-12-28 23:22:56 +01:00
def double(self):
2017-08-15 15:22:35 +02:00
"""Double this point (in-place operation).
:Return:
:class:`EccPoint` : this same object (to enable chaining)
"""
2015-12-28 23:22:56 +01:00
2019-01-25 22:04:37 +01:00
result = _ec_lib.ec_ws_double(self._point.get())
if result:
raise ValueError("Error %d while doubling an EC point" % result)
return self
2015-12-28 23:22:56 +01:00
2016-01-11 08:08:08 +01:00
def __iadd__(self, point):
"""Add a second point to this one"""
2015-12-28 23:22:56 +01:00
2019-01-25 22:04:37 +01:00
result = _ec_lib.ec_ws_add(self._point.get(), point._point.get())
if result:
2019-02-01 22:49:07 +01:00
if result == 16:
2019-03-10 22:06:57 +01:00
raise ValueError("EC points are not on the same curve")
2019-01-25 22:04:37 +01:00
raise ValueError("Error %d while adding two EC points" % result)
return self
2016-01-11 08:08:08 +01:00
def __add__(self, point):
"""Return a new point, the addition of this one and another"""
2019-01-25 22:04:37 +01:00
np = self.copy()
np += point
return np
2015-12-28 23:22:56 +01:00
2019-01-30 08:41:21 +01:00
def __imul__(self, scalar):
"""Multiply this point by a scalar"""
2015-12-28 23:22:56 +01:00
if scalar < 0:
2019-01-25 22:04:37 +01:00
raise ValueError("Scalar multiplication is only defined for non-negative integers")
sb = long_to_bytes(scalar)
result = _ec_lib.ec_ws_scalar(self._point.get(),
c_uint8_ptr(sb),
c_size_t(len(sb)),
c_ulonglong(getrandbits(64)))
2019-01-25 22:04:37 +01:00
if result:
raise ValueError("Error %d during scalar multiplication" % result)
2019-01-30 08:41:21 +01:00
return self
def __mul__(self, scalar):
"""Return a new point, the scalar product of this one"""
np = self.copy()
np *= scalar
2019-01-25 22:04:37 +01:00
return np
2016-01-02 15:33:28 -05:00
def __rmul__(self, left_hand):
return self.__mul__(left_hand)
2016-01-02 15:33:28 -05:00
2019-03-01 22:54:31 +01:00
# Last piece of initialization
2022-01-21 23:11:15 +01:00
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
2019-03-02 22:29:59 +01:00
p256_G = EccPoint(_curves['p256'].Gx, _curves['p256'].Gy, "p256")
2019-03-01 22:54:31 +01:00
p256 = _curves['p256']._replace(G=p256_G)
_curves.update(dict.fromkeys(p256_names, p256))
del p256_G, p256, p256_names
2016-01-09 14:48:37 +01:00
2019-03-02 22:29:59 +01:00
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
2016-01-09 14:48:37 +01:00
2016-01-02 15:33:28 -05:00
class EccKey(object):
2017-08-14 23:41:07 +02:00
r"""Class defining an ECC key.
Do not instantiate directly.
Use :func:`generate`, :func:`construct` or :func:`import_key` instead.
2019-03-22 08:16:39 +01:00
:ivar curve: The name of the ECC as defined in :numref:`curve_names`.
2017-08-14 23:41:07 +02:00
:vartype curve: string
:ivar pointQ: an ECC point representating the public component
:vartype pointQ: :class:`EccPoint`
2016-01-31 23:27:55 +01:00
2018-10-26 18:03:11 +02:00
:ivar d: A scalar representating the private component
:vartype d: integer
2016-01-31 23:27:55 +01:00
"""
2016-01-02 15:33:28 -05:00
def __init__(self, **kwargs):
"""Create a new ECC key
Keywords:
curve : string
2019-03-01 22:54:31 +01:00
It must be *"p256"*, *"P-256"*, *"prime256v1"* or *"secp256r1"*.
2016-01-02 15:33:28 -05:00
d : integer
2016-01-31 23:27:55 +01:00
Only for a private key. It must be in the range ``[1..order-1]``.
2016-01-02 15:33:28 -05:00
point : EccPoint
Mandatory for a public key. If provided for a private key,
the implementation will NOT check whether it matches ``d``.
"""
2016-01-09 22:18:11 +01:00
kwargs_ = dict(kwargs)
2019-03-01 22:54:31 +01:00
curve_name = kwargs_.pop("curve", None)
2016-01-09 22:18:11 +01:00
self._d = kwargs_.pop("d", None)
self._point = kwargs_.pop("point", None)
if kwargs_:
raise TypeError("Unknown parameters: " + str(kwargs_))
2016-01-02 15:33:28 -05:00
2019-03-01 22:54:31 +01:00
if curve_name not in _curves:
raise ValueError("Unsupported curve (%s)", curve_name)
self._curve = _curves[curve_name]
2016-01-02 15:33:28 -05:00
if self._d is None:
if self._point is None:
2019-03-01 22:54:31 +01:00
raise ValueError("Either private or public ECC component must be specified, not both")
2016-01-02 15:33:28 -05:00
else:
2016-01-09 22:41:29 +01:00
self._d = Integer(self._d)
2019-03-01 22:54:31 +01:00
if not 1 <= self._d < self._curve.order:
2016-01-02 15:33:28 -05:00
raise ValueError("Invalid ECC private component")
2019-03-01 22:54:31 +01:00
self.curve = self._curve.desc
2016-01-18 21:03:17 +01:00
def __eq__(self, other):
if other.has_private() != self.has_private():
return False
2019-03-01 22:54:31 +01:00
return other.pointQ == self.pointQ
2016-01-18 21:03:17 +01:00
2016-01-18 21:09:30 +01:00
def __repr__(self):
if self.has_private():
extra = ", d=%d" % int(self._d)
else:
extra = ""
2019-03-01 22:54:31 +01:00
x, y = self.pointQ.xy
return "EccKey(curve='%s', point_x=%d, point_y=%d%s)" % (self._curve.desc, x, y, extra)
2016-01-18 21:09:30 +01:00
2016-01-02 15:33:28 -05:00
def has_private(self):
2017-08-15 15:22:35 +02:00
"""``True`` if this key can be used for making signatures or decrypting data."""
2017-08-14 23:41:07 +02:00
2016-01-02 15:33:28 -05:00
return self._d is not None
2016-01-09 14:48:37 +01:00
def _sign(self, z, k):
2019-03-01 22:54:31 +01:00
assert 0 < k < self._curve.order
2019-03-01 22:54:31 +01:00
order = self._curve.order
blind = Integer.random_range(min_inclusive=1,
2019-03-01 22:54:31 +01:00
max_exclusive=order)
blind_d = self._d * blind
2019-03-01 22:54:31 +01:00
inv_blind_k = (blind * k).inverse(order)
2019-03-01 22:54:31 +01:00
r = (self._curve.G * k).x % order
s = inv_blind_k * (blind * z + blind_d * r) % order
2016-01-09 14:48:37 +01:00
return (r, s)
def _verify(self, z, rs):
2019-03-01 22:54:31 +01:00
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]
2016-01-09 14:48:37 +01:00
2016-01-02 15:33:28 -05:00
@property
def d(self):
if not self.has_private():
raise ValueError("This is not a private ECC key")
return self._d
@property
def pointQ(self):
if self._point is None:
2019-03-01 22:54:31 +01:00
self._point = self._curve.G * self._d
2016-01-02 15:33:28 -05:00
return self._point
2016-01-09 14:24:32 +01:00
def public_key(self):
2017-08-14 23:41:07 +02:00
"""A matching ECC public key.
Returns:
a new :class:`EccKey` object
"""
2019-03-01 22:54:31 +01:00
return EccKey(curve=self._curve.desc, point=self.pointQ)
2016-01-09 14:24:32 +01:00
def _export_SEC1(self, compress):
# 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.
2016-01-27 08:44:43 +01:00
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_subjectPublicKeyInfo(self, compress):
2016-01-27 08:44:43 +01:00
public_key = self._export_SEC1(compress)
2016-01-27 08:44:43 +01:00
unrestricted_oid = "1.2.840.10045.2.1"
return _create_subject_public_key_info(unrestricted_oid,
public_key,
2019-03-01 22:54:31 +01:00
DerObjectId(self._curve.oid))
2016-01-27 08:44:43 +01:00
def _export_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))
2016-01-27 08:44:43 +01:00
seq = [1,
DerOctetString(self.d.to_bytes(modulus_bytes)),
2019-03-01 22:54:31 +01:00
DerObjectId(self._curve.oid, explicit=0),
2016-01-27 08:44:43 +01:00
DerBitString(public_key, explicit=1)]
if not include_ec_params:
del seq[2]
return DerSequence(seq).encode()
def _export_pkcs8(self, **kwargs):
2019-11-01 23:39:04 +01:00
from Crypto.IO import PKCS8
2016-01-27 08:44:43 +01:00
if kwargs.get('passphrase', None) is not None and 'protection' not in kwargs:
raise ValueError("At least the 'protection' parameter should be present")
2019-03-01 22:54:31 +01:00
2016-01-27 08:44:43 +01:00
unrestricted_oid = "1.2.840.10045.2.1"
private_key = self._export_private_der(include_ec_params=False)
result = PKCS8.wrap(private_key,
unrestricted_oid,
2019-03-01 22:54:31 +01:00
key_params=DerObjectId(self._curve.oid),
2016-01-27 08:44:43 +01:00
**kwargs)
return result
def _export_public_pem(self, compress):
2019-11-01 23:39:04 +01:00
from Crypto.IO import PEM
encoded_der = self._export_subjectPublicKeyInfo(compress)
2016-01-27 08:44:43 +01:00
return PEM.encode(encoded_der, "PUBLIC KEY")
def _export_private_pem(self, passphrase, **kwargs):
2019-11-01 23:39:04 +01:00
from Crypto.IO import PEM
2016-01-27 08:44:43 +01:00
encoded_der = self._export_private_der()
return PEM.encode(encoded_der, "EC PRIVATE KEY", passphrase, **kwargs)
2016-01-27 08:44:43 +01:00
def _export_private_clear_pkcs8_in_clear_pem(self):
2019-11-01 23:39:04 +01:00
from Crypto.IO import PEM
2016-01-27 08:44:43 +01:00
encoded_der = self._export_pkcs8()
return PEM.encode(encoded_der, "PRIVATE KEY")
def _export_private_encrypted_pkcs8_in_clear_pem(self, passphrase, **kwargs):
2019-11-01 23:39:04 +01:00
from Crypto.IO import PEM
2016-01-27 08:44:43 +01:00
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
modulus_bytes = self.pointQ.size_in_bytes()
2019-01-25 22:04:37 +01:00
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)
2019-01-25 22:04:37 +01:00
blob = b"".join([struct.pack(">I", len(x)) + x for x in comps])
return desc + " " + tostr(binascii.b2a_base64(blob))
2016-01-27 08:44:43 +01:00
def export_key(self, **kwargs):
"""Export this ECC key.
2017-08-14 23:41:07 +02:00
Args:
format (string):
2018-02-16 13:44:00 +01:00
The format to use for encoding the key:
2019-03-21 23:39:47 +01:00
- ``'DER'``. The key will be encoded in ASN.1 DER format (binary).
2018-02-16 13:44:00 +01:00
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).
2019-03-21 23:39:47 +01:00
- ``'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).
2016-01-27 08:44:43 +01:00
2017-08-14 23:41:07 +02:00
passphrase (byte string or string):
2016-01-27 08:44:43 +01:00
The passphrase to use for protecting the private key.
2017-08-14 23:41:07 +02:00
use_pkcs8 (boolean):
2019-03-21 23:39:47 +01:00
Only relevant for private keys.
2017-08-15 15:22:35 +02:00
If ``True`` (default and recommended), the `PKCS#8`_ representation
will be used.
2016-01-27 08:44:43 +01:00
2018-02-16 13:44:00 +01:00
If ``False``, the much weaker `PEM encryption`_ mechanism will be used.
2016-01-27 08:44:43 +01:00
2017-08-14 23:41:07 +02:00
protection (string):
2017-08-15 15:22:35 +02:00
When a private key is exported with password-protection
2016-01-31 23:27:55 +01:00
and PKCS#8 (both ``DER`` and ``PEM`` formats), this parameter MUST be
2017-08-14 23:41:07 +02:00
present and be a valid algorithm supported by :mod:`Crypto.IO.PKCS8`.
2016-01-27 08:44:43 +01:00
It is recommended to use ``PBKDF2WithHMAC-SHA1AndAES128-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.
2017-08-14 23:41:07 +02:00
.. warning::
2017-08-15 15:22:35 +02:00
If you don't provide a passphrase, the private key will be
2017-08-14 23:41:07 +02:00
exported in the clear!
.. note::
When exporting a private key with password-protection and `PKCS#8`_
(both ``DER`` and ``PEM`` formats), any extra parameters
2019-03-21 23:39:47 +01:00
to ``export_key()`` will be passed to :mod:`Crypto.IO.PKCS8`.
2016-01-27 08:44:43 +01:00
.. _PEM: http://www.ietf.org/rfc/rfc1421.txt
.. _`PEM encryption`: http://www.ietf.org/rfc/rfc1423.txt
2016-01-31 23:27:55 +01:00
.. _`PKCS#8`: http://www.ietf.org/rfc/rfc5208.txt
.. _OpenSSH: http://www.openssh.com/txt/rfc5656.txt
2018-02-16 13:44:00 +01:00
.. _RFC5480: https://tools.ietf.org/html/rfc5480
.. _RFC5915: http://www.ietf.org/rfc/rfc5915.txt
.. _SEC1: https://www.secg.org/sec1-v2.pdf
2016-01-27 08:44:43 +01:00
2017-08-14 23:41:07 +02:00
Returns:
A multi-line string (for PEM and OpenSSH) or
``bytes`` (for DER and SEC1) with the encoded key.
2016-01-27 08:44:43 +01:00
"""
args = kwargs.copy()
ext_format = args.pop("format")
if ext_format not in ("PEM", "DER", "OpenSSH", "SEC1"):
2016-01-27 08:44:43 +01:00
raise ValueError("Unknown format '%s'" % ext_format)
2019-01-25 22:04:37 +01:00
compress = args.pop("compress", False)
2016-01-27 08:44:43 +01:00
if self.has_private():
passphrase = args.pop("passphrase", None)
if is_string(passphrase):
2016-01-27 08:44:43 +01:00
passphrase = tobytes(passphrase)
if not passphrase:
raise ValueError("Empty passphrase")
2016-01-27 08:44:43 +01:00
use_pkcs8 = args.pop("use_pkcs8", True)
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":
2016-01-27 08:44:43 +01:00
# 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_private_der()
else:
raise ValueError("Private keys cannot be exported "
"in the '%s' format" % ext_format)
2016-01-27 08:44:43 +01:00
else: # Public key
if args:
raise ValueError("Unexpected parameters: '%s'" % args)
2016-01-27 08:44:43 +01:00
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)
else:
return self._export_openssh(compress)
2016-01-27 08:44:43 +01:00
2016-01-02 15:33:28 -05:00
2016-01-09 14:48:37 +01:00
def generate(**kwargs):
2016-01-08 23:27:35 +01:00
"""Generate a new private key on the given curve.
2017-08-14 23:41:07 +02:00
Args:
curve (string):
2019-03-22 08:16:39 +01:00
Mandatory. It must be a curve name defined in :numref:`curve_names`.
2017-08-14 23:41:07 +02:00
randfunc (callable):
2016-01-31 23:27:55 +01:00
Optional. The RNG to read randomness from.
2017-08-14 23:41:07 +02:00
If ``None``, :func:`Crypto.Random.get_random_bytes` is used.
2016-01-08 23:27:35 +01:00
"""
2016-01-02 15:33:28 -05:00
2019-03-01 22:54:31 +01:00
curve_name = kwargs.pop("curve")
curve = _curves[curve_name]
2016-01-09 14:48:37 +01:00
randfunc = kwargs.pop("randfunc", get_random_bytes)
if kwargs:
raise TypeError("Unknown parameters: " + str(kwargs))
2016-01-02 15:33:28 -05:00
d = Integer.random_range(min_inclusive=1,
2019-03-01 22:54:31 +01:00
max_exclusive=curve.order,
2016-01-02 15:33:28 -05:00
randfunc=randfunc)
2019-03-01 22:54:31 +01:00
return EccKey(curve=curve_name, d=d)
2016-01-08 23:27:35 +01:00
2016-01-17 23:04:11 +01:00
2016-01-09 22:18:11 +01:00
def construct(**kwargs):
"""Build a new ECC key (private or public) starting
from some base components.
2017-08-14 23:41:07 +02:00
Args:
curve (string):
2019-03-22 08:16:39 +01:00
Mandatory. It must be a curve name defined in :numref:`curve_names`.
2017-08-14 23:41:07 +02:00
d (integer):
2016-01-31 23:27:55 +01:00
Only for a private key. It must be in the range ``[1..order-1]``.
2017-08-14 23:41:07 +02:00
point_x (integer):
2016-01-31 23:27:55 +01:00
Mandatory for a public key. X coordinate (affine) of the ECC point.
2017-08-14 23:41:07 +02:00
point_y (integer):
2016-01-31 23:27:55 +01:00
Mandatory for a public key. Y coordinate (affine) of the ECC point.
2017-08-14 23:41:07 +02:00
Returns:
:class:`EccKey` : a new ECC key object
2016-01-09 22:18:11 +01:00
"""
2019-03-01 22:54:31 +01:00
curve_name = kwargs["curve"]
curve = _curves[curve_name]
2016-01-09 22:18:11 +01:00
point_x = kwargs.pop("point_x", None)
point_y = kwargs.pop("point_y", None)
if "point" in kwargs:
raise TypeError("Unknown keyword: point")
2016-01-09 22:18:11 +01:00
if None not in (point_x, point_y):
# ValueError is raised if the point is not on the curve
2019-03-02 23:47:28 +01:00
kwargs["point"] = EccPoint(point_x, point_y, curve_name)
2016-01-09 22:18:11 +01:00
# Validate that the private key matches the public one
d = kwargs.get("d", None)
if d is not None and "point" in kwargs:
2019-03-01 22:54:31 +01:00
pub_key = curve.G * d
2019-02-15 23:32:09 +01:00
if pub_key.xy != (point_x, point_y):
raise ValueError("Private and public ECC keys do not match")
2016-01-09 22:18:11 +01:00
return EccKey(**kwargs)
2016-01-08 23:27:35 +01:00
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
"""
2016-01-17 23:04:11 +01:00
for _curve_name, curve in _curves.items():
if curve_oid and curve.oid == curve_oid:
break
if curve_name == _curve_name:
2019-03-01 22:54:31 +01:00
break
else:
if curve_oid:
raise UnsupportedEccFeature("Unsupported ECC curve (OID: %s)" % curve_oid)
else:
raise UnsupportedEccFeature("Unsupported ECC curve (%s)" % curve_name)
2016-01-17 23:04:11 +01:00
# 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.
2016-01-17 23:04:11 +01:00
modulus_bytes = curve.p.size_in_bytes()
point_type = bord(ec_point[0])
2019-01-25 22:04:37 +01:00
# 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():
2019-03-01 22:54:31 +01:00
y = curve.p - y
if point_type == 0x03 and y.is_even():
2019-03-01 22:54:31 +01:00
y = curve.p - y
else:
raise ValueError("Incorrect EC point encoding")
2016-01-17 23:04:11 +01:00
return construct(curve=_curve_name, point_x=x, point_y=y)
2016-01-17 23:04:11 +01:00
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)
# ec_point must be an encoded OCTET STRING
# params is encoded ECParameters
2016-01-17 23:04:11 +01:00
# We accept id-ecPublicKey, id-ecDH, id-ecMQV without making any
# distiction for now.
2019-01-25 22:04:37 +01:00
# Restrictions can be captured in the key usage certificate
# extension
unrestricted_oid = "1.2.840.10045.2.1"
2016-01-17 23:04:11 +01:00
ecdh_oid = "1.3.132.1.12"
ecmqv_oid = "1.3.132.1.13"
if oid not in (unrestricted_oid, ecdh_oid, ecmqv_oid):
raise UnsupportedEccFeature("Unsupported ECC purpose (OID: %s)" % oid)
2016-01-17 23:04:11 +01:00
# Parameters are mandatory for all three types
if not params:
raise ValueError("Missing ECC parameters")
2016-01-17 23:04:11 +01:00
# ECParameters ::= CHOICE {
# namedCurve OBJECT IDENTIFIER
# -- implicitCurve NULL
# -- specifiedCurve SpecifiedECDomain
# }
#
# implicitCurve and specifiedCurve are not supported (as per RFC)
curve_oid = DerObjectId().decode(params).value
2016-01-17 23:04:11 +01:00
return _import_public_der(ec_point, curve_oid=curve_oid)
2016-01-17 23:04:11 +01:00
2019-03-02 21:43:28 +01:00
def _import_private_der(encoded, passphrase, curve_oid=None):
2016-01-17 23:04:11 +01:00
2019-03-02 21:43:28 +01:00
# See RFC5915 https://tools.ietf.org/html/rfc5915
#
2016-01-17 23:04:11 +01:00
# 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:
2019-03-02 21:43:28 +01:00
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
2016-01-17 23:04:11 +01:00
except ValueError:
2019-03-02 21:43:28 +01:00
pass
if curve_oid is None:
raise ValueError("No curve found")
2016-01-17 23:04:11 +01:00
2019-03-01 22:54:31 +01:00
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)
2016-01-17 23:04:11 +01:00
2019-03-02 21:43:28 +01:00
# Decode public key (if any)
2016-01-17 23:04:11 +01:00
if len(private_key) == 4:
public_key_enc = DerBitString(explicit=1).decode(private_key[3]).value
public_key = _import_public_der(public_key_enc, curve_oid=curve_oid)
2016-01-17 23:04:11 +01:00
point_x = public_key.pointQ.x
point_y = public_key.pointQ.y
else:
point_x = point_y = None
2019-03-01 22:54:31 +01:00
return construct(curve=curve_name, d=d, point_x=point_x, point_y=point_y)
2016-01-17 23:04:11 +01:00
def _import_pkcs8(encoded, passphrase):
2019-11-01 23:39:04 +01:00
from Crypto.IO import PKCS8
2016-01-17 23:04:11 +01:00
# From RFC5915, Section 1:
#
# Distributing an EC private key with PKCS#8 [RFC5208] involves including:
# a) id-ecPublicKey, id-ecDH, or id-ecMQV (from [RFC5480]) with the
# namedCurve as the parameters in the privateKeyAlgorithm field; and
# b) ECPrivateKey in the PrivateKey field, which is an OCTET STRING.
algo_oid, private_key, params = PKCS8.unwrap(encoded, passphrase)
# We accept id-ecPublicKey, id-ecDH, id-ecMQV without making any
# distiction for now.
unrestricted_oid = "1.2.840.10045.2.1"
ecdh_oid = "1.3.132.1.12"
ecmqv_oid = "1.3.132.1.13"
if algo_oid not in (unrestricted_oid, ecdh_oid, ecmqv_oid):
2019-01-25 22:04:37 +01:00
raise UnsupportedEccFeature("Unsupported ECC purpose (OID: %s)" % algo_oid)
2016-01-17 23:04:11 +01:00
2019-03-02 21:43:28 +01:00
curve_oid = DerObjectId().decode(params).value
2016-01-17 23:04:11 +01:00
2019-03-02 21:43:28 +01:00
return _import_private_der(private_key, passphrase, curve_oid)
2016-01-17 23:04:11 +01:00
def _import_x509_cert(encoded, *kwargs):
sp_info = _extract_subject_public_key_info(encoded)
return _import_subjectPublicKeyInfo(sp_info)
2016-01-17 23:04:11 +01:00
def _import_der(encoded, passphrase):
try:
return _import_subjectPublicKeyInfo(encoded, passphrase)
2018-06-12 14:15:39 +02:00
except UnsupportedEccFeature as err:
raise err
except (ValueError, TypeError, IndexError):
pass
2019-01-25 22:04:37 +01:00
try:
return _import_x509_cert(encoded, passphrase)
2018-06-12 14:15:39 +02:00
except UnsupportedEccFeature as err:
raise err
except (ValueError, TypeError, IndexError):
pass
2019-01-25 22:04:37 +01:00
try:
return _import_private_der(encoded, passphrase)
2018-06-12 14:15:39 +02:00
except UnsupportedEccFeature as err:
raise err
except (ValueError, TypeError, IndexError):
pass
2019-01-25 22:04:37 +01:00
try:
return _import_pkcs8(encoded, passphrase)
2018-06-12 14:15:39 +02:00
except UnsupportedEccFeature as err:
raise err
except (ValueError, TypeError, IndexError):
pass
2016-01-17 23:04:11 +01:00
raise ValueError("Not an ECC DER key")
2019-08-17 09:02:23 +02:00
def _import_openssh_public(encoded):
keystring = binascii.a2b_base64(encoded.split(b' ')[1])
2016-01-26 22:05:02 +01:00
keyparts = []
while len(keystring) > 4:
2019-01-25 22:04:37 +01:00
lk = struct.unpack(">I", keystring[:4])[0]
keyparts.append(keystring[4:4 + lk])
keystring = keystring[4 + lk:]
2016-01-26 22:05:02 +01:00
for curve_name, curve in _curves.items():
middle = tobytes(curve.openssh.split("-")[2])
if keyparts[1] == middle:
break
else:
2016-01-26 22:05:02 +01:00
raise ValueError("Unsupported ECC curve")
return _import_public_der(keyparts[2], curve_oid=curve.oid)
2016-01-26 22:05:02 +01:00
2019-08-25 01:06:47 +02:00
def _import_openssh_private_ecc(data, password):
from ._openssh import (import_openssh_private_generic,
read_bytes, read_string, check_padding)
2019-08-25 01:06:47 +02:00
ssh_name, decrypted = import_openssh_private_generic(data, password)
2019-08-17 09:02:23 +02:00
name, decrypted = read_string(decrypted)
if name not in _curves:
raise UnsupportedEccFeature("Unsupported ECC curve %s" % name)
curve = _curves[name]
2019-08-25 00:33:16 +02:00
modulus_bytes = (curve.modulus_bits + 7) // 8
2019-08-17 09:02:23 +02:00
public_key, decrypted = read_bytes(decrypted)
2019-08-22 23:16:56 +02:00
2019-08-17 09:02:23 +02:00
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")
2019-08-22 23:16:56 +02:00
2019-08-17 09:02:23 +02:00
point_x = Integer.from_bytes(public_key[1:1+modulus_bytes])
point_y = Integer.from_bytes(public_key[1+modulus_bytes:])
point = EccPoint(point_x, point_y, curve=name)
private_key, decrypted = read_bytes(decrypted)
d = Integer.from_bytes(private_key)
_, padded = read_string(decrypted) # Comment
check_padding(padded)
2019-08-17 09:02:23 +02:00
return EccKey(curve=name, d=d, point=point)
def import_key(encoded, passphrase=None, curve_name=None):
2016-01-27 08:00:28 +01:00
"""Import an ECC key (public or private).
2017-08-14 23:41:07 +02:00
Args:
encoded (bytes or multi-line string):
2016-01-27 08:00:28 +01:00
The ECC key to import.
2017-08-14 23:41:07 +02:00
An ECC **public** key can be:
2016-01-31 23:27:55 +01:00
- An X.509 certificate, binary (DER) or ASCII (PEM)
- An X.509 ``subjectPublicKeyInfo``, binary (DER) or ASCII (PEM)
- A SEC1_ (or X9.62) byte string. You must also provide the
``curve_name``.
2016-01-31 23:27:55 +01:00
- An OpenSSH line (e.g. the content of ``~/.ssh/id_ecdsa``, ASCII)
2017-08-14 23:41:07 +02:00
An ECC **private** key can be:
2016-01-27 08:00:28 +01:00
2016-01-31 23:27:55 +01:00
- In binary format (DER, see section 3 of `RFC5915`_ or `PKCS#8`_)
2019-08-25 23:58:43 +02:00
- In ASCII format (PEM or `OpenSSH 6.5+`_)
2016-01-27 08:00:28 +01:00
2016-01-31 23:27:55 +01:00
Private keys can be in the clear or password-protected.
2016-01-27 08:00:28 +01:00
For details about the PEM encoding, see `RFC1421`_/`RFC1423`_.
2017-08-14 23:41:07 +02:00
passphrase (byte string):
2016-01-31 23:27:55 +01:00
The passphrase to use for decrypting a private key.
Encryption may be applied protected at the PEM level or at the PKCS#8 level.
This parameter is ignored if the key in input is not encrypted.
2016-01-27 08:00:28 +01:00
curve_name (string):
For a SEC1 byte string only. This is the name of the ECC curve,
as defined in :numref:`curve_names`.
2017-08-14 23:41:07 +02:00
Returns:
:class:`EccKey` : a new ECC key object
2016-01-27 08:00:28 +01:00
2017-08-14 23:41:07 +02:00
Raises:
ValueError: when the given key cannot be parsed (possibly because
2016-01-27 08:00:28 +01:00
the pass phrase is wrong).
.. _RFC1421: http://www.ietf.org/rfc/rfc1421.txt
.. _RFC1423: http://www.ietf.org/rfc/rfc1423.txt
.. _RFC5915: http://www.ietf.org/rfc/rfc5915.txt
.. _`PKCS#8`: http://www.ietf.org/rfc/rfc5208.txt
2019-08-25 23:58:43 +02:00
.. _`OpenSSH 6.5+`: https://flak.tedunangst.com/post/new-openssh-key-format-and-bcrypt-pbkdf
.. _SEC1: https://www.secg.org/sec1-v2.pdf
2016-01-27 08:00:28 +01:00
"""
2016-01-26 22:05:02 +01:00
2019-11-01 23:39:04 +01:00
from Crypto.IO import PEM
2016-01-26 22:05:02 +01:00
encoded = tobytes(encoded)
if passphrase is not None:
passphrase = tobytes(passphrase)
# PEM
2019-08-17 09:02:23 +02:00
if encoded.startswith(b'-----BEGIN OPENSSH PRIVATE KEY'):
text_encoded = tostr(encoded)
openssh_encoded, marker, enc_flag = PEM.decode(text_encoded, passphrase)
2019-08-25 01:06:47 +02:00
result = _import_openssh_private_ecc(openssh_encoded, passphrase)
2019-08-17 09:02:23 +02:00
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
2020-11-30 14:17:12 +01:00
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)
2016-01-26 22:05:02 +01:00
if enc_flag:
passphrase = None
try:
result = _import_der(der_encoded, passphrase)
2018-06-12 14:15:39 +02:00
except UnsupportedEccFeature as uef:
raise uef
except ValueError:
raise ValueError("Invalid DER encoding inside the PEM file")
return result
2016-01-26 22:05:02 +01:00
# OpenSSH
if encoded.startswith(b'ecdsa-sha2-'):
2019-08-17 09:02:23 +02:00
return _import_openssh_public(encoded)
2016-01-26 22:05:02 +01:00
# DER
if len(encoded) > 0 and bord(encoded[0]) == 0x30:
2016-01-26 22:05:02 +01:00
return _import_der(encoded, passphrase)
# SEC1
if len(encoded) > 0 and bord(encoded[0]) in b'\x02\x03\x04':
if curve_name is None:
raise ValueError("No curve name was provided")
return _import_public_der(encoded, curve_name=curve_name)
2016-01-26 22:05:02 +01:00
raise ValueError("ECC key format is not supported")
2016-01-08 23:27:35 +01:00
if __name__ == "__main__":
2019-01-25 22:04:37 +01:00
2016-01-08 23:27:35 +01:00
import time
2018-10-15 21:58:33 +02:00
2016-01-08 23:27:35 +01:00
d = 0xc51e4753afdec1e6b6c6a5b992f43f8dd0c7a8933072708b6522468b2ffb06fd
2019-03-21 22:30:47 +01:00
point = _curves['p256'].G.copy()
2019-01-30 08:41:21 +01:00
count = 3000
start = time.time()
for x in range(count):
pointX = point * d
print("(P-256 G)", (time.time() - start) / count * 1000, "ms")
2019-01-30 08:41:21 +01:00
2016-01-08 23:27:35 +01:00
start = time.time()
2018-10-15 21:58:33 +02:00
for x in range(count):
pointX = pointX * d
print("(P-256 arbitrary point)", (time.time() - start) / count * 1000, "ms")