slight update

This commit is contained in:
2024-12-01 04:19:04 +09:00
parent 00b0afd17a
commit 4dbe1bee11
3130 changed files with 508084 additions and 0 deletions

View File

@ -0,0 +1,403 @@
#
# Signature/DSS.py : DSS.py
#
# ===================================================================
#
# Copyright (c) 2014, 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.Util.asn1 import DerSequence
from Crypto.Util.number import long_to_bytes
from Crypto.Math.Numbers import Integer
from Crypto.Hash import HMAC
from Crypto.PublicKey.ECC import EccKey
from Crypto.PublicKey.DSA import DsaKey
__all__ = ['DssSigScheme', 'new']
class DssSigScheme(object):
"""A (EC)DSA signature object.
Do not instantiate directly.
Use :func:`Crypto.Signature.DSS.new`.
"""
def __init__(self, key, encoding, order):
"""Create a new Digital Signature Standard (DSS) object.
Do not instantiate this object directly,
use `Crypto.Signature.DSS.new` instead.
"""
self._key = key
self._encoding = encoding
self._order = order
self._order_bits = self._order.size_in_bits()
self._order_bytes = (self._order_bits - 1) // 8 + 1
def can_sign(self):
"""Return ``True`` if this signature object can be used
for signing messages."""
return self._key.has_private()
def _compute_nonce(self, msg_hash):
raise NotImplementedError("To be provided by subclasses")
def _valid_hash(self, msg_hash):
raise NotImplementedError("To be provided by subclasses")
def sign(self, msg_hash):
"""Compute the DSA/ECDSA signature of a message.
Args:
msg_hash (hash object):
The hash that was carried out over the message.
The object belongs to the :mod:`Crypto.Hash` package.
Under mode ``'fips-186-3'``, the hash must be a FIPS
approved secure hash (SHA-2 or SHA-3).
:return: The signature as ``bytes``
:raise ValueError: if the hash algorithm is incompatible to the (EC)DSA key
:raise TypeError: if the (EC)DSA key has no private half
"""
if not self._key.has_private():
raise TypeError("Private key is needed to sign")
if not self._valid_hash(msg_hash):
raise ValueError("Hash is not sufficiently strong")
# Generate the nonce k (critical!)
nonce = self._compute_nonce(msg_hash)
# Perform signature using the raw API
z = Integer.from_bytes(msg_hash.digest()[:self._order_bytes])
sig_pair = self._key._sign(z, nonce)
# Encode the signature into a single byte string
if self._encoding == 'binary':
output = b"".join([long_to_bytes(x, self._order_bytes)
for x in sig_pair])
else:
# Dss-sig ::= SEQUENCE {
# r INTEGER,
# s INTEGER
# }
# Ecdsa-Sig-Value ::= SEQUENCE {
# r INTEGER,
# s INTEGER
# }
output = DerSequence(sig_pair).encode()
return output
def verify(self, msg_hash, signature):
"""Check if a certain (EC)DSA signature is authentic.
Args:
msg_hash (hash object):
The hash that was carried out over the message.
This is an object belonging to the :mod:`Crypto.Hash` module.
Under mode ``'fips-186-3'``, the hash must be a FIPS
approved secure hash (SHA-2 or SHA-3).
signature (``bytes``):
The signature that needs to be validated.
:raise ValueError: if the signature is not authentic
"""
if not self._valid_hash(msg_hash):
raise ValueError("Hash is not sufficiently strong")
if self._encoding == 'binary':
if len(signature) != (2 * self._order_bytes):
raise ValueError("The signature is not authentic (length)")
r_prime, s_prime = [Integer.from_bytes(x)
for x in (signature[:self._order_bytes],
signature[self._order_bytes:])]
else:
try:
der_seq = DerSequence().decode(signature, strict=True)
except (ValueError, IndexError):
raise ValueError("The signature is not authentic (DER)")
if len(der_seq) != 2 or not der_seq.hasOnlyInts():
raise ValueError("The signature is not authentic (DER content)")
r_prime, s_prime = Integer(der_seq[0]), Integer(der_seq[1])
if not (0 < r_prime < self._order) or not (0 < s_prime < self._order):
raise ValueError("The signature is not authentic (d)")
z = Integer.from_bytes(msg_hash.digest()[:self._order_bytes])
result = self._key._verify(z, (r_prime, s_prime))
if not result:
raise ValueError("The signature is not authentic")
# Make PyCrypto code to fail
return False
class DeterministicDsaSigScheme(DssSigScheme):
# Also applicable to ECDSA
def __init__(self, key, encoding, order, private_key):
super(DeterministicDsaSigScheme, self).__init__(key, encoding, order)
self._private_key = private_key
def _bits2int(self, bstr):
"""See 2.3.2 in RFC6979"""
result = Integer.from_bytes(bstr)
q_len = self._order.size_in_bits()
b_len = len(bstr) * 8
if b_len > q_len:
# Only keep leftmost q_len bits
result >>= (b_len - q_len)
return result
def _int2octets(self, int_mod_q):
"""See 2.3.3 in RFC6979"""
assert 0 < int_mod_q < self._order
return long_to_bytes(int_mod_q, self._order_bytes)
def _bits2octets(self, bstr):
"""See 2.3.4 in RFC6979"""
z1 = self._bits2int(bstr)
if z1 < self._order:
z2 = z1
else:
z2 = z1 - self._order
return self._int2octets(z2)
def _compute_nonce(self, mhash):
"""Generate k in a deterministic way"""
# See section 3.2 in RFC6979.txt
# Step a
h1 = mhash.digest()
# Step b
mask_v = b'\x01' * mhash.digest_size
# Step c
nonce_k = b'\x00' * mhash.digest_size
for int_oct in (b'\x00', b'\x01'):
# Step d/f
nonce_k = HMAC.new(nonce_k,
mask_v + int_oct +
self._int2octets(self._private_key) +
self._bits2octets(h1), mhash).digest()
# Step e/g
mask_v = HMAC.new(nonce_k, mask_v, mhash).digest()
nonce = -1
while not (0 < nonce < self._order):
# Step h.C (second part)
if nonce != -1:
nonce_k = HMAC.new(nonce_k, mask_v + b'\x00',
mhash).digest()
mask_v = HMAC.new(nonce_k, mask_v, mhash).digest()
# Step h.A
mask_t = b""
# Step h.B
while len(mask_t) < self._order_bytes:
mask_v = HMAC.new(nonce_k, mask_v, mhash).digest()
mask_t += mask_v
# Step h.C (first part)
nonce = self._bits2int(mask_t)
return nonce
def _valid_hash(self, msg_hash):
return True
class FipsDsaSigScheme(DssSigScheme):
#: List of L (bit length of p) and N (bit length of q) combinations
#: that are allowed by FIPS 186-3. The security level is provided in
#: Table 2 of FIPS 800-57 (rev3).
_fips_186_3_L_N = (
(1024, 160), # 80 bits (SHA-1 or stronger)
(2048, 224), # 112 bits (SHA-224 or stronger)
(2048, 256), # 128 bits (SHA-256 or stronger)
(3072, 256) # 256 bits (SHA-512)
)
def __init__(self, key, encoding, order, randfunc):
super(FipsDsaSigScheme, self).__init__(key, encoding, order)
self._randfunc = randfunc
L = Integer(key.p).size_in_bits()
if (L, self._order_bits) not in self._fips_186_3_L_N:
error = ("L/N (%d, %d) is not compliant to FIPS 186-3"
% (L, self._order_bits))
raise ValueError(error)
def _compute_nonce(self, msg_hash):
# hash is not used
return Integer.random_range(min_inclusive=1,
max_exclusive=self._order,
randfunc=self._randfunc)
def _valid_hash(self, msg_hash):
"""Verify that SHA-1, SHA-2 or SHA-3 are used"""
return (msg_hash.oid == "1.3.14.3.2.26" or
msg_hash.oid.startswith("2.16.840.1.101.3.4.2."))
class FipsEcDsaSigScheme(DssSigScheme):
def __init__(self, key, encoding, order, randfunc):
super(FipsEcDsaSigScheme, self).__init__(key, encoding, order)
self._randfunc = randfunc
def _compute_nonce(self, msg_hash):
return Integer.random_range(min_inclusive=1,
max_exclusive=self._key._curve.order,
randfunc=self._randfunc)
def _valid_hash(self, msg_hash):
"""Verify that the strength of the hash matches or exceeds
the strength of the EC. We fail if the hash is too weak."""
modulus_bits = self._key.pointQ.size_in_bits()
# SHS: SHA-2, SHA-3, truncated SHA-512
sha224 = ("2.16.840.1.101.3.4.2.4", "2.16.840.1.101.3.4.2.7", "2.16.840.1.101.3.4.2.5")
sha256 = ("2.16.840.1.101.3.4.2.1", "2.16.840.1.101.3.4.2.8", "2.16.840.1.101.3.4.2.6")
sha384 = ("2.16.840.1.101.3.4.2.2", "2.16.840.1.101.3.4.2.9")
sha512 = ("2.16.840.1.101.3.4.2.3", "2.16.840.1.101.3.4.2.10")
shs = sha224 + sha256 + sha384 + sha512
try:
result = msg_hash.oid in shs
except AttributeError:
result = False
return result
def new(key, mode, encoding='binary', randfunc=None):
"""Create a signature object :class:`DssSigScheme` that
can perform (EC)DSA signature or verification.
.. note::
Refer to `NIST SP 800 Part 1 Rev 4`_ (or newer release) for an
overview of the recommended key lengths.
Args:
key (:class:`Crypto.PublicKey.DSA` or :class:`Crypto.PublicKey.ECC`):
The key to use for computing the signature (*private* keys only)
or for verifying one.
For DSA keys, let ``L`` and ``N`` be the bit lengths of the modulus ``p``
and of ``q``: the pair ``(L,N)`` must appear in the following list,
in compliance to section 4.2 of `FIPS 186-4`_:
- (1024, 160) *legacy only; do not create new signatures with this*
- (2048, 224) *deprecated; do not create new signatures with this*
- (2048, 256)
- (3072, 256)
For ECC, only keys over P-224, P-256, P-384, and P-521 are accepted.
mode (string):
The parameter can take these values:
- ``'fips-186-3'``. The signature generation is randomized and carried out
according to `FIPS 186-3`_: the nonce ``k`` is taken from the RNG.
- ``'deterministic-rfc6979'``. The signature generation is not
randomized. See RFC6979_.
encoding (string):
How the signature is encoded. This value determines the output of
:meth:`sign` and the input to :meth:`verify`.
The following values are accepted:
- ``'binary'`` (default), the signature is the raw concatenation
of ``r`` and ``s``. It is defined in the IEEE P.1363 standard.
For DSA, the size in bytes of the signature is ``N/4`` bytes
(e.g. 64 for ``N=256``).
For ECDSA, the signature is always twice the length of a point
coordinate (e.g. 64 bytes for P-256).
- ``'der'``, the signature is a ASN.1 DER SEQUENCE
with two INTEGERs (``r`` and ``s``). It is defined in RFC3279_.
The size of the signature is variable.
randfunc (callable):
A function that returns random ``bytes``, of a given length.
If omitted, the internal RNG is used.
Only applicable for the *'fips-186-3'* mode.
.. _FIPS 186-3: http://csrc.nist.gov/publications/fips/fips186-3/fips_186-3.pdf
.. _FIPS 186-4: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
.. _NIST SP 800 Part 1 Rev 4: http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-57pt1r4.pdf
.. _RFC6979: http://tools.ietf.org/html/rfc6979
.. _RFC3279: https://tools.ietf.org/html/rfc3279#section-2.2.2
"""
# The goal of the 'mode' parameter is to avoid to
# have the current version of the standard as default.
#
# Over time, such version will be superseded by (for instance)
# FIPS 186-4 and it will be odd to have -3 as default.
if encoding not in ('binary', 'der'):
raise ValueError("Unknown encoding '%s'" % encoding)
if isinstance(key, EccKey):
order = key._curve.order
private_key_attr = 'd'
if not key.curve.startswith("NIST"):
raise ValueError("ECC key is not on a NIST P curve")
elif isinstance(key, DsaKey):
order = Integer(key.q)
private_key_attr = 'x'
else:
raise ValueError("Unsupported key type " + str(type(key)))
if key.has_private():
private_key = getattr(key, private_key_attr)
else:
private_key = None
if mode == 'deterministic-rfc6979':
return DeterministicDsaSigScheme(key, encoding, order, private_key)
elif mode == 'fips-186-3':
if isinstance(key, EccKey):
return FipsEcDsaSigScheme(key, encoding, order, randfunc)
else:
return FipsDsaSigScheme(key, encoding, order, randfunc)
else:
raise ValueError("Unknown DSS mode '%s'" % mode)

