Spaces:
Paused
Paused
| # =================================================================== | |
| # | |
| # Copyright (c) 2022, 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. | |
| # =================================================================== | |
| from Crypto.Math.Numbers import Integer | |
| from Crypto.Hash import SHA512, SHAKE256 | |
| from Crypto.Util.py3compat import bchr, is_bytes | |
| from Crypto.PublicKey.ECC import (EccKey, | |
| construct, | |
| _import_ed25519_public_key, | |
| _import_ed448_public_key) | |
| def import_public_key(encoded): | |
| """Create a new Ed25519 or Ed448 public key object, | |
| starting from the key encoded as raw ``bytes``, | |
| in the format described in RFC8032. | |
| Args: | |
| encoded (bytes): | |
| The EdDSA public key to import. | |
| It must be 32 bytes for Ed25519, and 57 bytes for Ed448. | |
| Returns: | |
| :class:`Crypto.PublicKey.EccKey` : a new ECC key object. | |
| Raises: | |
| ValueError: when the given key cannot be parsed. | |
| """ | |
| if len(encoded) == 32: | |
| x, y = _import_ed25519_public_key(encoded) | |
| curve_name = "Ed25519" | |
| elif len(encoded) == 57: | |
| x, y = _import_ed448_public_key(encoded) | |
| curve_name = "Ed448" | |
| else: | |
| raise ValueError("Not an EdDSA key (%d bytes)" % len(encoded)) | |
| return construct(curve=curve_name, point_x=x, point_y=y) | |
| def import_private_key(encoded): | |
| """Create a new Ed25519 or Ed448 private key object, | |
| starting from the key encoded as raw ``bytes``, | |
| in the format described in RFC8032. | |
| Args: | |
| encoded (bytes): | |
| The EdDSA private key to import. | |
| It must be 32 bytes for Ed25519, and 57 bytes for Ed448. | |
| Returns: | |
| :class:`Crypto.PublicKey.EccKey` : a new ECC key object. | |
| Raises: | |
| ValueError: when the given key cannot be parsed. | |
| """ | |
| if len(encoded) == 32: | |
| curve_name = "ed25519" | |
| elif len(encoded) == 57: | |
| curve_name = "ed448" | |
| else: | |
| raise ValueError("Incorrect length. Only EdDSA private keys are supported.") | |
| # Note that the private key is truly a sequence of random bytes, | |
| # so we cannot check its correctness in any way. | |
| return construct(seed=encoded, curve=curve_name) | |
| class EdDSASigScheme(object): | |
| """An EdDSA signature object. | |
| Do not instantiate directly. | |
| Use :func:`Crypto.Signature.eddsa.new`. | |
| """ | |
| def __init__(self, key, context): | |
| """Create a new EdDSA object. | |
| Do not instantiate this object directly, | |
| use `Crypto.Signature.DSS.new` instead. | |
| """ | |
| self._key = key | |
| self._context = context | |
| self._A = key._export_eddsa() | |
| self._order = key._curve.order | |
| def can_sign(self): | |
| """Return ``True`` if this signature object can be used | |
| for signing messages.""" | |
| return self._key.has_private() | |
| def sign(self, msg_or_hash): | |
| """Compute the EdDSA signature of a message. | |
| Args: | |
| msg_or_hash (bytes or a hash object): | |
| The message to sign (``bytes``, in case of *PureEdDSA*) or | |
| the hash that was carried out over the message (hash object, for *HashEdDSA*). | |
| The hash object must be :class:`Crypto.Hash.SHA512` for Ed25519, | |
| and :class:`Crypto.Hash.SHAKE256` object for Ed448. | |
| :return: The signature as ``bytes``. It is always 64 bytes for Ed25519, and 114 bytes for Ed448. | |
| :raise TypeError: if the EdDSA key has no private half | |
| """ | |
| if not self._key.has_private(): | |
| raise TypeError("Private key is needed to sign") | |
| if self._key._curve.name == "ed25519": | |
| ph = isinstance(msg_or_hash, SHA512.SHA512Hash) | |
| if not (ph or is_bytes(msg_or_hash)): | |
| raise TypeError("'msg_or_hash' must be bytes of a SHA-512 hash") | |
| eddsa_sign_method = self._sign_ed25519 | |
| elif self._key._curve.name == "ed448": | |
| ph = isinstance(msg_or_hash, SHAKE256.SHAKE256_XOF) | |
| if not (ph or is_bytes(msg_or_hash)): | |
| raise TypeError("'msg_or_hash' must be bytes of a SHAKE256 hash") | |
| eddsa_sign_method = self._sign_ed448 | |
| else: | |
| raise ValueError("Incorrect curve for EdDSA") | |
| return eddsa_sign_method(msg_or_hash, ph) | |
| def _sign_ed25519(self, msg_or_hash, ph): | |
| if self._context or ph: | |
| flag = int(ph) | |
| # dom2(flag, self._context) | |
| dom2 = b'SigEd25519 no Ed25519 collisions' + bchr(flag) + \ | |
| bchr(len(self._context)) + self._context | |
| else: | |
| dom2 = b'' | |
| PHM = msg_or_hash.digest() if ph else msg_or_hash | |
| # See RFC 8032, section 5.1.6 | |
| # Step 2 | |
| r_hash = SHA512.new(dom2 + self._key._prefix + PHM).digest() | |
| r = Integer.from_bytes(r_hash, 'little') % self._order | |
| # Step 3 | |
| R_pk = EccKey(point=r * self._key._curve.G)._export_eddsa() | |
| # Step 4 | |
| k_hash = SHA512.new(dom2 + R_pk + self._A + PHM).digest() | |
| k = Integer.from_bytes(k_hash, 'little') % self._order | |
| # Step 5 | |
| s = (r + k * self._key.d) % self._order | |
| return R_pk + s.to_bytes(32, 'little') | |
| def _sign_ed448(self, msg_or_hash, ph): | |
| flag = int(ph) | |
| # dom4(flag, self._context) | |
| dom4 = b'SigEd448' + bchr(flag) + \ | |
| bchr(len(self._context)) + self._context | |
| PHM = msg_or_hash.read(64) if ph else msg_or_hash | |
| # See RFC 8032, section 5.2.6 | |
| # Step 2 | |
| r_hash = SHAKE256.new(dom4 + self._key._prefix + PHM).read(114) | |
| r = Integer.from_bytes(r_hash, 'little') % self._order | |
| # Step 3 | |
| R_pk = EccKey(point=r * self._key._curve.G)._export_eddsa() | |
| # Step 4 | |
| k_hash = SHAKE256.new(dom4 + R_pk + self._A + PHM).read(114) | |
| k = Integer.from_bytes(k_hash, 'little') % self._order | |
| # Step 5 | |
| s = (r + k * self._key.d) % self._order | |
| return R_pk + s.to_bytes(57, 'little') | |
| def verify(self, msg_or_hash, signature): | |
| """Check if an EdDSA signature is authentic. | |
| Args: | |
| msg_or_hash (bytes or a hash object): | |
| The message to verify (``bytes``, in case of *PureEdDSA*) or | |
| the hash that was carried out over the message (hash object, for *HashEdDSA*). | |
| The hash object must be :class:`Crypto.Hash.SHA512` object for Ed25519, | |
| and :class:`Crypto.Hash.SHAKE256` for Ed448. | |
| signature (``bytes``): | |
| The signature that needs to be validated. | |
| It must be 64 bytes for Ed25519, and 114 bytes for Ed448. | |
| :raise ValueError: if the signature is not authentic | |
| """ | |
| if self._key._curve.name == "ed25519": | |
| ph = isinstance(msg_or_hash, SHA512.SHA512Hash) | |
| if not (ph or is_bytes(msg_or_hash)): | |
| raise TypeError("'msg_or_hash' must be bytes of a SHA-512 hash") | |
| eddsa_verify_method = self._verify_ed25519 | |
| elif self._key._curve.name == "ed448": | |
| ph = isinstance(msg_or_hash, SHAKE256.SHAKE256_XOF) | |
| if not (ph or is_bytes(msg_or_hash)): | |
| raise TypeError("'msg_or_hash' must be bytes of a SHAKE256 hash") | |
| eddsa_verify_method = self._verify_ed448 | |
| else: | |
| raise ValueError("Incorrect curve for EdDSA") | |
| return eddsa_verify_method(msg_or_hash, signature, ph) | |
| def _verify_ed25519(self, msg_or_hash, signature, ph): | |
| if len(signature) != 64: | |
| raise ValueError("The signature is not authentic (length)") | |
| if self._context or ph: | |
| flag = int(ph) | |
| dom2 = b'SigEd25519 no Ed25519 collisions' + bchr(flag) + \ | |
| bchr(len(self._context)) + self._context | |
| else: | |
| dom2 = b'' | |
| PHM = msg_or_hash.digest() if ph else msg_or_hash | |
| # Section 5.1.7 | |
| # Step 1 | |
| try: | |
| R = import_public_key(signature[:32]).pointQ | |
| except ValueError: | |
| raise ValueError("The signature is not authentic (R)") | |
| s = Integer.from_bytes(signature[32:], 'little') | |
| if s > self._order: | |
| raise ValueError("The signature is not authentic (S)") | |
| # Step 2 | |
| k_hash = SHA512.new(dom2 + signature[:32] + self._A + PHM).digest() | |
| k = Integer.from_bytes(k_hash, 'little') % self._order | |
| # Step 3 | |
| point1 = s * 8 * self._key._curve.G | |
| # OPTIMIZE: with double-scalar multiplication, with no SCA | |
| # countermeasures because it is public values | |
| point2 = 8 * R + k * 8 * self._key.pointQ | |
| if point1 != point2: | |
| raise ValueError("The signature is not authentic") | |
| def _verify_ed448(self, msg_or_hash, signature, ph): | |
| if len(signature) != 114: | |
| raise ValueError("The signature is not authentic (length)") | |
| flag = int(ph) | |
| # dom4(flag, self._context) | |
| dom4 = b'SigEd448' + bchr(flag) + \ | |
| bchr(len(self._context)) + self._context | |
| PHM = msg_or_hash.read(64) if ph else msg_or_hash | |
| # Section 5.2.7 | |
| # Step 1 | |
| try: | |
| R = import_public_key(signature[:57]).pointQ | |
| except ValueError: | |
| raise ValueError("The signature is not authentic (R)") | |
| s = Integer.from_bytes(signature[57:], 'little') | |
| if s > self._order: | |
| raise ValueError("The signature is not authentic (S)") | |
| # Step 2 | |
| k_hash = SHAKE256.new(dom4 + signature[:57] + self._A + PHM).read(114) | |
| k = Integer.from_bytes(k_hash, 'little') % self._order | |
| # Step 3 | |
| point1 = s * 8 * self._key._curve.G | |
| # OPTIMIZE: with double-scalar multiplication, with no SCA | |
| # countermeasures because it is public values | |
| point2 = 8 * R + k * 8 * self._key.pointQ | |
| if point1 != point2: | |
| raise ValueError("The signature is not authentic") | |
| def new(key, mode, context=None): | |
| """Create a signature object :class:`EdDSASigScheme` that | |
| can perform or verify an EdDSA signature. | |
| Args: | |
| key (:class:`Crypto.PublicKey.ECC` object): | |
| The key to use for computing the signature (*private* keys only) | |
| or for verifying one. | |
| The key must be on the curve ``Ed25519`` or ``Ed448``. | |
| mode (string): | |
| This parameter must be ``'rfc8032'``. | |
| context (bytes): | |
| Up to 255 bytes of `context <https://datatracker.ietf.org/doc/html/rfc8032#page-41>`_, | |
| which is a constant byte string to segregate different protocols or | |
| different applications of the same key. | |
| """ | |
| if not isinstance(key, EccKey) or not key._is_eddsa(): | |
| raise ValueError("EdDSA can only be used with EdDSA keys") | |
| if mode != 'rfc8032': | |
| raise ValueError("Mode must be 'rfc8032'") | |
| if context is None: | |
| context = b'' | |
| elif len(context) > 255: | |
| raise ValueError("Context for EdDSA must not be longer than 255 bytes") | |
| return EdDSASigScheme(key, context) | |