Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable explicit ECC curve parameters export #368

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Mark Moraes
Lim Chee Siang
Bryan Olson
Wallace Owen
Sylvain Pelissier
Colin Plumb
Robey Pointer
Lorenz Quack
Expand Down
154 changes: 154 additions & 0 deletions lib/Crypto/Experimental/ECC.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# ===================================================================
#
# Copyright (c) 2019, Sylvain Pelissier <sylvain.pelissier@gmail.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ===================================================================
from Crypto.Math.Numbers import Integer
from Crypto.PublicKey.ECC import (EccKey, _curves, EccPoint)
from Crypto.Util.asn1 import (DerObjectId, DerOctetString, DerSequence,
DerBitString)

from Crypto.Random import get_random_bytes


class EccKeyExplicit(EccKey):
r"""Class defining an ECC key supporting the explicit curve parameters export.
"""

def __init__(self, **kwargs):
EccKey.__init__(self, **kwargs)

def _export_private_der(self, **kwargs):

assert self.has_private()

# ECPrivateKey ::= SEQUENCE {
# version INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
# privateKey OCTET STRING,
# parameters [0] ECParameters,
# publicKey [1] BIT STRING OPTIONAL
# }

# Public key - uncompressed form
args = kwargs.copy()
include_ec_params = args.pop("include_ec_params", True)
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))

order = int(self._curve.order)
p = int(self._curve.p)
generator = (b'\x04' +
self._curve.G.x.to_bytes(modulus_bytes) +
self._curve.G.y.to_bytes(modulus_bytes))
field_parameters = DerSequence([DerObjectId("1.2.840.10045.1.1"), p])
parameters = [DerSequence([1, field_parameters,
DerSequence([
DerOctetString(self._curve.a.to_bytes(modulus_bytes)),
DerOctetString(self._curve.b.to_bytes(modulus_bytes))]),
DerOctetString(generator),
order,
1
])]
seq = [1,
DerOctetString(self.d.to_bytes(modulus_bytes)),
DerSequence(parameters, implicit=0),
DerBitString(public_key, explicit=1)]

if not include_ec_params:
del seq[2]

return DerSequence(seq).encode()

def generate(**kwargs):
"""Generate a new private key on the given curve.

Args:

curve (string):
Mandatory. It must be a curve name defined in :numref:`curve_names`.

randfunc (callable):
Optional. The RNG to read randomness from.
If ``None``, :func:`Crypto.Random.get_random_bytes` is used.
"""

curve_name = kwargs.pop("curve")
curve = _curves[curve_name]
randfunc = kwargs.pop("randfunc", get_random_bytes)
if kwargs:
raise TypeError("Unknown parameters: " + str(kwargs))

d = Integer.random_range(min_inclusive=1,
max_exclusive=curve.order,
randfunc=randfunc)

return EccKeyExplicit(curve=curve_name, 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 a curve name defined in :numref:`curve_names`.

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
"""

curve_name = kwargs["curve"]
curve = _curves[curve_name]
point_x = kwargs.pop("point_x", None)
point_y = kwargs.pop("point_y", None)

if "point" in kwargs:
raise TypeError("Unknown keyword: point")

if None not in (point_x, point_y):
# ValueError is raised if the point is not on the curve
kwargs["point"] = EccPoint(point_x, point_y, curve_name)

# 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.xy != (point_x, point_y):
raise ValueError("Private and public ECC keys do not match")

return EccKeyExplicit(**kwargs)
Empty file.
23 changes: 16 additions & 7 deletions lib/Crypto/PublicKey/ECC.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
int ec_ws_is_pai(EcPoint *ecp);
""")

_Curve = namedtuple("_Curve", "p b order Gx Gy G modulus_bits oid context desc openssh")
_Curve = namedtuple("_Curve", "p a b order Gx Gy G modulus_bits oid context desc openssh")
_curves = {}


Expand All @@ -99,6 +99,7 @@

def init_p256():
p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff
a = p - 3
b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b
order = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551
Gx = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296
Expand All @@ -121,6 +122,7 @@ def init_p256():