View File

@ -0,0 +1,27 @@
from typing import Union, Optional, Callable
from typing_extensions import Protocol
from Crypto.PublicKey.DSA import DsaKey
from Crypto.PublicKey.ECC import EccKey
class Hash(Protocol):
def digest(self) -> bytes: ...
__all__ = ['new']
class DssSigScheme:
def __init__(self, key: Union[DsaKey, EccKey], encoding: str, order: int) -> None: ...
def can_sign(self) -> bool: ...
def sign(self, msg_hash: Hash) -> bytes: ...
def verify(self, msg_hash: Hash, signature: bytes) -> bool: ...
class DeterministicDsaSigScheme(DssSigScheme):
def __init__(self, key, encoding, order, private_key) -> None: ...
class FipsDsaSigScheme(DssSigScheme):
def __init__(self, key: DsaKey, encoding: str, order: int, randfunc: Callable) -> None: ...
class FipsEcDsaSigScheme(DssSigScheme):
def __init__(self, key: EccKey, encoding: str, order: int, randfunc: Callable) -> None: ...
def new(key: Union[DsaKey, EccKey], mode: str, encoding: Optional[str]='binary', randfunc: Optional[Callable]=None) -> Union[DeterministicDsaSigScheme, FipsDsaSigScheme, FipsEcDsaSigScheme]: ...

