From af73018c75f2e250b38327bc7c5793e59b5d8d18 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Fri, 13 Aug 2021 14:50:01 +0200 Subject: [PATCH 01/12] docs: general project description --- docs/source/index.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/source/index.rst b/docs/source/index.rst index 9aa5b3f1..1fb7c172 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -6,6 +6,20 @@ Welcome to python-ecdsa's documentation! ======================================== +``ecdsa`` implements +`elliptic-curve cryptography (ECC) `_, +more specifically the +`Elliptic Curve Digital Signature Algorithm (ECDSA) `_ +and the +`Elliptic Curve Diffie-Hellman (ECDH) `_ +algorithms. +Both of those algorithms are used in many protocols in practice, like +in +`TLS `_ +or +`SSH `_. + + .. toctree:: :maxdepth: 2 :caption: Contents: From 00790925fe7899e14a0c39ba39e56af5da75652c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 3 Apr 2022 17:51:07 +0200 Subject: [PATCH 02/12] add eddsa module to autogen --- docs/source/ecdsa.eddsa.rst | 7 +++++++ docs/source/ecdsa.rst | 1 + 2 files changed, 8 insertions(+) create mode 100644 docs/source/ecdsa.eddsa.rst diff --git a/docs/source/ecdsa.eddsa.rst b/docs/source/ecdsa.eddsa.rst new file mode 100644 index 00000000..1b1c3f4e --- /dev/null +++ b/docs/source/ecdsa.eddsa.rst @@ -0,0 +1,7 @@ +ecdsa.eddsa module +================== + +.. automodule:: ecdsa.eddsa + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/ecdsa.rst b/docs/source/ecdsa.rst index 2b5b548c..00b49361 100644 --- a/docs/source/ecdsa.rst +++ b/docs/source/ecdsa.rst @@ -16,6 +16,7 @@ Submodules ecdsa.der ecdsa.ecdh ecdsa.ecdsa + ecdsa.eddsa ecdsa.ellipticcurve ecdsa.errors ecdsa.keys From 6e8e6adc8a99aead59706e0ec22a254dca763cf5 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 3 Apr 2022 18:26:13 +0200 Subject: [PATCH 03/12] add glossary module --- docs/source/glossary.rst | 23 +++++++++++++++++++++++ docs/source/index.rst | 5 +++++ docs/source/modules.rst | 4 ++-- 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 docs/source/glossary.rst diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst new file mode 100644 index 00000000..78332b73 --- /dev/null +++ b/docs/source/glossary.rst @@ -0,0 +1,23 @@ +.. _glossary: + +Glossary +======== + +.. glossary:: + :sorted: + + ECC + Elliptic Curve Cryptography, a term for all the different ways of using + elliptic curves in cryptography. Also combined term for :term:`ECDSA`, + :term:`EdDSA`, :term:`ECDH`. + + ECDSA + Elliptic Curve Digital Signature Algorithm + + EdDSA + Edwards curve based Digital Signature Algorithm, the alternative + digital signature algorithm that's used for Curve25519 or Curve448 + + ECDH + Elliptic Curve Diffie-Hellman + diff --git a/docs/source/index.rst b/docs/source/index.rst index 1fb7c172..8b1d2a15 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -23,6 +23,10 @@ or .. toctree:: :maxdepth: 2 :caption: Contents: + :hidden: + + glossary + modules @@ -31,4 +35,5 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` +* :ref:`glossary` * :ref:`search` diff --git a/docs/source/modules.rst b/docs/source/modules.rst index d74a83e1..f5c495b8 100644 --- a/docs/source/modules.rst +++ b/docs/source/modules.rst @@ -1,5 +1,5 @@ -src -=== +python-ecdsa API +================ .. toctree:: :maxdepth: 4 From c6612ca422c80015237ae3a0fa238cedb1899a29 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 3 Apr 2022 20:03:57 +0200 Subject: [PATCH 04/12] move all glossary items to single file --- docs/source/conf.py | 3 ++ docs/source/glossary.rst | 65 ++++++++++++++++++++++++++++++++++++++ src/ecdsa/keys.py | 68 ---------------------------------------- 3 files changed, 68 insertions(+), 68 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index df4de1b3..0eaea411 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -61,6 +61,9 @@ # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {"https://docs.python.org/": None} + autodoc_default_options = { "undoc-members": True, "inherited-members": True, diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst index 78332b73..cbeede8e 100644 --- a/docs/source/glossary.rst +++ b/docs/source/glossary.rst @@ -21,3 +21,68 @@ Glossary ECDH Elliptic Curve Diffie-Hellman + raw encoding + Conversion of public, private keys and signatures (which in + mathematical sense are integers or pairs of integers) to strings of + bytes that does not use any special tags or encoding rules. + For any given curve, all keys of the same type or signatures will be + encoded to byte strings of the same length. In more formal sense, + the integers are encoded as big-endian, constant length byte strings, + where the string length is determined by the curve order (e.g. + for NIST256p the order is 256 bits long, so the private key will be 32 + bytes long while public key will be 64 bytes long). The encoding of a + single integer is zero-padded on the left if the numerical value is + low. In case of public keys and signatures, which are comprised of two + integers, the integers are simply concatenated. + + uncompressed + The most common formatting specified in PKIX standards. Specified in + X9.62 and SEC1 standards. The only difference between it and + :term:`raw encoding` is the prepending of a 0x04 byte. Thus an + uncompressed NIST256p public key encoding will be 65 bytes long. + + compressed + The public point representation that uses half of bytes of the + :term:`uncompressed` encoding (rounded up). It uses the first byte of + the encoding to specify the sign of the y coordinate and encodes the + x coordinate as-is. The first byte of the encoding is equal to + 0x02 or 0x03. Compressed encoding of NIST256p public key will be 33 + bytes long. + + hybrid + A combination of :term:`uncompressed` and :term:`compressed` encodings. + Both x and y coordinates are stored just as in :term:`compressed` + encoding, but the first byte reflects the sign of the y coordinate. The + first byte of the encoding will be equal to 0x06 or 0x7. Hybrid + encoding of NIST256p public key will be 65 bytes long. + + PEM + The acronym stands for Privacy Enhanced Mail, but currently it is used + primarily as the way to encode :term:`DER` objects into text that can + be either easily copy-pasted or transferred over email. + It uses headers like ``-----BEGIN -----`` and footers + like ``-----END -----`` to separate multiple + types of objects in the same file or the object from the surrounding + comments. The actual object stored is base64 encoded. + + DER + Distinguished Encoding Rules, the way to encode :term:`ASN.1` objects + deterministically and uniquely into byte strings. + + ASN.1 + Abstract Syntax Notation 1 is a standard description language for + specifying serialisation and deserialisation of data structures in a + portable and cross-platform way. + + bytes-like object + All the types that implement the buffer protocol. That includes + ``str`` (only on python2), ``bytes``, ``bytearray``, ``array.array`` + and ``memoryview`` of those objects. + Please note that ``array.array`` serialisation (converting it to byte + string) is endianess dependant! Signature computed over ``array.array`` + of integers on a big-endian system will not be verified on a + little-endian system and vice-versa. + + set-like object + All the types that support the ``in`` operator, like ``list``, + ``tuple``, ``set``, ``frozenset``, etc. diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index b606f57d..499c5e44 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -1,73 +1,5 @@ """ Primary classes for performing signing and verification operations. - -.. glossary:: - - raw encoding - Conversion of public, private keys and signatures (which in - mathematical sense are integers or pairs of integers) to strings of - bytes that does not use any special tags or encoding rules. - For any given curve, all keys of the same type or signatures will be - encoded to byte strings of the same length. In more formal sense, - the integers are encoded as big-endian, constant length byte strings, - where the string length is determined by the curve order (e.g. - for NIST256p the order is 256 bits long, so the private key will be 32 - bytes long while public key will be 64 bytes long). The encoding of a - single integer is zero-padded on the left if the numerical value is - low. In case of public keys and signatures, which are comprised of two - integers, the integers are simply concatenated. - - uncompressed - The most common formatting specified in PKIX standards. Specified in - X9.62 and SEC1 standards. The only difference between it and - :term:`raw encoding` is the prepending of a 0x04 byte. Thus an - uncompressed NIST256p public key encoding will be 65 bytes long. - - compressed - The public point representation that uses half of bytes of the - :term:`uncompressed` encoding (rounded up). It uses the first byte of - the encoding to specify the sign of the y coordinate and encodes the - x coordinate as-is. The first byte of the encoding is equal to - 0x02 or 0x03. Compressed encoding of NIST256p public key will be 33 - bytes long. - - hybrid - A combination of :term:`uncompressed` and :term:`compressed` encodings. - Both x and y coordinates are stored just as in :term:`compressed` - encoding, but the first byte reflects the sign of the y coordinate. The - first byte of the encoding will be equal to 0x06 or 0x7. Hybrid - encoding of NIST256p public key will be 65 bytes long. - - PEM - The acronym stands for Privacy Enhanced Email, but currently it is used - primarily as the way to encode :term:`DER` objects into text that can - be either easily copy-pasted or transferred over email. - It uses headers like ``-----BEGIN -----`` and footers - like ``-----END -----`` to separate multiple - types of objects in the same file or the object from the surrounding - comments. The actual object stored is base64 encoded. - - DER - Distinguished Encoding Rules, the way to encode :term:`ASN.1` objects - deterministically and uniquely into byte strings. - - ASN.1 - Abstract Syntax Notation 1 is a standard description language for - specifying serialisation and deserialisation of data structures in a - portable and cross-platform way. - - bytes-like object - All the types that implement the buffer protocol. That includes - ``str`` (only on python2), ``bytes``, ``bytesarray``, ``array.array`` - and ``memoryview`` of those objects. - Please note that ``array.array`` serialisation (converting it to byte - string) is endianess dependant! Signature computed over ``array.array`` - of integers on a big-endian system will not be verified on a - little-endian system and vice-versa. - - set-like object - All the types that support the ``in`` operator, like ``list``, - ``tuple``, ``set``, ``frozenset``, etc. """ import binascii From f61435adad78ed72b2604d01de4808e8a097a27f Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sun, 3 Apr 2022 20:04:21 +0200 Subject: [PATCH 05/12] start basics and quickstart modules --- docs/source/basics.rst | 99 ++++++++++++++++++++++++++++++++++++++ docs/source/index.rst | 20 +++++++- docs/source/quickstart.rst | 21 ++++++++ 3 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 docs/source/basics.rst create mode 100644 docs/source/quickstart.rst diff --git a/docs/source/basics.rst b/docs/source/basics.rst new file mode 100644 index 00000000..5f10970c --- /dev/null +++ b/docs/source/basics.rst @@ -0,0 +1,99 @@ +========== +ECC basics +========== + +The :term:`ECC`, as any asymmetric cryptography system, deals with private +keys and public keys. Private keys are generally used to create signatures, +and are kept, as the name suggest, private. That's because possession of a +private key allows creating a signature that can be verified with a public key. +If the public key is associated with an identity (like a person or an +institution), possession of the private key will allow to impersonate +that identity. + +The public keys on the other hand are widely distributed, and they don't +have to be kept private. The primary purpose of them, is to allow +checking if a given signature was made with the associated private key. + +On a more low level, the private key is a single number, usually the +size of the curve size: a NIST P-256 private key will have a size of 256 bits, +though as it needs to be selected randomly, it may be a slightly smaller +number (255-bit, 248-bit, etc.). +Public points are a pair of numbers. That pair specifies a point on an +elliptic curve (a pair of points that satisfy the curve equation). +Those two numbers are similarly close in size to the curve size, so both the +``x`` and ``y`` coordinate of a NIST P-256 curve will also be around 256 bit in +size. + +.. note:: + To be more precise, the size of the private key is related to the + curve *order*, i.e. the number of points on a curve. The coordinates + of the curve depend on the *field* of the curve, which usually means the + size of the *prime* used for operations on points. While the *order* and + the *prime* size are related and fairly close in size, it's possible + to have a curve where either of them is larger by a bit (i.e. + it's possible to have a curve that uses a 256 bit prime that has a 257 bit + order). + +Since normally computers work with much smaller numbers, like 32 bit or 64 bit, +we need to use special approaches to represent numbers that are hundreds of +bits large. + +First is to decide if the numbers should be stored in a big +endian format, or in little endian format. In big endian, the most +significant bits are stored first, so a number like :math:`2^{16}` is saved +as a three of byes: byte with value 1 and two bytes with value 0. +In little endian format the least significant bits are stored first, so +the number like :math:`2^{16}` would be stored as three bytes: +first two bytes with value 0, than a byte with value 1. + +For :term:`ECDSA` big endian encoding is usually used, for :term:`EdDSA` +little endian encoding is usually used. + +Secondly, we need to decide if the numbers need to be stored as fixed length +strings (zero padded if necessary), or if they should be stored with +minimal number of bytes necessary. +That depends on the format and place it's used, some require strict +sizes (so even if the number encoded is 1, but the curve used is 128 bit large, +that number 1 still needs to be encoded with 16 bytes, with fifteen most +significant bytes equal zero). + +Generally, public keys (i.e. points) are expressed as fixed size byte strings. + +While public keys can be saved as two integers, one to represent the +``x`` coordinate and one to represent ``y`` coordinate, that actually +provides a lot of redundancy. Because of the specifics of elliptic curves, +for every valid ``x`` value there are only two valid ``y`` values. +Moreover, if you have an ``x`` values, you can compute those two possible +``y`` values (if they exist). +As such, it's possible to save just the ``x`` coordinate and the sign +of the ``y`` coordinate (as the two possible values are negatives of +each-other: :math:`y_1 == -y_2`). + +That gives us few options to represent the public point, the most common are: + +1. As a concatenation of two fixed-length big-endian integers, so called + :term:`raw encoding`. +2. As a concatenation of two fixed-length big-endian integers prefixed with + the type of the encoding, so called :term:`uncompressed` point + representation (the type is represented by a 0x04 byte). +3. As a fixed-length big-endian integer representing the ``x`` coordinate + prefixed with the byte representing the combined type of the encoding + and the sign of the ``y`` coordinate, so called :term:`compressed` + point representation. + +Now, while we can save the byte strings as-is and "remember" which curve +was used to generate those private and public keys, interoperability usually +requires us to also save information about the curve together with the +corresponding key. Here too there are many ways to do it: +save the parameters of the used curve explicitly, use the name of the +well-known curve as a string, use a numerical identifier of the well-known +curve, etc. + +For public keys the most interoperable format is the one described +in RFC5912 (look for SubjectPublicKeyInfo structure). +For private keys, the RFC5915 format (also known as the ssleay format) +and the PKCS#8 format (described in RFC5958) are the most popular. +All of those specify a binary encoding, called DER, which can use +bytes with any values. For some uses it's useful to limit byte use +to printable characters, then the PEM formatting of the DER-encoded data +can be used. diff --git a/docs/source/index.rst b/docs/source/index.rst index 8b1d2a15..6917a869 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -19,17 +19,35 @@ in or `SSH `_. +This library provides key generation, signing, verifying, and shared secret +derivation for five +popular NIST "Suite B" GF(p) (*prime field*) curves, with key lengths of 192, +224, 256, 384, and 521 bits. The "short names" for these curves, as known by +the OpenSSL tool (``openssl ecparam -list_curves``), are: ``prime192v1``, +``secp224r1``, ``prime256v1``, ``secp384r1``, and ``secp521r1``. It includes +the +256-bit curve ``secp256k1`` used by Bitcoin. There is also support for the +regular (non-twisted) variants of Brainpool curves from 160 to 512 bits. The +"short names" of those curves are: ``brainpoolP160r1``, ``brainpoolP192r1``, +``brainpoolP224r1``, ``brainpoolP256r1``, ``brainpoolP320r1``, +``brainpoolP384r1``, +``brainpoolP512r1``. Few of the small curves from SEC standard are also +included (mainly to speed-up testing of the library), those are: +``secp112r1``, ``secp112r2``, ``secp128r1``, and ``secp160r1``. +No other curves are included, but it is not too hard to add support for more +curves over prime fields. .. toctree:: :maxdepth: 2 :caption: Contents: :hidden: + quickstart + basics glossary modules - Indices and tables ================== diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst new file mode 100644 index 00000000..8ae2cee6 --- /dev/null +++ b/docs/source/quickstart.rst @@ -0,0 +1,21 @@ +=============== +Getting started +=============== + +The library has just one mandatory dependency: ``six``. +If you install ``python-ecdsa`` through pip, it should automatically +install ``six`` too. + +The high level API provided by the library is primarily in the +:py:class:`~ecdsa.keys` module. +There you will find the :py:class:`~ecdsa.keys.SigningKey` (the class +that enables handling of the private keys) and the +:py:class:`~ecdsa.keys.VerifyingKey` (the class that enables handling of +the public keys). + +To handle shared key derivation, the :py:class:`~ecdsa.ecdh.ECDH` class +is used. + +Finally, in case use of custom elliptic curves is necessary, the +:py:class:`~ecdsa.curves.Curve` class may be needed. + From 4ae9955be3ce5596d1a30d21a340152a692fd449 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Tue, 5 Apr 2022 14:59:57 +0200 Subject: [PATCH 06/12] better cross-module linking in ecdsa.keys --- src/ecdsa/keys.py | 59 +++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index 499c5e44..397cf77f 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -636,7 +636,7 @@ def verify( :type signature: sigdecode method dependent :param data: data signed by the `signature`, will be hashed using `hashfunc`, if specified, or default hash function - :type data: bytes like object + :type data: :term:`bytes-like object` :param hashfunc: The default hash function that will be used for verification, needs to implement the same interface as hashlib.sha1 :type hashfunc: callable @@ -690,7 +690,7 @@ def verify_digest( :param signature: encoding of the signature :type signature: sigdecode method dependent :param digest: raw hash value that the signature authenticates. - :type digest: bytes like object + :type digest: :term:`bytes-like object` :param sigdecode: Callable to define the way the signature needs to be decoded to an object, needs to handle `signature` as the first parameter, the curve order (an int) as the second and return @@ -879,7 +879,7 @@ def from_string(cls, string, curve=NIST192p, hashfunc=sha1): In Python 3, the expected type is `bytes`. :param string: the raw encoding of the private key - :type string: bytes like object + :type string: :term:`bytes-like object` :param curve: The curve on which the point needs to reside :type curve: ~ecdsa.curves.Curve :param hashfunc: The default hash function that will be used for @@ -1012,7 +1012,7 @@ def from_der(cls, string, hashfunc=sha1, valid_curve_encodings=None): in them will not be detected. :param string: binary string with DER-encoded private ECDSA key - :type string: bytes like object + :type string: :term:`bytes-like object` :param valid_curve_encodings: list of allowed encoding formats for curve parameters. By default (``None``) all are supported: ``named_curve`` and ``explicit``. @@ -1315,7 +1315,7 @@ def sign_deterministic( of data is necessary. :param data: data to be hashed and computed signature over - :type data: bytes like object + :type data: :term:`bytes-like object` :param hashfunc: hash function to use for computing the signature, if unspecified, the default hash function selected during object initialisation will be used (see @@ -1334,7 +1334,7 @@ def sign_deterministic( :param extra_entropy: additional data that will be fed into the random number generator used in the RFC6979 process. Entirely optional. Ignored with EdDSA. - :type extra_entropy: bytes like object + :type extra_entropy: :term:`bytes-like object` :return: encoded signature over `data` :rtype: bytes or sigencode function dependent type @@ -1374,24 +1374,26 @@ def sign_digest_deterministic( hashing of data is necessary. :param digest: hash of data that will be signed - :type digest: bytes like object + :type digest: :term:`bytes-like object` :param hashfunc: hash function to use for computing the random "k" value from RFC6979 process, if unspecified, the default hash function selected during object initialisation will be used (see - `VerifyingKey.default_hashfunc`). The object needs to implement - the same interface as hashlib.sha1. + :attr:`.VerifyingKey.default_hashfunc`). The object needs to + implement + the same interface as :func:`~hashlib.sha1` from :py:mod:`hashlib`. :type hashfunc: callable :param sigencode: function used to encode the signature. The function needs to accept three parameters: the two integers that are the signature and the order of the curve over which the signature was computed. It needs to return an encoded signature. - See `ecdsa.util.sigencode_string` and `ecdsa.util.sigencode_der` + See :func:`~ecdsa.util.sigencode_string` and + :func:`~ecdsa.util.sigencode_der` as examples of such functions. :type sigencode: callable :param extra_entropy: additional data that will be fed into the random number generator used in the RFC6979 process. Entirely optional. - :type extra_entropy: bytes like object + :type extra_entropy: :term:`bytes-like object` :param bool allow_truncate: if True, the provided digest can have bigger bit-size than the order of the curve, the extra bits (at the end of the digest) will be truncated. Use it when signing @@ -1456,28 +1458,35 @@ def sign( method instead of this one. :param data: data that will be hashed for signing - :type data: bytes like object - :param callable entropy: randomness source, os.urandom by default. - Ignored with EdDSA. - :param hashfunc: hash function to use for hashing the provided `data`. + :type data: :term:`bytes-like object` + :param callable entropy: randomness source, :func:`os.urandom` by + default. Ignored with EdDSA. + :param hashfunc: hash function to use for hashing the provided + ``data``. If unspecified the default hash function selected during object initialisation will be used (see - `VerifyingKey.default_hashfunc`). - Should behave like hashlib.sha1. The output length of the + :attr:`.VerifyingKey.default_hashfunc`). + Should behave like :func:`~hashlib.sha1` from :py:mod:`hashlib`. + The output length of the hash (in bytes) must not be longer than the length of the curve order (rounded up to the nearest byte), so using SHA256 with NIST256p is ok, but SHA256 with NIST192p is not. (In the 2**-96ish unlikely event of a hash output larger than the curve order, the hash will effectively be wrapped mod n). - Use hashfunc=hashlib.sha1 to match openssl's -ecdsa-with-SHA1 mode, - or hashfunc=hashlib.sha256 for openssl-1.0.0's -ecdsa-with-SHA256. + If you want to explicitly allow use of large hashes with small + curves set the ``allow_truncate`` to ``True``. + Use ``hashfunc=hashlib.sha1`` to match openssl's + ``-ecdsa-with-SHA1`` mode, + or ``hashfunc=hashlib.sha256`` for openssl-1.0.0's + ``-ecdsa-with-SHA256``. Ignored for EdDSA :type hashfunc: callable :param sigencode: function used to encode the signature. The function needs to accept three parameters: the two integers that are the signature and the order of the curve over which the signature was computed. It needs to return an encoded signature. - See `ecdsa.util.sigencode_string` and `ecdsa.util.sigencode_der` + See :func:`~ecdsa.util.sigencode_string` and + :func:`~ecdsa.util.sigencode_der` as examples of such functions. Ignored for EdDSA :type sigencode: callable @@ -1485,17 +1494,17 @@ def sign( In typical use cases, it should be set to None (the default) to allow its generation from an entropy source. Ignored for EdDSA. - :param bool allow_truncate: if True, the provided digest can have + :param bool allow_truncate: if ``True``, the provided digest can have bigger bit-size than the order of the curve, the extra bits (at the end of the digest) will be truncated. Use it when signing SHA-384 output using NIST256p or in similar situations. True by default. Ignored for EdDSA. - :raises RSZeroError: in the unlikely event when "r" parameter or - "s" parameter of the created signature is equal 0, as that would + :raises RSZeroError: in the unlikely event when *r* parameter or + *s* parameter of the created signature is equal 0, as that would leak the key. Caller should try a better entropy source, retry with - different 'k', or use the + different ``k``, or use the :func:`~SigningKey.sign_deterministic` in such case. :return: encoded signature of the hash of `data` @@ -1529,7 +1538,7 @@ def sign_digest( instead of this one. :param digest: hash value that will be signed - :type digest: bytes like object + :type digest: :term:`bytes-like object` :param callable entropy: randomness source, os.urandom by default :param sigencode: function used to encode the signature. The function needs to accept three parameters: the two integers From 3a85d220cc460534326b2e50f6213af4bd16ee4d Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Wed, 20 Apr 2022 17:48:52 +0200 Subject: [PATCH 07/12] add description of low level elliptic curve operations --- docs/source/ec_arithmetic.rst | 125 ++++++++++++++++++++++++++++++++++ docs/source/glossary.rst | 4 ++ docs/source/index.rst | 1 + src/ecdsa/ellipticcurve.py | 5 +- 4 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 docs/source/ec_arithmetic.rst diff --git a/docs/source/ec_arithmetic.rst b/docs/source/ec_arithmetic.rst new file mode 100644 index 00000000..b7abaaf2 --- /dev/null +++ b/docs/source/ec_arithmetic.rst @@ -0,0 +1,125 @@ +========================= +Elliptic Curve arithmetic +========================= + +The python-ecdsa also provides generic API for performing operations on +elliptic curve points. + +.. warning:: + + This is documentation of a very low-level API, if you want to + handle keys or signatures you should look at documentation of + the :py:mod:`~ecdsa.keys` module. + +Short Weierstrass curves +======================== + +There are two low-level implementations for +:term:`short Weierstrass curves `: +:py:class:`~ecdsa.ellipticcurve.Point` and +:py:class:`~ecdsa.ellipticcurve.PointJacobi`. + +Both of them use the curves specified using the +:py:class:`~ecdsa.ellipticcurve.CurveFp` object. + +You can either provide your own curve parameters or use one of the predefined +curves. +For example, to define a curve :math:`x^2 = x^3 + x + 4 \text{ mod } 5` use +code like this: + +.. code:: python + + from ecdsa.ellipticcurve import CurveFp + custom_curve = CurveFp(5, 1, 4) + +The predefined curves are specified in the :py:mod:`~ecdsa.ecdsa` module, +but it's much easier to use the helper functions (and proper names) +from the :py:mod:`~ecdsa.curves` module. + +For example, to get the curve parameters for the NIST P-256 curve use this +code: + +.. code:: python + + from ecdsa.curves import NIST256p + curve = NIST256p.curve + +.. tip:: + + You can also use :py:class:`~ecdsa.curves.Curve` to get the curve + parameters from a PEM or DER file. Or use the + :py:func:`~ecdsa.curves.find_curve` to get a curve by specifying its + ASN.1 object identifier (OID). + +After taking hold of curve parameters you can create a point on the +curve. The :py:class:`~ecdsa.ellipticcurve.Point` uses affine coordinates, +i.e. the :math:`x` and :math:`y` from the curve equation directly. + +To specify a point (1, 1) on the ``custom_curve`` you can use this code: + +.. code:: python + + from ecdsa.ellipticcurve import Point + point_a = Point(custom_curve, 1, 1) + +Then it's possible to either perform scalar multiplication: + +.. code:: python + + point_b = point_a * 3 + +Or specify other points and perform addition: + +.. code:: python + + point_b = Point(custom_curve, 3, 2) + point_c = point_a + point_b + +To get the affine coordinates of the point, call the ``x()`` and ``y()`` +methods of the object: + +.. code:: python + + print("x: {0}, y: {1}".format(point_c.x(), point_c.y())) + + +When using the Jacobi coordinates, the point is defined by 3 integers, +which are related to the :math:`x` and :math:`y` in the following way: + +.. math:: + + x = X/Z^2 \\ + y = Y/Z^3 + +That means that if you have point in affine coordinates, it's possible +to convert them to Jacobi by simply assuming :math:`Z = 1`. + +So the same points can be specified as so: + +.. code:: python + + from ecdsa.ellipticcurve import PointJacobi + point_a = PointJacobi(custom_curve, 1, 1, 1) + point_b = PointJacobi(custom_curve, 3, 2, 1) + + +.. note:: + + Unlike the :py:class:`~ecdsa.ellipticcurve.Point`, the + :py:class:`~ecdsa.ellipticcurve.PointJacobi` does **not** check if the + coordinates specify a valid point on the curve as that operation is + computationally expensive for Jacobi coordinates. + If you want to verify if they specify a valid + point, you need to convert the point to affine coordinates and use the + :py:meth:`~ecdsa.ellipticcurve.CurveFp.contains_point` method. + +Then all the operations work exactly the same as with regular +:py:class:`~ecdsa.ellipticcurve.Point` implementation. +While it's not possible to get the internal :math:`X`, :math:`Y`, and :math:`Z` +coordinates, it's possible to get the affine projection just like with +the regular implementation: + +.. code:: python + + point_c = point_a + point_b + print("x: {0}, y: {1}".format(point_c.x(), point_c.y())) diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst index cbeede8e..54866730 100644 --- a/docs/source/glossary.rst +++ b/docs/source/glossary.rst @@ -86,3 +86,7 @@ Glossary set-like object All the types that support the ``in`` operator, like ``list``, ``tuple``, ``set``, ``frozenset``, etc. + + short Weierstrass curve + A curve with the curve equation: :math:`x^2=y^3+ax+b`. Most popular + curves use equation of this format (e.g. NIST256p). diff --git a/docs/source/index.rst b/docs/source/index.rst index 6917a869..f69893fc 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -44,6 +44,7 @@ curves over prime fields. quickstart basics + ec_arithmetic glossary modules diff --git a/src/ecdsa/ellipticcurve.py b/src/ecdsa/ellipticcurve.py index f358fbd5..d6f71463 100644 --- a/src/ecdsa/ellipticcurve.py +++ b/src/ecdsa/ellipticcurve.py @@ -56,7 +56,10 @@ @python_2_unicode_compatible class CurveFp(object): - """Short Weierstrass Elliptic Curve over a prime field.""" + """ + :term:`Short Weierstrass Elliptic Curve ` over a + prime field. + """ if GMPY: # pragma: no branch From f08f04dba896dbf12b8fc51920195783f8f3b96a Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 23 Apr 2022 23:51:57 +0200 Subject: [PATCH 08/12] document that EdDSA is supported too --- docs/source/index.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index f69893fc..915071ac 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -9,11 +9,12 @@ Welcome to python-ecdsa's documentation! ``ecdsa`` implements `elliptic-curve cryptography (ECC) `_, more specifically the -`Elliptic Curve Digital Signature Algorithm (ECDSA) `_ +`Elliptic Curve Digital Signature Algorithm (ECDSA) `_, +`Edwards-curve Digital Signature Algorithm (EdDSA) `_ and the `Elliptic Curve Diffie-Hellman (ECDH) `_ algorithms. -Both of those algorithms are used in many protocols in practice, like +All of those algorithms are used in many protocols in practice, like in `TLS `_ or @@ -34,6 +35,8 @@ regular (non-twisted) variants of Brainpool curves from 160 to 512 bits. The ``brainpoolP512r1``. Few of the small curves from SEC standard are also included (mainly to speed-up testing of the library), those are: ``secp112r1``, ``secp112r2``, ``secp128r1``, and ``secp160r1``. +Key generation, siging and verifying is also supported for Ed25519 and Ed448 +curves. No other curves are included, but it is not too hard to add support for more curves over prime fields. From 51512716f23f84bc8dd69b11e1d6387c5613ac8c Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 25 Jun 2022 16:00:21 +0200 Subject: [PATCH 09/12] ec arithmetic additions --- docs/source/ec_arithmetic.rst | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/source/ec_arithmetic.rst b/docs/source/ec_arithmetic.rst index b7abaaf2..36d0ad49 100644 --- a/docs/source/ec_arithmetic.rst +++ b/docs/source/ec_arithmetic.rst @@ -47,10 +47,16 @@ code: .. tip:: You can also use :py:class:`~ecdsa.curves.Curve` to get the curve - parameters from a PEM or DER file. Or use the + parameters from a PEM or DER file. You can also use + :py:func:`~ecdsa.curves.curve_by_name` to get a curve by specifying its + name. + Or use the :py:func:`~ecdsa.curves.find_curve` to get a curve by specifying its ASN.1 object identifier (OID). +Affine coordinates +------------------ + After taking hold of curve parameters you can create a point on the curve. The :py:class:`~ecdsa.ellipticcurve.Point` uses affine coordinates, i.e. the :math:`x` and :math:`y` from the curve equation directly. @@ -82,6 +88,8 @@ methods of the object: print("x: {0}, y: {1}".format(point_c.x(), point_c.y())) +Projective coordinates +---------------------- When using the Jacobi coordinates, the point is defined by 3 integers, which are related to the :math:`x` and :math:`y` in the following way: @@ -123,3 +131,7 @@ the regular implementation: point_c = point_a + point_b print("x: {0}, y: {1}".format(point_c.x(), point_c.y())) + +All the other operations, like scalar multiplication or point addition work +on projective points the same as with affine representation, but they +are much more effective computationally. From 0d269934cd440e386007dcc60a31a7ae068e9503 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 25 Jun 2022 16:50:23 +0200 Subject: [PATCH 10/12] expand on ECC basics --- docs/source/basics.rst | 91 +++++++++++++++++++++++++++++++++++------- 1 file changed, 77 insertions(+), 14 deletions(-) diff --git a/docs/source/basics.rst b/docs/source/basics.rst index 5f10970c..b71e925c 100644 --- a/docs/source/basics.rst +++ b/docs/source/basics.rst @@ -1,6 +1,6 @@ -========== -ECC basics -========== +====================== +Basics of ECC handling +====================== The :term:`ECC`, as any asymmetric cryptography system, deals with private keys and public keys. Private keys are generally used to create signatures, @@ -14,12 +14,15 @@ The public keys on the other hand are widely distributed, and they don't have to be kept private. The primary purpose of them, is to allow checking if a given signature was made with the associated private key. +Number representations +====================== + On a more low level, the private key is a single number, usually the size of the curve size: a NIST P-256 private key will have a size of 256 bits, though as it needs to be selected randomly, it may be a slightly smaller number (255-bit, 248-bit, etc.). Public points are a pair of numbers. That pair specifies a point on an -elliptic curve (a pair of points that satisfy the curve equation). +elliptic curve (a pair of integers that satisfy the curve equation). Those two numbers are similarly close in size to the curve size, so both the ``x`` and ``y`` coordinate of a NIST P-256 curve will also be around 256 bit in size. @@ -31,8 +34,8 @@ size. size of the *prime* used for operations on points. While the *order* and the *prime* size are related and fairly close in size, it's possible to have a curve where either of them is larger by a bit (i.e. - it's possible to have a curve that uses a 256 bit prime that has a 257 bit - order). + it's possible to have a curve that uses a 256 bit *prime* that has a 257 bit + *order*). Since normally computers work with much smaller numbers, like 32 bit or 64 bit, we need to use special approaches to represent numbers that are hundreds of @@ -41,7 +44,7 @@ bits large. First is to decide if the numbers should be stored in a big endian format, or in little endian format. In big endian, the most significant bits are stored first, so a number like :math:`2^{16}` is saved -as a three of byes: byte with value 1 and two bytes with value 0. +as a three bytes: byte with value 1 and two bytes with value 0. In little endian format the least significant bits are stored first, so the number like :math:`2^{16}` would be stored as three bytes: first two bytes with value 0, than a byte with value 1. @@ -57,13 +60,16 @@ sizes (so even if the number encoded is 1, but the curve used is 128 bit large, that number 1 still needs to be encoded with 16 bytes, with fifteen most significant bytes equal zero). +Public key encoding +=================== + Generally, public keys (i.e. points) are expressed as fixed size byte strings. While public keys can be saved as two integers, one to represent the ``x`` coordinate and one to represent ``y`` coordinate, that actually provides a lot of redundancy. Because of the specifics of elliptic curves, for every valid ``x`` value there are only two valid ``y`` values. -Moreover, if you have an ``x`` values, you can compute those two possible +Moreover, if you have an ``x`` value, you can compute those two possible ``y`` values (if they exist). As such, it's possible to save just the ``x`` coordinate and the sign of the ``y`` coordinate (as the two possible values are negatives of @@ -79,11 +85,15 @@ That gives us few options to represent the public point, the most common are: 3. As a fixed-length big-endian integer representing the ``x`` coordinate prefixed with the byte representing the combined type of the encoding and the sign of the ``y`` coordinate, so called :term:`compressed` - point representation. + point representation (the type is then represented by a 0x02 or a 0x03 + byte). + +Interoperable file formats +========================== Now, while we can save the byte strings as-is and "remember" which curve was used to generate those private and public keys, interoperability usually -requires us to also save information about the curve together with the +requires to also save information about the curve together with the corresponding key. Here too there are many ways to do it: save the parameters of the used curve explicitly, use the name of the well-known curve as a string, use a numerical identifier of the well-known @@ -93,7 +103,60 @@ For public keys the most interoperable format is the one described in RFC5912 (look for SubjectPublicKeyInfo structure). For private keys, the RFC5915 format (also known as the ssleay format) and the PKCS#8 format (described in RFC5958) are the most popular. -All of those specify a binary encoding, called DER, which can use -bytes with any values. For some uses it's useful to limit byte use -to printable characters, then the PEM formatting of the DER-encoded data -can be used. + +All three formats effectively support two ways of providing the information +about the curve used: by specifying the curve parameters explicitly or +by specifying the curve using ASN.1 OBJECT IDENTIFIER (OID), which is +called ``named_curve``. ASN.1 OIDs are a hierarchical system of representing +types of objects, for example, NIST P-256 curve is identified by the +1.2.840.10045.3.1.7 OID (in dotted-decimal formatting of the OID, also +known by the ``prime256v1`` OID node name or short name). Those OIDs +uniquely, identify a particular curve, but the receiver needs to know +which numerical OID maps to which curve parameters. Thus the prospect of +using the explicit encoding, where all the needed parameters are provided +is tempting, the downside is that curve parameters may specify a *weak* +curve, which is easy to attack and break (that is to deduce the private key +from the public key). To verify curve parameters is complex and computationally +expensive, thus generally protocols use few specific curves and require +all implementations to carry the parameters of them. As such, use of +``named_curve`` parameters is generally recommended. + +All of the mentioned formats specify a binary encoding, called DER. That +encoding uses bytes with all possible numerical values, which means it's not +possible to embed it directly in text files. For uses where it's useful to +limit bytes to printable characters, so that the keys can be embedded in text +files or text-only protocols (like email), the PEM formatting of the +DER-encoded data can be used. The PEM formatting is just a base64 encoding +with appropriate header and footer. + +Signature formats +================= + +Finally, ECDSA signatures at the lowest level are a pair of numbers, usually +called ``r`` and ``s``. While they are the ``x`` coordinates of special +points on the curve, they are saved modulo *order* of the curve, not +modulo *prime* of the curve (as a coordinate needs to be). + +That again means we have multiple ways of encoding those two numbers. +The two most popular formats are to save them as a concatenation of big-endian +integers of fixed size (determined by the curve *order*) or as a DER +structure with two INTEGERS. +The first of those is called the :term:``raw encoding`` inside the Python +ecdsa library. + +As ASN.1 signature format requires the encoding of INTEGERS, and DER INTEGERs +must use the fewest possible number of bytes, a numerically small value of +``r`` or ``s`` will require fewer +bytes to represent in the DER structure. Thus, DER encoding isn't fixed +size for a given curve, but has a maximum possible size. + +.. note:: + + As DER INTEGER uses so-called two's complement representation of + numbers, the most significant bit of the most significant byte + represents the *sign* of the number. If that bit is set, then the + number is considered to be negative. Thus, to represent a number like + 255, which in binary representation is 0b11111111 (i.e. a byte with all + bits set high), the DER encoding of it will require two bytes, one + zero byte to make sure the sign bit is 0, and a byte with value 255 to + encode the numerical value of the integer. From 4d9ed5d6b996ce25c04a7570c21c137a44c631fa Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 25 Jun 2022 17:47:41 +0200 Subject: [PATCH 11/12] Expand quickstart - add basic operations --- docs/source/quickstart.rst | 157 +++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst index 8ae2cee6..e83a6a6f 100644 --- a/docs/source/quickstart.rst +++ b/docs/source/quickstart.rst @@ -6,6 +6,12 @@ The library has just one mandatory dependency: ``six``. If you install ``python-ecdsa`` through pip, it should automatically install ``six`` too. +To install it you can run the following command: + +.. code:: bash + + pip install ecdsa + The high level API provided by the library is primarily in the :py:class:`~ecdsa.keys` module. There you will find the :py:class:`~ecdsa.keys.SigningKey` (the class @@ -19,3 +25,154 @@ is used. Finally, in case use of custom elliptic curves is necessary, the :py:class:`~ecdsa.curves.Curve` class may be needed. +Key generation +============== + +To generate a key, import the :py:class:`~ecdsa.keys.SigningKey` and +call the :py:func:`~ecdsa.keys.SigningKey.generate` function in it: + +.. code:: python + + from ecdsa.keys import SigningKey + + key = SigningKey.generate() + +By default, that will create a key that uses the NIST P-192 curve. To +select a more secure curve, like NIST P-256, import it from the +:py:mod:`ecdsa.curves` or from the :py:mod:`ecdsa` module: + +.. code:: python + + from ecdsa import SigningKey, NIST256p + + key = SigningKey.generate(curve=NIST256p) + +Private key storage and retrieval +================================= + +To store a key as string or file, you can serialise it using many formats, +in general we recommend the PKCS#8 PEM encoding. + +If you have a :py:class:`~ecdsa.keys.SigningKey` object in ``key`` and +want to save it to a file like ``priv_key.pem`` you can run the following +code: + +.. code:: python + + with open("priv_key.pem", "wb") as f: + f.write(key.to_pem(format="pkcs8")) + +.. warning:: + + Not specifying the ``format=pkcs8`` will create a file that uses the legacy + ``ssleay`` file format which is most commonly used by applications + that use OpenSSL, as that was originally the only format supported by it. + For a long time though OpenSSL supports the PKCS# 8 format too. + +To read that file back, you can run code like this: + +.. code:: python + + from ecdsa import SigningKey + + with open("priv_key.pem") as f: + key = SigningKey.from_pem(f.read()) + +.. tip:: + + As the format is self-describing, the parser will automatically detect + if the provided file is in the ``ssleay`` or the ``pkcs8`` format + and process it accordingly. + +Public key derivation +===================== + +To get the public key associated with the given private key, either +call the :py:func:`~ecdsa.keys.SigningKey.get_verifying_key` method or +access the ``verifying_key`` attribute in +:py:class:`~ecdsa.keys.SigningKey` directly: + +.. code:: python + + from ecdsa import SigningKey, NIST256p + + private_key = SigningKey.generate(curve=NIST256p) + + public_key = private_key.verifying_key + +Public key storage and retrieval +================================ + +Similarly to private keys, public keys can be stored in files: + +.. code:: python + + from ecdsa import SigningKey + + private_key = SigningKey.generate() + + public_key = private_key.verifying_key + + with open("pub_key.pem", "wb") as f: + f.write(public_key.to_pem()) + +And read from files: + +.. code:: python + + from ecdsa import VerifyingKey + + with open("pub_key.pem") as f: + public_key = VerifyingKey.from_pem(f.read()) + +Signing +======= + +To sign a byte string stored in variable ``message`` using SigningKey in +``private_key``, SHA-256, get a signature in the DER format and save it to a +file, you can use the following code: + +.. code:: python + + from hashlib import sha256 + from ecdsa.util import sigencode_der + + sig = private_key.sign_deterministic( + message, + hashfunc=sha256, + sigencode=sigencode_der + ) + + with open("message.sig", "wb") as f: + f.write(sig) + +.. note:: + + As cryptographic hashes (SHA-256, SHA3-256, etc.) operate on *bytes* not + text strings, any text needs to be serialised into *bytes* before it can + be signed. This is because encoding of string "text" results in very + different bytes when it's encoded using UTF-8 and when it's encoded using + UCS-2. + +Verifying +========= + +To verify a signature of a byte string in ``message`` using a VerifyingKey +in ``public_key``, SHA-256 and a DER signature in a ``message.sig`` file, +you can use the following code: + +.. code:: python + + from hashlib import sha256 + from ecdsa import BadSignatureError + from ecdsa.util import sigdecode_der + + with open("message.sig", "rb") as f: + sig = f.read() + + try: + ret = public_key.verify(sig, message, sha256, sigdecode=sigdecode_der) + assert ret + print("Valid signature") + except BadSignatureError: + print("Incorrect signature") From 1943ef37a1ae0ae82965b3d894af857a35665669 Mon Sep 17 00:00:00 2001 From: Hubert Kario Date: Sat, 25 Jun 2022 17:47:53 +0200 Subject: [PATCH 12/12] fixup formatting in keys module --- src/ecdsa/keys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ecdsa/keys.py b/src/ecdsa/keys.py index 397cf77f..2b7d3168 100644 --- a/src/ecdsa/keys.py +++ b/src/ecdsa/keys.py @@ -733,7 +733,7 @@ class SigningKey(object): """ Class for handling keys that can create signatures (private keys). - :ivar `~ecdsa.curves.Curve` ~.curve: The Curve over which all the + :ivar `~ecdsa.curves.Curve` curve: The Curve over which all the cryptographic operations will take place :ivar default_hashfunc: the function that will be used for hashing the data. Should implement the same API as :py:class:`hashlib.sha1`