context = SmartPointer(ec_p256_context.get(), _ec_lib.ec_free_context)
p256 = _Curve(Integer(p),
Integer(a),
Integer(b),
Integer(order),
Integer(Gx),
Expand All @@ -145,6 +147,7 @@ def init_p256():

def init_p384():
p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000ffffffff
a = p - 3
b = 0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aef
order = 0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196accc52973
Gx = 0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760aB7
Expand All @@ -167,6 +170,7 @@ def init_p384():

context = SmartPointer(ec_p384_context.get(), _ec_lib.ec_free_context)
p384 = _Curve(Integer(p),
Integer(a),
Integer(b),
Integer(order),
Integer(Gx),
Expand All @@ -191,6 +195,7 @@ def init_p384():

def init_p521():
p = 0x000001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
a = p - 3
b = 0x00000051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00
order = 0x000001fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc0148f709a5d03bb5c9b8899c47aebb6fb71e91386409
Gx = 0x000000c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66
Expand All @@ -213,6 +218,7 @@ def init_p521():

context = SmartPointer(ec_p521_context.get(), _ec_lib.ec_free_context)
p521 = _Curve(Integer(p),
Integer(a),
Integer(b),
Integer(order),
Integer(Gx),
Expand Down Expand Up @@ -560,7 +566,7 @@ def _export_subjectPublicKeyInfo(self, compress):
public_key,
DerObjectId(self._curve.oid))

def _export_private_der(self, include_ec_params=True):
def _export_private_der(self, **kwargs):

assert self.has_private()

Expand All @@ -572,15 +578,17 @@ def _export_private_der(self, include_ec_params=True):
# }

# Public key - uncompressed form
args = kwargs.copy()
include_ec_params = args.pop("include_ec_params", True)
modulus_bytes = self.pointQ.size_in_bytes()
public_key = (b'\x04' +
self.pointQ.x.to_bytes(modulus_bytes) +
self.pointQ.y.to_bytes(modulus_bytes))

seq = [1,
DerOctetString(self.d.to_bytes(modulus_bytes)),
DerObjectId(self._curve.oid, explicit=0),
DerBitString(public_key, explicit=1)]
DerOctetString(self.d.to_bytes(modulus_bytes)),
DerObjectId(self._curve.oid, explicit=0),
DerBitString(public_key, explicit=1)]

if not include_ec_params:
del seq[2]
Expand Down Expand Up @@ -610,7 +618,7 @@ def _export_public_pem(self, compress):
def _export_private_pem(self, passphrase, **kwargs):
from Crypto.IO import PEM

encoded_der = self._export_private_der()
encoded_der = self._export_private_der(**kwargs)
return PEM.encode(encoded_der, "EC PRIVATE KEY", passphrase, **kwargs)

def _export_private_clear_pkcs8_in_clear_pem(self):
Expand Down Expand Up @@ -650,6 +658,7 @@ def _export_openssh(self, compress):
return desc + " " + tostr(binascii.b2a_base64(blob))

def export_key(self, **kwargs):

"""Export this ECC key.

Args:
Expand Down Expand Up @@ -738,7 +747,7 @@ def export_key(self, **kwargs):
if use_pkcs8:
return self._export_pkcs8(passphrase=passphrase, **args)
else:
return self._export_private_der()
return self._export_private_der(**args)
else:
raise ValueError("Private keys cannot be exported in OpenSSH format")
else: # Public key
Expand Down
39 changes: 39 additions & 0 deletions lib/Crypto/SelfTest/Experimental/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
#
# SelfTest/PublicKey/__init__.py: Self-test for public key crypto
#
# Written in 2008 by Dwayne C. Litzenberger <dlitz@dlitz.net>
#
# ===================================================================
# The contents of this file are dedicated to the public domain. To
# the extent that dedication to the public domain is not available,
# everyone is granted a worldwide, perpetual, royalty-free,
# non-exclusive license to exercise all rights associated with the
# contents of this file for any purpose whatsoever.
# No rights are reserved.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# ===================================================================

"""Self-test for experimental crypto"""

__revision__ = "$Id$"

def get_tests(config={}):
tests = []
from Crypto.SelfTest.Experimental import test_ECC; tests += test_ECC.get_tests(config=config)
return tests

if __name__ == '__main__':
import unittest
suite = lambda: unittest.TestSuite(get_tests())
unittest.main(defaultTest='suite')

# vim:set ts=4 sw=4 sts=4 expandtab:
68 changes: 68 additions & 0 deletions lib/Crypto/SelfTest/Experimental/test_ECC.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# ===================================================================
#
# 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 sys
import unittest
from binascii import unhexlify

from Crypto.SelfTest.st_common import list_test_cases
from Crypto.Util._file_system import pycryptodome_filename
from Crypto.Util.py3compat import bord, tostr
from Crypto.Util.number import bytes_to_long

from Crypto.Experimental import ECC

def load_file(filename, mode="rb"):
comps = [ "Crypto", "SelfTest", "PublicKey", "test_vectors", "ECC" ]
with open(pycryptodome_filename(comps, filename), mode) as fd:
return fd.read()


def compact(lines):
ext = b"".join(lines)
return unhexlify(tostr(ext).replace(" ", "").replace(":", ""))

class TestExportExplicit_P256(unittest.TestCase):

def test_export_private_explicit_params_der(self):
key_file = load_file("ecc_p256_private_explicit_params.der")
key = ECC.construct(curve='P-256', d=0x5c4e4320ef260f91ed9fc597aee98c8236b60e0ced692cc7a057d5e45798a052)

encoded = key._export_private_der()
self.assertEqual(key_file, encoded)

def get_tests(config={}):
tests = []
tests += list_test_cases(TestExportExplicit_P256)
return tests

if __name__ == '__main__':
suite = lambda: unittest.TestSuite(get_tests())
unittest.main(defaultTest='suite')
Binary file not shown.
1 change: 1 addition & 0 deletions lib/Crypto/SelfTest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ def get_tests(config={}):
from Crypto.SelfTest import Signature; tests += Signature.get_tests(config=config)
from Crypto.SelfTest import IO; tests += IO.get_tests(config=config)
from Crypto.SelfTest import Math; tests += Math.get_tests(config=config)
from Crypto.SelfTest import Experimental; tests += Experimental.get_tests(config=config)
return tests

if __name__ == '__main__':
Expand Down
Loading