View File

@ -0,0 +1,55 @@
# ===================================================================
#
# Copyright (c) 2014, 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.
# ===================================================================
"""
Legacy module for PKCS#1 PSS signatures.
:undocumented: __package__
"""
import types
from Crypto.Signature import pss
def _pycrypto_verify(self, hash_object, signature):
try:
self._verify(hash_object, signature)
except (ValueError, TypeError):
return False
return True
def new(rsa_key, mgfunc=None, saltLen=None, randfunc=None):
pkcs1 = pss.new(rsa_key, mask_func=mgfunc,
salt_bytes=saltLen, rand_func=randfunc)
pkcs1._verify = pkcs1.verify
pkcs1.verify = types.MethodType(_pycrypto_verify, pkcs1)
return pkcs1

View File

@ -0,0 +1,28 @@
from typing import Union, Callable, Optional
from typing_extensions import Protocol
from Crypto.PublicKey.RSA import RsaKey
class Hash(Protocol):
def digest(self) -> bytes: ...
def update(self, bytes) -> None: ...
class HashModule(Protocol):
@staticmethod
def new(data: Optional[bytes]) -> Hash: ...
MaskFunction = Callable[[bytes, int, Union[Hash, HashModule]], bytes]
RndFunction = Callable[[int], bytes]
class PSS_SigScheme:
def __init__(self, key: RsaKey, mgfunc: MaskFunction, saltLen: int, randfunc: RndFunction) -> None: ...
def can_sign(self) -> bool: ...
def sign(self, msg_hash: Hash) -> bytes: ...
def verify(self, msg_hash: Hash, signature: bytes) -> bool: ...
def new(rsa_key: RsaKey, mgfunc: Optional[MaskFunction]=None, saltLen: Optional[int]=None, randfunc: Optional[RndFunction]=None) -> PSS_SigScheme: ...

