pycryptodome/lib/Crypto/PublicKey/ECC.py

1343 lines
47 KiB
Python
Raw Permalink 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
2015-12-28 23:22:56 +01:00
from Crypto.Util.py3compat import bord, tobytes, tostr, bchr, is_string
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)
2022-05-16 09:13:35 +02:00
from Crypto.Hash import SHA512, SHAKE256
2019-02-08 08:39:45 +01:00
2019-08-22 23:16:56 +02:00
from Crypto.Random import get_random_bytes
2024-01-27 16:53:23 +01:00
from ._point import EccPoint, EccXPoint, _curves
from ._point import CurveID as _CurveID
2024-01-27 16:53:23 +01:00
class UnsupportedEccFeature(ValueError):
pass
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.
:ivar curve: The **canonical** name of the curve as defined in the `ECC table`_.
2017-08-14 23:41:07 +02:00
:vartype curve: string
2025-03-16 16:30:46 +01:00
:ivar pointQ: an ECC point representing the public component.
2024-01-27 16:53:23 +01:00
:vartype pointQ: :class:`EccPoint` or :class:`EccXPoint`
2016-01-31 23:27:55 +01:00
2022-06-22 19:52:38 +02:00
:ivar d: A scalar that represents the private component
in NIST P curves. It is smaller than the
order of the generator point.
2018-10-26 18:03:11 +02:00
:vartype d: integer
2022-04-15 00:15:48 +02:00
2025-03-16 16:30:46 +01:00
:ivar seed: A seed that represents the private component
2024-08-24 22:29:52 +02:00
in Ed22519 (32 bytes), Curve25519 (32 bytes),
Curve448 (56 bytes), Ed448 (57 bytes).
2022-04-15 00:15:48 +02:00
:vartype seed: bytes
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
2022-05-16 09:13:35 +02:00
The name of the curve.
2016-01-02 15:33:28 -05:00
d : integer
2022-04-15 00:15:48 +02:00
Mandatory for a private key one NIST P curves.
It must be in the range ``[1..order-1]``.
seed : bytes
2024-08-24 22:29:52 +02:00
Mandatory for a private key on Ed25519 (32 bytes),
Curve25519 (32 bytes), Curve448 (56 bytes) or Ed448 (57 bytes).
2024-01-27 16:53:23 +01:00
point : EccPoint or EccXPoint
2016-01-02 15:33:28 -05:00
Mandatory for a public key. If provided for a private key,
the implementation will NOT check whether it matches ``d``.
2022-04-15 00:15:48 +02:00
Only one parameter among ``d``, ``seed`` or ``point`` may be used.
2016-01-02 15:33:28 -05:00
"""
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)
2022-04-15 00:15:48 +02:00
self._seed = kwargs_.pop("seed", None)
2016-01-09 22:18:11 +01:00
self._point = kwargs_.pop("point", None)
2022-04-15 00:15:48 +02:00
if curve_name is None and self._point:
curve_name = self._point.curve
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:
2022-04-15 00:15:48 +02:00
raise ValueError("Unsupported curve (%s)" % curve_name)
2019-03-01 22:54:31 +01:00
self._curve = _curves[curve_name]
self.curve = self._curve.canonical
2022-04-15 00:15:48 +02:00
count = int(self._d is not None) + int(self._seed is not None)
2016-01-02 15:33:28 -05:00
2022-04-15 00:15:48 +02:00
if count == 0:
2016-01-02 15:33:28 -05:00
if self._point is None:
2022-04-15 00:15:48 +02:00
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")
2022-05-16 09:13:35 +02:00
# NIST P curves work with d, EdDSA works with seed
2022-04-15 00:15:48 +02:00
2024-01-27 16:53:23 +01:00
# RFC 8032, 5.1.5
if self._curve.id == _CurveID.ED25519:
2024-01-27 16:53:23 +01:00
if self._d is not None:
raise ValueError("Parameter d can only be used with NIST P curves")
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
self._d = Integer.from_bytes(tmp, byteorder='little')
# RFC 8032, 5.2.5
elif self._curve.id == _CurveID.ED448:
2024-01-27 16:53:23 +01:00
if self._d is not None:
raise ValueError("Parameter d can only be used with NIST P curves")
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')
# RFC 7748, 5
elif self._curve.id == _CurveID.CURVE25519:
2024-01-27 16:53:23 +01:00
if self._d is not None:
raise ValueError("Parameter d can only be used with NIST P curves")
if len(self._seed) != 32:
raise ValueError("Parameter seed must be 32 bytes long for Curve25519")
tmp = bytearray(self._seed)
tmp[0] &= 0xF8
tmp[31] = (tmp[31] & 0x7F) | 0x40
self._d = Integer.from_bytes(tmp, byteorder='little')
2024-08-24 22:29:52 +02:00
elif self._curve.id == _CurveID.CURVE448:
if self._d is not None:
raise ValueError("Parameter d can only be used with NIST P curves")
if len(self._seed) != 56:
raise ValueError("Parameter seed must be 56 bytes long for Curve448")
tmp = bytearray(self._seed)
tmp[0] &= 0xFC
tmp[55] |= 0x80
self._d = Integer.from_bytes(tmp, byteorder='little')
2024-01-27 16:53:23 +01:00
else:
2022-04-15 00:15:48 +02:00
if self._seed is not None:
2024-01-27 16:53:23 +01:00
raise ValueError("Parameter 'seed' cannot be used with NIST P-curves")
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:
2022-04-15 00:15:48 +02:00
raise ValueError("Parameter d must be an integer smaller than the curve order")
2022-05-16 09:13:35 +02:00
2016-01-18 21:03:17 +01:00
def __eq__(self, other):
if not isinstance(other, EccKey):
return False
2016-01-18 21:03:17 +01:00
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():
if self._curve.is_edwards:
2022-12-04 08:51:38 +01:00
extra = ", seed=%s" % tostr(binascii.hexlify(self._seed))
2022-04-15 00:15:48 +02:00
else:
extra = ", d=%d" % int(self._d)
2016-01-18 21:09:30 +01:00
else:
extra = ""
2024-08-24 22:29:52 +02:00
if self._curve.id in (_CurveID.CURVE25519,
_CurveID.CURVE448):
2024-01-27 16:53:23 +01:00
x = self.pointQ.x
result = "EccKey(curve='%s', point_x=%d%s)" % (self._curve.canonical, x, extra)
2024-01-27 16:53:23 +01:00
else:
x, y = self.pointQ.xy
result = "EccKey(curve='%s', point_x=%d, point_y=%d%s)" % (self._curve.canonical, x, y, extra)
2024-01-27 16:53:23 +01:00
return result
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
2022-04-15 00:15:48 +02:00
# ECDSA
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)
2022-04-15 00:15:48 +02:00
# ECDSA
2016-01-09 14:48:37 +01:00
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
2022-04-15 00:15:48 +02:00
@property
def seed(self):
if not self.has_private():
raise ValueError("This is not a private ECC key")
return self._seed
2016-01-02 15:33:28 -05:00
@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
"""
return EccKey(curve=self._curve.canonical, point=self.pointQ)
2016-01-09 14:24:32 +01:00
def _export_SEC1(self, compress):
if not self._curve.is_weierstrass:
2024-01-27 16:53:23 +01:00
raise ValueError("SEC1 format is only supported for NIST P curves")
2022-04-15 00:15:48 +02: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-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
2024-01-27 16:53:23 +01:00
def _export_eddsa_public(self):
2022-04-15 00:15:48 +02:00
x, y = self.pointQ.xy
if self._curve.id == _CurveID.ED25519:
2022-05-16 09:13:35 +02:00
result = bytearray(y.to_bytes(32, byteorder='little'))
result[31] = ((x & 1) << 7) | result[31]
elif self._curve.id == _CurveID.ED448:
2022-05-16 09:13:35 +02:00
result = bytearray(y.to_bytes(57, byteorder='little'))
result[56] = (x & 1) << 7
else:
raise ValueError("Not an EdDSA key to export")
2022-04-15 00:15:48 +02:00
return bytes(result)
2024-01-27 16:53:23 +01:00
def _export_montgomery_public(self):
if not self._curve.is_montgomery:
2024-01-27 16:53:23 +01:00
raise ValueError("Not a Montgomery key to export")
x = self.pointQ.x
2024-08-24 22:29:52 +02:00
field_size = self.pointQ.size_in_bytes()
result = bytearray(x.to_bytes(field_size, byteorder='little'))
2024-01-27 16:53:23 +01:00
return bytes(result)
def _export_subjectPublicKeyInfo(self, compress):
if self._curve.is_edwards:
2022-05-16 09:13:35 +02:00
oid = self._curve.oid
2024-01-27 16:53:23 +01:00
public_key = self._export_eddsa_public()
params = None
elif self._curve.is_montgomery:
2024-01-27 16:53:23 +01:00
oid = self._curve.oid
public_key = self._export_montgomery_public()
2022-04-15 00:15:48 +02:00
params = None
else:
oid = "1.2.840.10045.2.1" # unrestricted
public_key = self._export_SEC1(compress)
params = DerObjectId(self._curve.oid)
2016-01-27 08:44:43 +01:00
2022-04-15 00:15:48 +02:00
return _create_subject_public_key_info(oid,
2016-01-27 08:44:43 +01:00
public_key,
2022-04-15 00:15:48 +02:00
params)
2016-01-27 08:44:43 +01:00
2022-04-15 00:15:48 +02:00
def _export_rfc5915_private_der(self, include_ec_params=True):
2016-01-27 08:44:43 +01:00
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:
2024-01-07 17:28:50 +01:00
raise ValueError("At least the 'protection' parameter must be present")
2019-03-01 22:54:31 +01:00
2024-01-27 16:53:23 +01:00
if self._seed is not None:
2022-05-16 09:13:35 +02:00
oid = self._curve.oid
2022-04-15 00:15:48 +02:00
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)
2016-01-27 08:44:43 +01:00
result = PKCS8.wrap(private_key,
2022-04-15 00:15:48 +02:00
oid,
key_params=params,
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
2022-04-15 00:15:48 +02:00
encoded_der = self._export_rfc5915_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
2019-01-25 22:04:37 +01:00
2022-05-16 09:13:35 +02:00
if desc is None:
raise ValueError("Cannot export %s keys as OpenSSH" % self.curve)
2022-05-16 09:13:35 +02:00
elif desc == "ssh-ed25519":
2024-01-27 16:53:23 +01:00
public_key = self._export_eddsa_public()
2022-04-15 00:15:48 +02:00
comps = (tobytes(desc), tobytes(public_key))
else:
2022-04-15 00:15:48 +02:00
modulus_bytes = self.pointQ.size_in_bytes()
if compress:
first_byte = 2 + self.pointQ.y.is_odd()
public_key = (bchr(first_byte) +
2022-05-16 09:13:35 +02:00
self.pointQ.x.to_bytes(modulus_bytes))
2022-04-15 00:15:48 +02:00
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):
2024-01-07 17:28:50 +01:00
The output format:
2018-02-16 13:44:00 +01:00
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).
Only for NIST P-curves.
2022-06-22 19:52:38 +02:00
- ``'raw'``. The public key will be encoded as ``bytes``,
without any metadata.
* For NIST P-curves: equivalent to ``'SEC1'``.
2024-08-24 22:29:52 +02:00
* For Ed25519 and Ed448: ``bytes`` in the format
2024-01-27 16:53:23 +01:00
defined in `RFC8032`_.
2024-08-24 22:29:52 +02:00
* For Curve25519 and Curve448: ``bytes`` in the format
2024-01-27 16:53:23 +01:00
defined in `RFC7748`_.
2016-01-27 08:44:43 +01:00
2024-01-07 17:28:50 +01:00
passphrase (bytes or string):
(*Private keys only*) The passphrase to protect the
private key.
2016-01-27 08:44:43 +01:00
2017-08-14 23:41:07 +02:00
use_pkcs8 (boolean):
2024-01-07 17:28:50 +01:00
(*Private keys only*)
2017-08-15 15:22:35 +02:00
If ``True`` (default and recommended), the `PKCS#8`_ representation
2024-01-27 16:53:23 +01:00
will be used.
2024-08-24 22:29:52 +02:00
It must be ``True`` for Ed25519, Ed448, Curve25519, and Curve448.
2016-01-27 08:44:43 +01:00
2024-01-07 17:28:50 +01:00
If ``False`` and a passphrase is present, the obsolete PEM
encryption will be used.
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
2024-01-07 17:28:50 +01:00
present,
For all possible protection schemes,
refer to :ref:`the encryption parameters of PKCS#8<enc_params>`.
2024-09-24 10:25:16 +02:00
It is recommended to use ``'PBKDF2WithHMAC-SHA512AndAES128-CBC'``.
2016-01-27 08:44:43 +01:00
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.
2024-08-24 22:29:52 +02:00
This parameter is ignored for Ed25519/Ed448/Curve25519/Curve448,
as compression is mandatory.
2022-04-15 00:15:48 +02:00
2024-01-07 17:28:50 +01:00
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.
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
.. _OpenSSH: http://www.openssh.com/txt/rfc5656.txt
2018-02-16 13:44:00 +01:00
.. _RFC5480: https://tools.ietf.org/html/rfc5480
.. _SEC1: https://www.secg.org/sec1-v2.pdf
2024-01-27 16:53:23 +01:00
.. _RFC7748: https://tools.ietf.org/html/rfc7748
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'``, ``'SEC1'``, and ``'raw'``) 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", "raw"):
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")
2022-04-15 00:15:48 +02:00
2024-01-27 16:53:23 +01:00
use_pkcs8 = args.pop("use_pkcs8", True)
if use_pkcs8 is False:
if self._curve.is_edwards:
2024-01-07 17:28:50 +01:00
raise ValueError("'pkcs8' must be True for EdDSA curves")
if self._curve.is_montgomery:
2024-01-27 16:53:23 +01:00
raise ValueError("'pkcs8' must be True for Curve25519")
2024-01-07 17:28:50 +01:00
if 'protection' in args:
raise ValueError("'protection' is only supported for PKCS#8")
2022-04-15 00:15:48 +02:00
2016-01-27 08:44:43 +01:00
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:
2022-04-15 00:15:48 +02:00
return self._export_rfc5915_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)
elif ext_format == "raw":
if self._curve.is_edwards:
2024-01-27 16:53:23 +01:00
return self._export_eddsa_public()
elif self._curve.is_montgomery:
2024-01-27 16:53:23 +01:00
return self._export_montgomery_public()
else:
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):
Mandatory. It must be a curve name defined in the `ECC table`_.
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
if _curves[curve_name].id == _CurveID.ED25519:
2022-04-15 00:15:48 +02:00
seed = randfunc(32)
new_key = EccKey(curve=curve_name, seed=seed)
elif _curves[curve_name].id == _CurveID.ED448:
2022-05-16 09:13:35 +02:00
seed = randfunc(57)
new_key = EccKey(curve=curve_name, seed=seed)
elif _curves[curve_name].id == _CurveID.CURVE25519:
2024-06-23 22:31:40 +02:00
seed = randfunc(32)
new_key = EccKey(curve=curve_name, seed=seed)
2024-09-08 09:36:22 +02:00
_curves[curve_name].validate(new_key.pointQ)
2024-08-24 22:29:52 +02:00
elif _curves[curve_name].id == _CurveID.CURVE448:
seed = randfunc(56)
new_key = EccKey(curve=curve_name, seed=seed)
2024-09-08 09:36:22 +02:00
_curves[curve_name].validate(new_key.pointQ)
2022-04-15 00:15:48 +02:00
else:
d = Integer.random_range(min_inclusive=1,
max_exclusive=curve.order,
randfunc=randfunc)
new_key = EccKey(curve=curve_name, d=d)
2016-01-02 15:33:28 -05:00
2022-04-15 00:15:48 +02:00
return new_key
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.
2022-04-15 00:15:48 +02:00
In most cases, you will already have an existing key
which you can read in with :func:`import_key` instead
of this function.
2017-08-14 23:41:07 +02:00
2022-04-15 00:15:48 +02:00
Args:
2017-08-14 23:41:07 +02:00
curve (string):
Mandatory. The name of the elliptic curve, as defined in the `ECC table`_.
2017-08-14 23:41:07 +02:00
d (integer):
2024-01-27 16:53:23 +01:00
Mandatory for a private key and a NIST P-curve (e.g., P-256).
It must be an integer in the range ``[1..order-1]``.
2022-04-15 00:15:48 +02:00
seed (bytes):
2024-08-24 22:29:52 +02:00
Mandatory for a private key and curves Ed25519 (32 bytes),
Curve25519 (32 bytes), Curve448 (56 bytes) and Ed448 (57 bytes).
2017-08-14 23:41:07 +02:00
point_x (integer):
2024-01-27 16:53:23 +01:00
The X coordinate (affine) of the ECC point.
Mandatory for a public key.
2017-08-14 23:41:07 +02:00
point_y (integer):
2024-01-27 16:53:23 +01:00
The Y coordinate (affine) of the ECC point.
2024-08-24 22:29:52 +02:00
Mandatory for a public key,
except for Curve25519 and Curve448.
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")
if curve.id == _CurveID.CURVE25519:
2024-01-27 16:53:23 +01:00
if point_x is not None:
kwargs["point"] = EccXPoint(point_x, curve_name)
new_key = EccKey(**kwargs)
2024-09-08 09:36:22 +02:00
curve.validate(new_key.pointQ)
2024-06-23 22:31:40 +02:00
2024-08-24 22:29:52 +02:00
elif curve.id == _CurveID.CURVE448:
if point_x is not None:
kwargs["point"] = EccXPoint(point_x, curve_name)
new_key = EccKey(**kwargs)
2024-09-08 09:36:22 +02:00
curve.validate(new_key.pointQ)
2024-08-24 22:29:52 +02:00
2024-01-27 16:53:23 +01:00
else:
2016-01-09 22:18:11 +01:00
2024-01-27 16:53:23 +01:00
if None not in (point_x, point_y):
kwargs["point"] = EccPoint(point_x, point_y, curve_name)
new_key = EccKey(**kwargs)
2022-04-15 00:15:48 +02:00
2024-01-27 16:53:23 +01:00
# 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")
2022-04-15 00:15:48 +02:00
return new_key
2016-01-09 22:18:11 +01:00
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)
2022-04-15 00:15:48 +02:00
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
)
2022-05-16 09:13:35 +02:00
eddsa_oids = {
"1.3.101.112": ("Ed25519", _import_ed25519_public_key), # id-Ed25519
"1.3.101.113": ("Ed448", _import_ed448_public_key) # id-Ed448
}
2024-08-24 22:29:52 +02:00
xdh_oids = {
"1.3.101.110": ("Curve25519", _import_curve25519_public_key), # id-X25519
"1.3.101.111": ("Curve448", _import_curve448_public_key), # id-X448
}
2022-04-15 00:15:48 +02:00
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")
2016-01-17 23:04:11 +01:00
2022-04-15 00:15:48 +02:00
# ECPoint ::= OCTET STRING
return _import_public_der(ec_point, curve_oid=curve_oid)
2016-01-17 23:04:11 +01:00
2022-04-15 00:15:48 +02:00
elif oid in eddsa_oids:
# See RFC8410
2022-05-16 09:13:35 +02:00
curve_name, import_eddsa_public_key = eddsa_oids[oid]
2022-04-15 00:15:48 +02:00
# Parameters must be absent
if params:
raise ValueError("Unexpected ECC parameters for ECC OID %s" % oid)
2016-01-17 23:04:11 +01:00
2022-05-16 09:13:35 +02:00
x, y = import_eddsa_public_key(ec_point)
return construct(point_x=x, point_y=y, curve=curve_name)
2024-01-27 16:53:23 +01:00
2024-08-24 22:29:52 +02:00
elif oid in xdh_oids:
curve_name, import_xdh_public_key = xdh_oids[oid]
2024-01-27 16:53:23 +01:00
# Parameters must be absent
if params:
raise ValueError("Unexpected ECC parameters for ECC OID %s" % oid)
2024-08-24 22:29:52 +02:00
x = import_xdh_public_key(ec_point)
return construct(point_x=x, curve=curve_name)
2024-01-27 16:53:23 +01:00
2022-04-15 00:15:48 +02:00
else:
raise UnsupportedEccFeature("Unsupported ECC OID: %s" % oid)
2016-01-17 23:04:11 +01:00
2022-04-15 00:15:48 +02:00
def _import_rfc5915_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
# }
ec_private_key = DerSequence().decode(encoded, nr_elements=(2, 3, 4))
if ec_private_key[0] != 1:
2016-01-17 23:04:11 +01:00
raise ValueError("Incorrect ECC private key version")
scalar_bytes = DerOctetString().decode(ec_private_key[1]).payload
next_element = 2
# Try to decode 'parameters'
if next_element < len(ec_private_key):
try:
parameters = DerObjectId(explicit=0).decode(ec_private_key[next_element]).value
if curve_oid is not None and parameters != curve_oid:
raise ValueError("Curve mismatch")
curve_oid = parameters
next_element += 1
except ValueError:
pass
2019-03-02 21:43:28 +01:00
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)
modulus_bytes = curve.p.size_in_bytes()
if len(scalar_bytes) != modulus_bytes:
raise ValueError("Private key is too small")
2016-01-17 23:04:11 +01:00
# Try to decode 'publicKey'
point_x = point_y = None
if next_element < len(ec_private_key):
try:
public_key_enc = DerBitString(explicit=1).decode(ec_private_key[next_element]).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
next_element += 1
except ValueError:
pass
2016-01-17 23:04:11 +01:00
d = Integer.from_bytes(scalar_bytes)
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
algo_oid, private_key, params = PKCS8.unwrap(encoded, passphrase)
2022-04-15 00:15:48 +02:00
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
)
2022-05-16 09:13:35 +02:00
eddsa_oids = {
"1.3.101.112": "Ed25519", # id-Ed25519
"1.3.101.113": "Ed448", # id-Ed448
}
2024-01-27 16:53:23 +01:00
xdh_oids = {
"1.3.101.110": "Curve25519", # id-X25519
2024-08-24 22:29:52 +02:00
"1.3.101.111": "Curve448", # id-X448
2024-01-27 16:53:23 +01:00
}
2022-04-15 00:15:48 +02:00
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
2022-05-16 09:13:35 +02:00
return construct(curve=eddsa_oids[algo_oid], seed=seed)
2024-01-27 16:53:23 +01:00
elif algo_oid in xdh_oids:
2024-08-24 22:29:52 +02:00
curve_name = xdh_oids[algo_oid]
2024-01-27 16:53:23 +01:00
if params is not None:
2024-08-24 22:29:52 +02:00
raise ValueError("%s ECC private key must not have parameters" %
curve_name)
2024-01-27 16:53:23 +01:00
curve_oid = None
seed = DerOctetString().decode(private_key).payload
return construct(curve=xdh_oids[algo_oid], seed=seed)
2022-04-15 00:15:48 +02:00
else:
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
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:
2022-04-15 00:15:48 +02:00
return _import_rfc5915_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):
2022-04-15 00:15:48 +02:00
parts = encoded.split(b' ')
if len(parts) not in (2, 3):
raise ValueError("Not an openssh public key")
2016-01-26 22:05:02 +01:00
2022-04-15 00:15:48 +02:00
try:
keystring = binascii.a2b_base64(parts[1])
2016-01-26 22:05:02 +01:00
2022-04-15 00:15:48 +02:00
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-"):
2016-01-26 22:05:02 +01:00
2022-04-15 00:15:48 +02:00
for curve_name, curve in _curves.items():
2022-06-05 13:51:03 +02:00
if curve.openssh is None:
continue
if not curve.openssh.startswith("ecdsa-sha2"):
continue
2022-04-15 00:15:48 +02:00
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
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
2022-04-15 00:15:48 +02:00
key_type, decrypted = import_openssh_private_generic(data, password)
2022-05-16 09:13:35 +02:00
eddsa_keys = {
"ssh-ed25519": ("Ed25519", _import_ed25519_public_key, 32),
}
2022-04-15 00:15:48 +02:00
# https://datatracker.ietf.org/doc/html/draft-miller-ssh-agent-04
if key_type.startswith("ecdsa-sha2"):
2019-08-17 09:02:23 +02:00
2022-04-15 00:15:48 +02:00
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
2019-08-17 09:02:23 +02:00
2022-04-15 00:15:48 +02:00
public_key, decrypted = read_bytes(decrypted)
2019-08-22 23:16:56 +02:00
2022-04-15 00:15:48 +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
2022-04-15 00:15:48 +02:00
point_x = Integer.from_bytes(public_key[1:1+modulus_bytes])
point_y = Integer.from_bytes(public_key[1+modulus_bytes:])
2019-08-17 09:02:23 +02:00
2022-04-15 00:15:48 +02:00
private_key, decrypted = read_bytes(decrypted)
d = Integer.from_bytes(private_key)
params = {'d': d, 'curve': ecdsa_curve_name}
2022-05-16 09:13:35 +02:00
elif key_type in eddsa_keys:
curve_name, import_eddsa_public_key, seed_len = eddsa_keys[key_type]
2022-04-15 00:15:48 +02:00
public_key, decrypted = read_bytes(decrypted)
2022-05-16 09:13:35 +02:00
point_x, point_y = import_eddsa_public_key(public_key)
2022-04-15 00:15:48 +02:00
private_public_key, decrypted = read_bytes(decrypted)
2022-05-16 09:13:35 +02:00
seed = private_public_key[:seed_len]
2022-04-15 00:15:48 +02:00
2022-05-16 09:13:35 +02:00
params = {'seed': seed, 'curve': curve_name}
2022-04-15 00:15:48 +02:00
else:
raise ValueError("Unsupport SSH agent key type:" + key_type)
2019-08-17 09:02:23 +02:00
_, padded = read_string(decrypted) # Comment
check_padding(padded)
2022-04-15 00:15:48 +02:00
return construct(point_x=point_x, point_y=point_y, **params)
def _import_ed25519_public_key(encoded):
2022-05-16 09:13:35 +02:00
"""Import an Ed25519 ECC public key, encoded as raw bytes as described
2022-04-15 00:15:48 +02:00
in RFC8032_.
Args:
encoded (bytes):
2022-05-16 09:13:35 +02:00
The Ed25519 public key to import. It must be 32 bytes long.
2022-04-15 00:15:48 +02:00
Returns:
2024-01-27 16:53:23 +01:00
x and y (integer)
2022-04-15 00:15:48 +02:00
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
2022-05-16 09:13:35 +02:00
point_y = Integer.from_bytes(y, byteorder='little')
2022-04-15 00:15:48 +02:00
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:
2022-05-16 09:13:35 +02:00
v_inv = v.inverse(p)
x2 = (u * v_inv) % p
2022-04-15 00:15:48 +02:00
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")
2022-05-16 09:13:35 +02:00
return point_x, point_y
2024-01-27 16:53:23 +01:00
def _import_curve25519_public_key(encoded):
"""Import a Curve25519 ECC public key,
encoded as raw bytes as described in RFC7748_.
Args:
encoded (bytes):
The Curve25519 public key to import. It must be 32 bytes long.
Returns:
x (integer)
Raises:
ValueError: when the given key cannot be parsed.
.. _RFC7748: https://datatracker.ietf.org/doc/html/rfc7748
"""
if len(encoded) != 32:
2024-08-24 22:29:52 +02:00
raise ValueError("Incorrect Curve25519 key length")
2024-01-27 16:53:23 +01:00
x = bytearray(encoded)
2024-06-23 22:31:40 +02:00
# RFC 7741, Section 5
2024-01-27 16:53:23 +01:00
x[31] &= 0x7F
point_x = Integer.from_bytes(x, byteorder='little')
return point_x
2024-08-24 22:29:52 +02:00
def _import_curve448_public_key(encoded):
"""Import a Curve448 ECC public key,
encoded as raw bytes as described in RFC7748_.
Args:
encoded (bytes):
The Curve448 public key to import. It must be 56 bytes long.
Returns:
x (integer)
Raises:
ValueError: when the given key cannot be parsed.
.. _RFC7748: https://datatracker.ietf.org/doc/html/rfc7748
"""
if len(encoded) != 56:
raise ValueError("Incorrect Curve448 key length")
point_x = Integer.from_bytes(encoded, byteorder='little')
return point_x
2022-05-16 09:13:35 +02:00
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:
2024-01-27 16:53:23 +01:00
x and y (integer)
2022-05-16 09:13:35 +02:00
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.")
2024-08-24 22:29:52 +02:00
p = _curves['curve448'].p
d = p - 39081
2022-05-16 09:13:35 +02:00
y = encoded[:56]
2022-06-05 13:41:33 +02:00
x_lsb = bord(encoded[56]) >> 7
2022-05-16 09:13:35 +02:00
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
2019-08-17 09:02:23 +02:00
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.
2022-04-15 00:15:48 +02:00
The function will try to automatically detect the right format.
2016-01-27 08:00:28 +01:00
2022-04-15 00:15:48 +02:00
Supported formats for an ECC **public** key:
2016-01-31 23:27:55 +01:00
* 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).
2022-04-15 00:15:48 +02:00
This is normally the content of files like ``~/.ssh/id_ecdsa.pub``.
2016-01-31 23:27:55 +01:00
2022-04-15 00:15:48 +02:00
Supported formats for an ECC **private** key:
2016-01-27 08:00:28 +01:00
* A binary ``ECPrivateKey`` structure, as defined in `RFC5915`_ (DER).
2022-04-15 00:15:48 +02:00
NIST P curves only.
2024-08-24 22:29:52 +02:00
* A `PKCS#8`_ structure (or the more recent Asymmetric Key
Package, RFC5958_): binary (DER) or ASCII (PEM).
* `OpenSSH 6.5`_ and newer versions (ASCII).
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.
2022-04-15 00:15:48 +02:00
Encryption may be applied protected at the PEM level (not recommended)
or at the PKCS#8 level (recommended).
2016-01-31 23:27:55 +01:00
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 encoding only. This is the name of the curve,
as defined in the `ECC table`_.
2022-06-22 19:52:38 +02:00
.. 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`.
2022-04-15 00:15:48 +02:00
.. note::
2024-08-24 22:29:52 +02:00
To import X25519/X448 private and public keys, when encoded as raw ``bytes``, use:
2024-08-24 22:29:52 +02:00
* :func:`Crypto.Protocol.DH.import_x25519_public_key`
* :func:`Crypto.Protocol.DH.import_x25519_private_key`
* :func:`Crypto.Protocol.DH.import_x448_public_key`
* :func:`Crypto.Protocol.DH.import_x448_private_key`
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).
2022-04-15 00:15:48 +02:00
.. _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
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, "",
2022-04-15 00:15:48 +02:00
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
2022-05-16 09:13:35 +02:00
if encoded.startswith((b'ecdsa-sha2-', b'ssh-ed25519')):
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
2023-08-26 18:57:49 +02:00
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)
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")