/usr/lib/python3/dist-packages/Cryptodome/PublicKey/ECC.py is in python3-pycryptodome 3.4.7-1ubuntu1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 | # ===================================================================
#
# 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.
# ===================================================================
import struct
import binascii
from Cryptodome.Util.py3compat import bord, tobytes, b, tostr, bchr
from Cryptodome.Math.Numbers import Integer
from Cryptodome.Random import get_random_bytes
from Cryptodome.Util.asn1 import (DerObjectId, DerOctetString, DerSequence,
DerBitString)
from Cryptodome.IO import PKCS8, PEM
from Cryptodome.PublicKey import (_expand_subject_public_key_info,
_create_subject_public_key_info,
_extract_subject_public_key_info)
class _Curve(object):
pass
_curve = _Curve()
_curve.p = Integer(0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff)
_curve.b = Integer(0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b)
_curve.order = Integer(0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551)
_curve.Gx = Integer(0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296)
_curve.Gy = Integer(0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5)
_curve.names = ("P-256", "prime256v1", "secp256r1")
_curve.oid = "1.2.840.10045.3.1.7"
class EccPoint(object):
"""A class to abstract a point over an Elliptic Curve.
:ivar x: The X-coordinate of the ECC point
:vartype x: integer
:ivar y: The Y-coordinate of the ECC point
:vartype y: integer
"""
def __init__(self, x, y):
self._x = Integer(x)
self._y = Integer(y)
# Buffers
self._common = Integer(0)
self._tmp1 = Integer(0)
self._x3 = Integer(0)
self._y3 = Integer(0)
def set(self, point):
self._x = Integer(point._x)
self._y = Integer(point._y)
return self
def __eq__(self, point):
return self._x == point._x and self._y == point._y
def __neg__(self):
if self.is_point_at_infinity():
return self.point_at_infinity()
return EccPoint(self._x, _curve.p - self._y)
def copy(self):
return EccPoint(self._x, self._y)
def is_point_at_infinity(self):
return not (self._x or self._y)
@staticmethod
def point_at_infinity():
return EccPoint(0, 0)
@property
def x(self):
if self.is_point_at_infinity():
raise ValueError("Point at infinity")
return self._x
@property
def y(self):
if self.is_point_at_infinity():
raise ValueError("Point at infinity")
return self._y
def double(self):
"""Double this point (in-place operation).
:Return:
:class:`EccPoint` : this same object (to enable chaining)
"""
if not self._y:
return self.point_at_infinity()
common = self._common
tmp1 = self._tmp1
x3 = self._x3
y3 = self._y3
# common = (pow(self._x, 2, _curve.p) * 3 - 3) * (self._y << 1).inverse(_curve.p) % _curve.p
common.set(self._x)
common.inplace_pow(2, _curve.p)
common *= 3
common -= 3
tmp1.set(self._y)
tmp1 <<= 1
tmp1.inplace_inverse(_curve.p)
common *= tmp1
common %= _curve.p
# x3 = (pow(common, 2, _curve.p) - 2 * self._x) % _curve.p
x3.set(common)
x3.inplace_pow(2, _curve.p)
x3 -= self._x
x3 -= self._x
while x3.is_negative():
x3 += _curve.p
# y3 = ((self._x - x3) * common - self._y) % _curve.p
y3.set(self._x)
y3 -= x3
y3 *= common
y3 -= self._y
y3 %= _curve.p
self._x.set(x3)
self._y.set(y3)
return self
def __iadd__(self, point):
"""Add a second point to this one"""
if self.is_point_at_infinity():
return self.set(point)
if point.is_point_at_infinity():
return self
if self == point:
return self.double()
if self._x == point._x:
return self.set(self.point_at_infinity())
common = self._common
tmp1 = self._tmp1
x3 = self._x3
y3 = self._y3
# common = (point._y - self._y) * (point._x - self._x).inverse(_curve.p) % _curve.p
common.set(point._y)
common -= self._y
tmp1.set(point._x)
tmp1 -= self._x
tmp1.inplace_inverse(_curve.p)
common *= tmp1
common %= _curve.p
# x3 = (pow(common, 2, _curve.p) - self._x - point._x) % _curve.p
x3.set(common)
x3.inplace_pow(2, _curve.p)
x3 -= self._x
x3 -= point._x
while x3.is_negative():
x3 += _curve.p
# y3 = ((self._x - x3) * common - self._y) % _curve.p
y3.set(self._x)
y3 -= x3
y3 *= common
y3 -= self._y
y3 %= _curve.p
self._x.set(x3)
self._y.set(y3)
return self
def __add__(self, point):
"""Return a new point, the addition of this one and another"""
result = self.copy()
result += point
return result
def __mul__(self, scalar):
"""Return a new point, the scalar product of this one"""
if scalar < 0:
raise ValueError("Scalar multiplication only defined for non-negative integers")
# Trivial results
if scalar == 0 or self.is_point_at_infinity():
return self.point_at_infinity()
elif scalar == 1:
return self.copy()
# Scalar randomization
scalar_blind = Integer.random(exact_bits=64) * _curve.order + scalar
# Montgomery key ladder
r = [self.point_at_infinity().copy(), self.copy()]
bit_size = int(scalar_blind.size_in_bits())
scalar_int = int(scalar_blind)
for i in range(bit_size, -1, -1):
di = scalar_int >> i & 1
r[di ^ 1] += r[di]
r[di].double()
return r[0]
_curve.G = EccPoint(_curve.Gx, _curve.Gy)
class EccKey(object):
r"""Class defining an ECC key.
Do not instantiate directly.
Use :func:`generate`, :func:`construct` or :func:`import_key` instead.
:ivar curve: The name of the ECC curve
:vartype curve: string
:ivar pointQ: an ECC point representating the public component
:vartype pointQ: :class:`EccPoint`
:ivar q: A scalar representating the private component
:vartype q: integer
"""
def __init__(self, **kwargs):
"""Create a new ECC key
Keywords:
curve : string
It must be *"P-256"*, *"prime256v1"* or *"secp256r1"*.
d : integer
Only for a private key. It must be in the range ``[1..order-1]``.
point : EccPoint
Mandatory for a public key. If provided for a private key,
the implementation will NOT check whether it matches ``d``.
"""
kwargs_ = dict(kwargs)
self.curve = kwargs_.pop("curve", None)
self._d = kwargs_.pop("d", None)
self._point = kwargs_.pop("point", None)
if kwargs_:
raise TypeError("Unknown parameters: " + str(kwargs_))
if self.curve not in _curve.names:
raise ValueError("Unsupported curve (%s)", self.curve)
if self._d is None:
if self._point is None:
raise ValueError("Either private or public ECC component must be specified")
else:
self._d = Integer(self._d)
if not 1 <= self._d < _curve.order:
raise ValueError("Invalid ECC private component")
def __eq__(self, other):
if other.has_private() != self.has_private():
return False
return (other.pointQ.x == self.pointQ.x) and (other.pointQ.y == self.pointQ.y)
def __repr__(self):
if self.has_private():
extra = ", d=%d" % int(self._d)
else:
extra = ""
return "EccKey(curve='P-256', x=%d, y=%d%s)" %\
(self.pointQ.x, self.pointQ.y, extra)
def has_private(self):
"""``True`` if this key can be used for making signatures or decrypting data."""
return self._d is not None
def _sign(self, z, k):
assert 0 < k < _curve.order
blind = Integer.random_range(min_inclusive=1,
max_exclusive=_curve.order)
blind_d = self._d * blind
inv_blind_k = (blind * k).inverse(_curve.order)
r = (_curve.G * k).x % _curve.order
s = inv_blind_k * (blind * z + blind_d * r) % _curve.order
return (r, s)
def _verify(self, z, rs):
sinv = rs[1].inverse(_curve.order)
point1 = _curve.G * ((sinv * z) % _curve.order)
point2 = self.pointQ * ((sinv * rs[0]) % _curve.order)
return (point1 + point2).x == rs[0]
@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:
self._point = _curve.G * self._d
return self._point
def public_key(self):
"""A matching ECC public key.
Returns:
a new :class:`EccKey` object
"""
return EccKey(curve="P-256", point=self.pointQ)
def _export_subjectPublicKeyInfo(self):
# Uncompressed form
order_bytes = _curve.order.size_in_bytes()
public_key = (bchr(4) +
self.pointQ.x.to_bytes(order_bytes) +
self.pointQ.y.to_bytes(order_bytes))
unrestricted_oid = "1.2.840.10045.2.1"
return _create_subject_public_key_info(unrestricted_oid,
public_key,
DerObjectId(_curve.oid))
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
order_bytes = _curve.order.size_in_bytes()
public_key = (bchr(4) +
self.pointQ.x.to_bytes(order_bytes) +
self.pointQ.y.to_bytes(order_bytes))
seq = [1,
DerOctetString(self.d.to_bytes(order_bytes)),
DerObjectId(_curve.oid, explicit=0),
DerBitString(public_key, explicit=1)]
if not include_ec_params:
del seq[2]
return DerSequence(seq).encode()
def _export_pkcs8(self, **kwargs):
if kwargs.get('passphrase', None) is not None and 'protection' not in kwargs:
raise ValueError("At least the 'protection' parameter should be present")
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,
key_params=DerObjectId(_curve.oid),
**kwargs)
return result
def _export_public_pem(self):
encoded_der = self._export_subjectPublicKeyInfo()
return PEM.encode(encoded_der, "PUBLIC KEY")
def _export_private_pem(self, passphrase, **kwargs):
encoded_der = self._export_private_der()
return PEM.encode(encoded_der, "EC PRIVATE KEY", passphrase, **kwargs)
def _export_private_clear_pkcs8_in_clear_pem(self):
encoded_der = self._export_pkcs8()
return PEM.encode(encoded_der, "PRIVATE KEY")
def _export_private_encrypted_pkcs8_in_clear_pem(self, passphrase, **kwargs):
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):
assert not self.has_private()
desc = "ecdsa-sha2-nistp256"
# Uncompressed form
order_bytes = _curve.order.size_in_bytes()
public_key = (bchr(4) +
self.pointQ.x.to_bytes(order_bytes) +
self.pointQ.y.to_bytes(order_bytes))
comps = (tobytes(desc), b("nistp256"), public_key)
blob = b("").join([ struct.pack(">I", len(x)) + x for x in comps])
return desc + " " + tostr(binascii.b2a_base64(blob))
def export_key(self, **kwargs):
"""Export this ECC key.
Args:
format (string):
The format to use for wrapping the key:
- *'DER'*. The key will be encoded in an ASN.1 DER_ structure (binary).
- *'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).
passphrase (byte string or string):
The passphrase to use for protecting the private key.
use_pkcs8 (boolean):
If ``True`` (default and recommended), the `PKCS#8`_ representation
will be used.
If ``False``, the much weaker and `PEM encryption`_ mechanism will be used.
protection (string):
When a private key is exported with password-protection
and PKCS#8 (both ``DER`` and ``PEM`` formats), this parameter MUST be
present and be a valid algorithm supported by :mod:`Cryptodome.IO.PKCS8`.
It is recommended to use ``PBKDF2WithHMAC-SHA1AndAES128-CBC``.
.. warning::
If you don't provide a passphrase, the private key will be
exported in the clear!
.. note::
When exporting a private key with password-protection and `PKCS#8`_
(both ``DER`` and ``PEM`` formats), any extra parameters
is passed to :mod:`Cryptodome.IO.PKCS8`.
.. _DER: http://www.ietf.org/rfc/rfc5915.txt
.. _PEM: http://www.ietf.org/rfc/rfc1421.txt
.. _`PEM encryption`: http://www.ietf.org/rfc/rfc1423.txt
.. _`PKCS#8`: http://www.ietf.org/rfc/rfc5208.txt
.. _OpenSSH: http://www.openssh.com/txt/rfc5656.txt
Returns:
A multi-line string (for PEM and OpenSSH) or bytes (for DER) with the encoded key.
"""
args = kwargs.copy()
ext_format = args.pop("format")
if ext_format not in ("PEM", "DER", "OpenSSH"):
raise ValueError("Unknown format '%s'" % ext_format)
if self.has_private():
passphrase = args.pop("passphrase", None)
if isinstance(passphrase, str):
passphrase = tobytes(passphrase)
if not passphrase:
raise ValueError("Empty passphrase")
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":
# 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 OpenSSH format")
else: # Public key
if args:
raise ValueError("Unexpected parameters: '%s'" % args)
if ext_format == "PEM":
return self._export_public_pem()
elif ext_format == "DER":
return self._export_subjectPublicKeyInfo()
else:
return self._export_openssh()
def generate(**kwargs):
"""Generate a new private key on the given curve.
Args:
curve (string):
Mandatory. It must be "P-256", "prime256v1" or "secp256r1".
randfunc (callable):
Optional. The RNG to read randomness from.
If ``None``, :func:`Cryptodome.Random.get_random_bytes` is used.
"""
curve = kwargs.pop("curve")
randfunc = kwargs.pop("randfunc", get_random_bytes)
if kwargs:
raise TypeError("Unknown parameters: " + str(kwargs))
d = Integer.random_range(min_inclusive=1,
max_exclusive=_curve.order,
randfunc=randfunc)
return EccKey(curve=curve, d=d)
def construct(**kwargs):
"""Build a new ECC key (private or public) starting
from some base components.
Args:
curve (string):
Mandatory. It must be "P-256", "prime256v1" or "secp256r1".
d (integer):
Only for a private key. It must be in the range ``[1..order-1]``.
point_x (integer):
Mandatory for a public key. X coordinate (affine) of the ECC point.
point_y (integer):
Mandatory for a public key. Y coordinate (affine) of the ECC point.
Returns:
:class:`EccKey` : a new ECC key object
"""
point_x = kwargs.pop("point_x", None)
point_y = kwargs.pop("point_y", None)
if "point" in kwargs:
raise TypeError("Unknown keyword: point")
if None not in (point_x, point_y):
kwargs["point"] = EccPoint(point_x, point_y)
# Validate that the point is on the P-256 curve
eq1 = pow(Integer(point_y), 2, _curve.p)
x = Integer(point_x)
eq2 = pow(x, 3, _curve.p)
x *= -3
eq2 += x
eq2 += _curve.b
eq2 %= _curve.p
if eq1 != eq2:
raise ValueError("The point is not on the curve")
# Validate that the private key matches the public one
d = kwargs.get("d", None)
if d is not None and "point" in kwargs:
pub_key = _curve.G * d
if pub_key.x != point_x or pub_key.y != point_y:
raise ValueError("Private and public ECC keys do not match")
return EccKey(**kwargs)
def _import_public_der(curve_name, publickey):
# We only support P-256 named curves for now
if curve_name != _curve.oid:
raise ValueError("Unsupport curve")
# ECPoint ::= OCTET STRING
# We support only uncompressed points
order_bytes = _curve.order.size_in_bytes()
if len(publickey) != (1 + 2 * order_bytes) or bord(publickey[0]) != 4:
raise ValueError("Only uncompressed points are supported")
point_x = Integer.from_bytes(publickey[1:order_bytes+1])
point_y = Integer.from_bytes(publickey[order_bytes+1:])
return construct(curve="P-256", point_x=point_x, point_y=point_y)
def _import_subjectPublicKeyInfo(encoded, *kwargs):
oid, encoded_key, params = _expand_subject_public_key_info(encoded)
# 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 oid not in (unrestricted_oid, ecdh_oid, ecmqv_oid) or not params:
raise ValueError("Invalid ECC OID")
# ECParameters ::= CHOICE {
# namedCurve OBJECT IDENTIFIER
# -- implicitCurve NULL
# -- specifiedCurve SpecifiedECDomain
# }
curve_name = DerObjectId().decode(params).value
return _import_public_der(curve_name, encoded_key)
def _import_private_der(encoded, passphrase, curve_name=None):
# 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")
scalar_bytes = DerOctetString().decode(private_key[1]).payload
order_bytes = _curve.order.size_in_bytes()
if len(scalar_bytes) != order_bytes:
raise ValueError("Private key is too small")
d = Integer.from_bytes(scalar_bytes)
try:
curve_name = DerObjectId(explicit=0).decode(private_key[2]).value
except ValueError:
pass
if curve_name != _curve.oid:
raise ValueError("Unsupport curve")
# Decode public key (if any, it must be P-256)
if len(private_key) == 4:
public_key_enc = DerBitString(explicit=1).decode(private_key[3]).value
public_key = _import_public_der(curve_name, public_key_enc)
point_x = public_key.pointQ.x
point_y = public_key.pointQ.y
else:
point_x = point_y = None
return construct(curve="P-256", d=d, point_x=point_x, point_y=point_y)
def _import_pkcs8(encoded, passphrase):
# 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):
raise ValueError("No PKCS#8 encoded ECC key")
curve_name = DerObjectId().decode(params).value
return _import_private_der(private_key, passphrase, curve_name)
def _import_x509_cert(encoded, *kwargs):
sp_info = _extract_subject_public_key_info(encoded)
return _import_subjectPublicKeyInfo(sp_info)
def _import_der(encoded, passphrase):
decodings = (
_import_subjectPublicKeyInfo,
_import_x509_cert,
_import_private_der,
_import_pkcs8,
)
for decoding in decodings:
try:
return decoding(encoded, passphrase)
except (ValueError, TypeError, IndexError):
pass
raise ValueError("Not an ECC DER key")
def _import_openssh(encoded):
keystring = binascii.a2b_base64(encoded.split(b(' '))[1])
keyparts = []
while len(keystring) > 4:
l = struct.unpack(">I", keystring[:4])[0]
keyparts.append(keystring[4:4 + l])
keystring = keystring[4 + l:]
if keyparts[1] != b("nistp256"):
raise ValueError("Unsupported ECC curve")
return _import_public_der(_curve.oid, keyparts[2])
def import_key(encoded, passphrase=None):
"""Import an ECC key (public or private).
Args:
encoded (bytes or multi-line string):
The ECC key to import.
An ECC **public** key can be:
- An X.509 certificate, binary (DER) or ASCII (PEM)
- An X.509 ``subjectPublicKeyInfo``, binary (DER) or ASCII (PEM)
- An OpenSSH line (e.g. the content of ``~/.ssh/id_ecdsa``, ASCII)
An ECC **private** key can be:
- In binary format (DER, see section 3 of `RFC5915`_ or `PKCS#8`_)
- In ASCII format (PEM or OpenSSH)
Private keys can be in the clear or password-protected.
For details about the PEM encoding, see `RFC1421`_/`RFC1423`_.
passphrase (byte string):
The passphrase to use for decrypting a private key.
Encryption may be applied protected at the PEM level or at the PKCS#8 level.
This parameter is ignored if the key in input is not encrypted.
Returns:
:class:`EccKey` : a new ECC key object
Raises:
ValueError: when the given key cannot be parsed (possibly because
the pass phrase is wrong).
.. _RFC1421: 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
"""
encoded = tobytes(encoded)
if passphrase is not None:
passphrase = tobytes(passphrase)
# PEM
if encoded.startswith(b('-----')):
der_encoded, marker, enc_flag = PEM.decode(tostr(encoded), passphrase)
if enc_flag:
passphrase = None
return _import_der(der_encoded, passphrase)
# OpenSSH
if encoded.startswith(b('ecdsa-sha2-')):
return _import_openssh(encoded)
# DER
if bord(encoded[0]) == 0x30:
return _import_der(encoded, passphrase)
raise ValueError("ECC key format is not supported")
if __name__ == "__main__":
import time
d = 0xc51e4753afdec1e6b6c6a5b992f43f8dd0c7a8933072708b6522468b2ffb06fd
point = generate(curve="P-256").pointQ
start = time.time()
count = 30
for x in range(count):
_ = point * d
print((time.time() - start) / count * 1000, "ms")
|