View File

@ -0,0 +1,53 @@
# ===================================================================
#
# Copyright (c) 2014, 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.
# ===================================================================
"""
Legacy module for PKCS#1 v1.5 signatures.
:undocumented: __package__
"""
import types
from Crypto.Signature import pkcs1_15
def _pycrypto_verify(self, hash_object, signature):
try:
self._verify(hash_object, signature)
except (ValueError, TypeError):
return False
return True
def new(rsa_key):
pkcs1 = pkcs1_15.new(rsa_key)
pkcs1._verify = pkcs1.verify
pkcs1.verify = types.MethodType(_pycrypto_verify, pkcs1)
return pkcs1

View File

@ -0,0 +1,16 @@
from typing import Optional
from typing_extensions import Protocol
from Crypto.PublicKey.RSA import RsaKey
class Hash(Protocol):
def digest(self) -> bytes: ...
class PKCS115_SigScheme:
def __init__(self, rsa_key: RsaKey) -> None: ...
def can_sign(self) -> bool: ...
def sign(self, msg_hash: Hash) -> bytes: ...
def verify(self, msg_hash: Hash, signature: bytes) -> bool: ...
def new(rsa_key: RsaKey) -> PKCS115_SigScheme: ...

View File

@ -0,0 +1,36 @@
# ===================================================================
#
# Copyright (c) 2014, 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.
# ===================================================================
"""Digital signature protocols
A collection of standardized protocols to carry out digital signatures.
"""
__all__ = ['PKCS1_v1_5', 'PKCS1_PSS', 'DSS', 'pkcs1_15', 'pss', 'eddsa']

View File

@ -0,0 +1,343 @@
# ===================================================================
#
# 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_public()
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 == "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 == "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_public()
# 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_public()
# 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 == "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 == "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 key.curve not in ("Ed25519", "Ed448"):
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)

View File

@ -0,0 +1,21 @@
from typing import Union, Optional
from typing_extensions import Protocol
from Crypto.PublicKey.ECC import EccKey
class Hash(Protocol):
def digest(self) -> bytes: ...
class XOF(Protocol):
def read(self, len: int) -> bytes: ...
def import_public_key(encoded: bytes) -> EccKey: ...
def import_private_key(encoded: bytes) -> EccKey: ...
class EdDSASigScheme(object):
def __init__(self, key: EccKey, context: bytes) -> None: ...
def can_sign(self) -> bool: ...
def sign(self, msg_or_hash: Union[bytes, Hash, XOF]) -> bytes: ...
def verify(self, msg_or_hash: Union[bytes, Hash, XOF], signature: bytes) -> None: ...
def new(key: EccKey, mode: str, context: Optional[bytes]=None) -> EdDSASigScheme: ...

View File

@ -0,0 +1,223 @@
# ===================================================================
#
# Copyright (c) 2014, 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 Crypto.Util.number
from Crypto.Util.number import ceil_div, bytes_to_long, long_to_bytes
from Crypto.Util.asn1 import DerSequence, DerNull, DerOctetString, DerObjectId
class PKCS115_SigScheme:
"""A signature object for ``RSASSA-PKCS1-v1_5``.
Do not instantiate directly.
Use :func:`Crypto.Signature.pkcs1_15.new`.
"""
def __init__(self, rsa_key):
"""Initialize this PKCS#1 v1.5 signature scheme object.
:Parameters:
rsa_key : an RSA key object
Creation of signatures is only possible if this is a *private*
RSA key. Verification of signatures is always possible.
"""
self._key = rsa_key
def can_sign(self):
"""Return ``True`` if this object can be used to sign messages."""
return self._key.has_private()
def sign(self, msg_hash):
"""Create the PKCS#1 v1.5 signature of a message.
This function is also called ``RSASSA-PKCS1-V1_5-SIGN`` and
it is specified in
`section 8.2.1 of RFC8017 <https://tools.ietf.org/html/rfc8017#page-36>`_.
:parameter msg_hash:
This is an object from the :mod:`Crypto.Hash` package.
It has been used to digest the message to sign.
:type msg_hash: hash object
:return: the signature encoded as a *byte string*.
:raise ValueError: if the RSA key is not long enough for the given hash algorithm.
:raise TypeError: if the RSA key has no private half.
"""
# See 8.2.1 in RFC3447
modBits = Crypto.Util.number.size(self._key.n)
k = ceil_div(modBits,8) # Convert from bits to bytes
# Step 1
em = _EMSA_PKCS1_V1_5_ENCODE(msg_hash, k)
# Step 2a (OS2IP)
em_int = bytes_to_long(em)
# Step 2b (RSASP1) and Step 2c (I2OSP)
signature = self._key._decrypt_to_bytes(em_int)
# Verify no faults occurred
if em_int != pow(bytes_to_long(signature), self._key.e, self._key.n):
raise ValueError("Fault detected in RSA private key operation")
return signature
def verify(self, msg_hash, signature):
"""Check if the PKCS#1 v1.5 signature over a message is valid.
This function is also called ``RSASSA-PKCS1-V1_5-VERIFY`` and
it is specified in
`section 8.2.2 of RFC8037 <https://tools.ietf.org/html/rfc8017#page-37>`_.
:parameter msg_hash:
The hash that was carried out over the message. This is an object
belonging to the :mod:`Crypto.Hash` module.
:type parameter: hash object
:parameter signature:
The signature that needs to be validated.
:type signature: byte string
:raise ValueError: if the signature is not valid.
"""
# See 8.2.2 in RFC3447
modBits = Crypto.Util.number.size(self._key.n)
k = ceil_div(modBits, 8) # Convert from bits to bytes
# Step 1
if len(signature) != k:
raise ValueError("Invalid signature")
# Step 2a (O2SIP)
signature_int = bytes_to_long(signature)
# Step 2b (RSAVP1)
em_int = self._key._encrypt(signature_int)
# Step 2c (I2OSP)
em1 = long_to_bytes(em_int, k)
# Step 3
try:
possible_em1 = [ _EMSA_PKCS1_V1_5_ENCODE(msg_hash, k, True) ]
# MD2/4/5 hashes always require NULL params in AlgorithmIdentifier.
# For all others, it is optional.
try:
algorithm_is_md = msg_hash.oid.startswith('1.2.840.113549.2.')
except AttributeError:
algorithm_is_md = False
if not algorithm_is_md: # MD2/MD4/MD5
possible_em1.append(_EMSA_PKCS1_V1_5_ENCODE(msg_hash, k, False))
except ValueError:
raise ValueError("Invalid signature")
# Step 4
# By comparing the full encodings (as opposed to checking each
# of its components one at a time) we avoid attacks to the padding
# scheme like Bleichenbacher's (see http://www.mail-archive.com/cryptography@metzdowd.com/msg06537).
#
if em1 not in possible_em1:
raise ValueError("Invalid signature")
pass
def _EMSA_PKCS1_V1_5_ENCODE(msg_hash, emLen, with_hash_parameters=True):
"""
Implement the ``EMSA-PKCS1-V1_5-ENCODE`` function, as defined
in PKCS#1 v2.1 (RFC3447, 9.2).
``_EMSA-PKCS1-V1_5-ENCODE`` actually accepts the message ``M`` as input,
and hash it internally. Here, we expect that the message has already
been hashed instead.
:Parameters:
msg_hash : hash object
The hash object that holds the digest of the message being signed.
emLen : int
The length the final encoding must have, in bytes.
with_hash_parameters : bool
If True (default), include NULL parameters for the hash
algorithm in the ``digestAlgorithm`` SEQUENCE.
:attention: the early standard (RFC2313) stated that ``DigestInfo``
had to be BER-encoded. This means that old signatures
might have length tags in indefinite form, which
is not supported in DER. Such encoding cannot be
reproduced by this function.
:Return: An ``emLen`` byte long string that encodes the hash.
"""
# First, build the ASN.1 DER object DigestInfo:
#
# DigestInfo ::= SEQUENCE {
# digestAlgorithm AlgorithmIdentifier,
# digest OCTET STRING
# }
#
# where digestAlgorithm identifies the hash function and shall be an
# algorithm ID with an OID in the set PKCS1-v1-5DigestAlgorithms.
#
# PKCS1-v1-5DigestAlgorithms ALGORITHM-IDENTIFIER ::= {
# { OID id-md2 PARAMETERS NULL }|
# { OID id-md5 PARAMETERS NULL }|
# { OID id-sha1 PARAMETERS NULL }|
# { OID id-sha256 PARAMETERS NULL }|
# { OID id-sha384 PARAMETERS NULL }|
# { OID id-sha512 PARAMETERS NULL }
# }
#
# Appendix B.1 also says that for SHA-1/-2 algorithms, the parameters
# should be omitted. They may be present, but when they are, they shall
# have NULL value.
digestAlgo = DerSequence([ DerObjectId(msg_hash.oid).encode() ])
if with_hash_parameters:
digestAlgo.append(DerNull().encode())
digest = DerOctetString(msg_hash.digest())
digestInfo = DerSequence([
digestAlgo.encode(),
digest.encode()
]).encode()
# We need at least 11 bytes for the remaining data: 3 fixed bytes and
# at least 8 bytes of padding).
if emLen<len(digestInfo)+11:
raise TypeError("DigestInfo is too long for this RSA key (%d bytes)." % len(digestInfo))
PS = b'\xFF' * (emLen - len(digestInfo) - 3)
return b'\x00\x01' + PS + b'\x00' + digestInfo
def new(rsa_key):
"""Create a signature object for creating
or verifying PKCS#1 v1.5 signatures.
:parameter rsa_key:
The RSA key to use for signing or verifying the message.
This is a :class:`Crypto.PublicKey.RSA` object.
Signing is only possible when ``rsa_key`` is a **private** RSA key.
:type rsa_key: RSA object
:return: a :class:`PKCS115_SigScheme` signature object
"""
return PKCS115_SigScheme(rsa_key)

View File

@ -0,0 +1,17 @@
from typing import Optional
from typing_extensions import Protocol
from Crypto.PublicKey.RSA import RsaKey
class Hash(Protocol):
def digest(self) -> bytes: ...
class PKCS115_SigScheme:
def __init__(self, rsa_key: RsaKey) -> None: ...
def can_sign(self) -> bool: ...
def sign(self, msg_hash: Hash) -> bytes: ...
def verify(self, msg_hash: Hash, signature: bytes) -> None: ...
def _EMSA_PKCS1_V1_5_ENCODE(msg_hash: Hash, emLen: int, with_hash_parameters: Optional[bool]=True) -> bytes: ...
def new(rsa_key: RsaKey) -> PKCS115_SigScheme: ...

View File

@ -0,0 +1,387 @@
# ===================================================================
#
# Copyright (c) 2014, 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.Util.py3compat import bchr, bord, iter_range
import Crypto.Util.number
from Crypto.Util.number import (ceil_div,
long_to_bytes,
bytes_to_long
)
from Crypto.Util.strxor import strxor
from Crypto import Random
class PSS_SigScheme:
"""A signature object for ``RSASSA-PSS``.
Do not instantiate directly.
Use :func:`Crypto.Signature.pss.new`.
"""
def __init__(self, key, mgfunc, saltLen, randfunc):
"""Initialize this PKCS#1 PSS signature scheme object.
:Parameters:
key : an RSA key object
If a private half is given, both signature and
verification are possible.
If a public half is given, only verification is possible.
mgfunc : callable
A mask generation function that accepts two parameters:
a string to use as seed, and the lenth of the mask to
generate, in bytes.
saltLen : integer
Length of the salt, in bytes.
randfunc : callable
A function that returns random bytes.
"""
self._key = key
self._saltLen = saltLen
self._mgfunc = mgfunc
self._randfunc = randfunc
def can_sign(self):
"""Return ``True`` if this object can be used to sign messages."""
return self._key.has_private()
def sign(self, msg_hash):
"""Create the PKCS#1 PSS signature of a message.
This function is also called ``RSASSA-PSS-SIGN`` and
it is specified in
`section 8.1.1 of RFC8017 <https://tools.ietf.org/html/rfc8017#section-8.1.1>`_.
:parameter msg_hash:
This is an object from the :mod:`Crypto.Hash` package.
It has been used to digest the message to sign.
:type msg_hash: hash object
:return: the signature encoded as a *byte string*.
:raise ValueError: if the RSA key is not long enough for the given hash algorithm.
:raise TypeError: if the RSA key has no private half.
"""
# Set defaults for salt length and mask generation function
if self._saltLen is None:
sLen = msg_hash.digest_size
else:
sLen = self._saltLen
if self._mgfunc is None:
mgf = lambda x, y: MGF1(x, y, msg_hash)
else:
mgf = self._mgfunc
modBits = Crypto.Util.number.size(self._key.n)
# See 8.1.1 in RFC3447
k = ceil_div(modBits, 8) # k is length in bytes of the modulus
# Step 1
em = _EMSA_PSS_ENCODE(msg_hash, modBits-1, self._randfunc, mgf, sLen)
# Step 2a (OS2IP)
em_int = bytes_to_long(em)
# Step 2b (RSASP1) and Step 2c (I2OSP)
signature = self._key._decrypt_to_bytes(em_int)
# Verify no faults occurred
if em_int != pow(bytes_to_long(signature), self._key.e, self._key.n):
raise ValueError("Fault detected in RSA private key operation")
return signature
def verify(self, msg_hash, signature):
"""Check if the PKCS#1 PSS signature over a message is valid.
This function is also called ``RSASSA-PSS-VERIFY`` and
it is specified in
`section 8.1.2 of RFC8037 <https://tools.ietf.org/html/rfc8017#section-8.1.2>`_.
:parameter msg_hash:
The hash that was carried out over the message. This is an object
belonging to the :mod:`Crypto.Hash` module.
:type parameter: hash object
:parameter signature:
The signature that needs to be validated.
:type signature: bytes
:raise ValueError: if the signature is not valid.
"""
# Set defaults for salt length and mask generation function
if self._saltLen is None:
sLen = msg_hash.digest_size
else:
sLen = self._saltLen
if self._mgfunc:
mgf = self._mgfunc
else:
mgf = lambda x, y: MGF1(x, y, msg_hash)
modBits = Crypto.Util.number.size(self._key.n)
# See 8.1.2 in RFC3447
k = ceil_div(modBits, 8) # Convert from bits to bytes
# Step 1
if len(signature) != k:
raise ValueError("Incorrect signature")
# Step 2a (O2SIP)
signature_int = bytes_to_long(signature)
# Step 2b (RSAVP1)
em_int = self._key._encrypt(signature_int)
# Step 2c (I2OSP)
emLen = ceil_div(modBits - 1, 8)
em = long_to_bytes(em_int, emLen)
# Step 3/4
_EMSA_PSS_VERIFY(msg_hash, em, modBits-1, mgf, sLen)
def MGF1(mgfSeed, maskLen, hash_gen):
"""Mask Generation Function, described in `B.2.1 of RFC8017
<https://tools.ietf.org/html/rfc8017>`_.
:param mfgSeed:
seed from which the mask is generated
:type mfgSeed: byte string
:param maskLen:
intended length in bytes of the mask
:type maskLen: integer
:param hash_gen:
A module or a hash object from :mod:`Crypto.Hash`
:type hash_object:
:return: the mask, as a *byte string*
"""
T = b""
for counter in iter_range(ceil_div(maskLen, hash_gen.digest_size)):
c = long_to_bytes(counter, 4)
hobj = hash_gen.new()
hobj.update(mgfSeed + c)
T = T + hobj.digest()
assert(len(T) >= maskLen)
return T[:maskLen]
def _EMSA_PSS_ENCODE(mhash, emBits, randFunc, mgf, sLen):
r"""
Implement the ``EMSA-PSS-ENCODE`` function, as defined
in PKCS#1 v2.1 (RFC3447, 9.1.1).
The original ``EMSA-PSS-ENCODE`` actually accepts the message ``M``
as input, and hash it internally. Here, we expect that the message
has already been hashed instead.
:Parameters:
mhash : hash object
The hash object that holds the digest of the message being signed.
emBits : int
Maximum length of the final encoding, in bits.
randFunc : callable
An RNG function that accepts as only parameter an int, and returns
a string of random bytes, to be used as salt.
mgf : callable
A mask generation function that accepts two parameters: a string to
use as seed, and the lenth of the mask to generate, in bytes.
sLen : int
Length of the salt, in bytes.
:Return: An ``emLen`` byte long string that encodes the hash
(with ``emLen = \ceil(emBits/8)``).
:Raise ValueError:
When digest or salt length are too big.
"""
emLen = ceil_div(emBits, 8)
# Bitmask of digits that fill up
lmask = 0
for i in iter_range(8*emLen-emBits):
lmask = lmask >> 1 | 0x80
# Step 1 and 2 have been already done
# Step 3
if emLen < mhash.digest_size+sLen+2:
raise ValueError("Digest or salt length are too long"
" for given key size.")
# Step 4
salt = randFunc(sLen)
# Step 5
m_prime = bchr(0)*8 + mhash.digest() + salt
# Step 6
h = mhash.new()
h.update(m_prime)
# Step 7
ps = bchr(0)*(emLen-sLen-mhash.digest_size-2)
# Step 8
db = ps + bchr(1) + salt
# Step 9
dbMask = mgf(h.digest(), emLen-mhash.digest_size-1)
# Step 10
maskedDB = strxor(db, dbMask)
# Step 11
maskedDB = bchr(bord(maskedDB[0]) & ~lmask) + maskedDB[1:]
# Step 12
em = maskedDB + h.digest() + bchr(0xBC)
return em
def _EMSA_PSS_VERIFY(mhash, em, emBits, mgf, sLen):
"""
Implement the ``EMSA-PSS-VERIFY`` function, as defined
in PKCS#1 v2.1 (RFC3447, 9.1.2).
``EMSA-PSS-VERIFY`` actually accepts the message ``M`` as input,
and hash it internally. Here, we expect that the message has already
been hashed instead.
:Parameters:
mhash : hash object
The hash object that holds the digest of the message to be verified.
em : string
The signature to verify, therefore proving that the sender really
signed the message that was received.
emBits : int
Length of the final encoding (em), in bits.
mgf : callable
A mask generation function that accepts two parameters: a string to
use as seed, and the lenth of the mask to generate, in bytes.
sLen : int
Length of the salt, in bytes.
:Raise ValueError:
When the encoding is inconsistent, or the digest or salt lengths
are too big.
"""
emLen = ceil_div(emBits, 8)
# Bitmask of digits that fill up
lmask = 0
for i in iter_range(8*emLen-emBits):
lmask = lmask >> 1 | 0x80
# Step 1 and 2 have been already done
# Step 3
if emLen < mhash.digest_size+sLen+2:
raise ValueError("Incorrect signature")
# Step 4
if ord(em[-1:]) != 0xBC:
raise ValueError("Incorrect signature")
# Step 5
maskedDB = em[:emLen-mhash.digest_size-1]
h = em[emLen-mhash.digest_size-1:-1]
# Step 6
if lmask & bord(em[0]):
raise ValueError("Incorrect signature")
# Step 7
dbMask = mgf(h, emLen-mhash.digest_size-1)
# Step 8
db = strxor(maskedDB, dbMask)
# Step 9
db = bchr(bord(db[0]) & ~lmask) + db[1:]
# Step 10
if not db.startswith(bchr(0)*(emLen-mhash.digest_size-sLen-2) + bchr(1)):
raise ValueError("Incorrect signature")
# Step 11
if sLen > 0:
salt = db[-sLen:]
else:
salt = b""
# Step 12
m_prime = bchr(0)*8 + mhash.digest() + salt
# Step 13
hobj = mhash.new()
hobj.update(m_prime)
hp = hobj.digest()
# Step 14
if h != hp:
raise ValueError("Incorrect signature")
def new(rsa_key, **kwargs):
"""Create an object for making or verifying PKCS#1 PSS signatures.
:parameter rsa_key:
The RSA key to use for signing or verifying the message.
This is a :class:`Crypto.PublicKey.RSA` object.
Signing is only possible when ``rsa_key`` is a **private** RSA key.
:type rsa_key: RSA object
:Keyword Arguments:
* *mask_func* (``callable``) --
A function that returns the mask (as `bytes`).
It must accept two parameters: a seed (as `bytes`)
and the length of the data to return.
If not specified, it will be the function :func:`MGF1` defined in
`RFC8017 <https://tools.ietf.org/html/rfc8017#page-67>`_ and
combined with the same hash algorithm applied to the
message to sign or verify.
If you want to use a different function, for instance still :func:`MGF1`
but together with another hash, you can do::
from Crypto.Hash import SHA256
from Crypto.Signature.pss import MGF1
mgf = lambda x, y: MGF1(x, y, SHA256)
* *salt_bytes* (``integer``) --
Length of the salt, in bytes.
It is a value between 0 and ``emLen - hLen - 2``, where ``emLen``
is the size of the RSA modulus and ``hLen`` is the size of the digest
applied to the message to sign or verify.
The salt is generated internally, you don't need to provide it.
If not specified, the salt length will be ``hLen``.
If it is zero, the signature scheme becomes deterministic.
Note that in some implementations such as OpenSSL the default
salt length is ``emLen - hLen - 2`` (even though it is not more
secure than ``hLen``).
* *rand_func* (``callable``) --
A function that returns random ``bytes``, of the desired length.
The default is :func:`Crypto.Random.get_random_bytes`.
:return: a :class:`PSS_SigScheme` signature object
"""
mask_func = kwargs.pop("mask_func", None)
salt_len = kwargs.pop("salt_bytes", None)
rand_func = kwargs.pop("rand_func", None)
if rand_func is None:
rand_func = Random.get_random_bytes
if kwargs:
raise ValueError("Unknown keywords: " + str(kwargs.keys()))
return PSS_SigScheme(rsa_key, mask_func, salt_len, rand_func)

View File

@ -0,0 +1,30 @@
from typing import Union, Callable, Optional
from typing_extensions import Protocol
from Crypto.PublicKey.RSA import RsaKey
class Hash(Protocol):
def digest(self) -> bytes: ...
def update(self, bytes) -> None: ...
class HashModule(Protocol):
@staticmethod
def new(data: Optional[bytes]) -> Hash: ...
MaskFunction = Callable[[bytes, int, Union[Hash, HashModule]], bytes]
RndFunction = Callable[[int], bytes]
class PSS_SigScheme:
def __init__(self, key: RsaKey, mgfunc: MaskFunction, saltLen: int, randfunc: RndFunction) -> None: ...
def can_sign(self) -> bool: ...
def sign(self, msg_hash: Hash) -> bytes: ...
def verify(self, msg_hash: Hash, signature: bytes) -> None: ...
MGF1 : MaskFunction
def _EMSA_PSS_ENCODE(mhash: Hash, emBits: int, randFunc: RndFunction, mgf:MaskFunction, sLen: int) -> str: ...
def _EMSA_PSS_VERIFY(mhash: Hash, em: str, emBits: int, mgf: MaskFunction, sLen: int) -> None: ...
def new(rsa_key: RsaKey, **kwargs: Union[MaskFunction, RndFunction, int]) -> PSS_SigScheme: ...