859 lines
28 KiB
Python
859 lines
28 KiB
Python
|
# Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
|
||
|
"""RSA key generation code.
|
||
|
|
||
|
Create new keys with the newkeys() function. It will give you a PublicKey and a
|
||
|
PrivateKey object.
|
||
|
|
||
|
Loading and saving keys requires the pyasn1 module. This module is imported as
|
||
|
late as possible, such that other functionality will remain working in absence
|
||
|
of pyasn1.
|
||
|
|
||
|
.. note::
|
||
|
|
||
|
Storing public and private keys via the `pickle` module is possible.
|
||
|
However, it is insecure to load a key from an untrusted source.
|
||
|
The pickle module is not secure against erroneous or maliciously
|
||
|
constructed data. Never unpickle data received from an untrusted
|
||
|
or unauthenticated source.
|
||
|
|
||
|
"""
|
||
|
|
||
|
import threading
|
||
|
import typing
|
||
|
import warnings
|
||
|
|
||
|
import rsa.prime
|
||
|
import rsa.pem
|
||
|
import rsa.common
|
||
|
import rsa.randnum
|
||
|
import rsa.core
|
||
|
|
||
|
|
||
|
DEFAULT_EXPONENT = 65537
|
||
|
|
||
|
|
||
|
T = typing.TypeVar("T", bound="AbstractKey")
|
||
|
|
||
|
|
||
|
class AbstractKey:
|
||
|
"""Abstract superclass for private and public keys."""
|
||
|
|
||
|
__slots__ = ("n", "e", "blindfac", "blindfac_inverse", "mutex")
|
||
|
|
||
|
def __init__(self, n: int, e: int) -> None:
|
||
|
self.n = n
|
||
|
self.e = e
|
||
|
|
||
|
# These will be computed properly on the first call to blind().
|
||
|
self.blindfac = self.blindfac_inverse = -1
|
||
|
|
||
|
# Used to protect updates to the blinding factor in multi-threaded
|
||
|
# environments.
|
||
|
self.mutex = threading.Lock()
|
||
|
|
||
|
@classmethod
|
||
|
def _load_pkcs1_pem(cls: typing.Type[T], keyfile: bytes) -> T:
|
||
|
"""Loads a key in PKCS#1 PEM format, implement in a subclass.
|
||
|
|
||
|
:param keyfile: contents of a PEM-encoded file that contains
|
||
|
the public key.
|
||
|
:type keyfile: bytes
|
||
|
|
||
|
:return: the loaded key
|
||
|
:rtype: AbstractKey
|
||
|
"""
|
||
|
|
||
|
@classmethod
|
||
|
def _load_pkcs1_der(cls: typing.Type[T], keyfile: bytes) -> T:
|
||
|
"""Loads a key in PKCS#1 PEM format, implement in a subclass.
|
||
|
|
||
|
:param keyfile: contents of a DER-encoded file that contains
|
||
|
the public key.
|
||
|
:type keyfile: bytes
|
||
|
|
||
|
:return: the loaded key
|
||
|
:rtype: AbstractKey
|
||
|
"""
|
||
|
|
||
|
def _save_pkcs1_pem(self) -> bytes:
|
||
|
"""Saves the key in PKCS#1 PEM format, implement in a subclass.
|
||
|
|
||
|
:returns: the PEM-encoded key.
|
||
|
:rtype: bytes
|
||
|
"""
|
||
|
|
||
|
def _save_pkcs1_der(self) -> bytes:
|
||
|
"""Saves the key in PKCS#1 DER format, implement in a subclass.
|
||
|
|
||
|
:returns: the DER-encoded key.
|
||
|
:rtype: bytes
|
||
|
"""
|
||
|
|
||
|
@classmethod
|
||
|
def load_pkcs1(cls: typing.Type[T], keyfile: bytes, format: str = "PEM") -> T:
|
||
|
"""Loads a key in PKCS#1 DER or PEM format.
|
||
|
|
||
|
:param keyfile: contents of a DER- or PEM-encoded file that contains
|
||
|
the key.
|
||
|
:type keyfile: bytes
|
||
|
:param format: the format of the file to load; 'PEM' or 'DER'
|
||
|
:type format: str
|
||
|
|
||
|
:return: the loaded key
|
||
|
:rtype: AbstractKey
|
||
|
"""
|
||
|
|
||
|
methods = {
|
||
|
"PEM": cls._load_pkcs1_pem,
|
||
|
"DER": cls._load_pkcs1_der,
|
||
|
}
|
||
|
|
||
|
method = cls._assert_format_exists(format, methods)
|
||
|
return method(keyfile)
|
||
|
|
||
|
@staticmethod
|
||
|
def _assert_format_exists(
|
||
|
file_format: str, methods: typing.Mapping[str, typing.Callable]
|
||
|
) -> typing.Callable:
|
||
|
"""Checks whether the given file format exists in 'methods'."""
|
||
|
|
||
|
try:
|
||
|
return methods[file_format]
|
||
|
except KeyError as ex:
|
||
|
formats = ", ".join(sorted(methods.keys()))
|
||
|
raise ValueError(
|
||
|
"Unsupported format: %r, try one of %s" % (file_format, formats)
|
||
|
) from ex
|
||
|
|
||
|
def save_pkcs1(self, format: str = "PEM") -> bytes:
|
||
|
"""Saves the key in PKCS#1 DER or PEM format.
|
||
|
|
||
|
:param format: the format to save; 'PEM' or 'DER'
|
||
|
:type format: str
|
||
|
:returns: the DER- or PEM-encoded key.
|
||
|
:rtype: bytes
|
||
|
"""
|
||
|
|
||
|
methods = {
|
||
|
"PEM": self._save_pkcs1_pem,
|
||
|
"DER": self._save_pkcs1_der,
|
||
|
}
|
||
|
|
||
|
method = self._assert_format_exists(format, methods)
|
||
|
return method()
|
||
|
|
||
|
def blind(self, message: int) -> typing.Tuple[int, int]:
|
||
|
"""Performs blinding on the message.
|
||
|
|
||
|
:param message: the message, as integer, to blind.
|
||
|
:param r: the random number to blind with.
|
||
|
:return: tuple (the blinded message, the inverse of the used blinding factor)
|
||
|
|
||
|
The blinding is such that message = unblind(decrypt(blind(encrypt(message))).
|
||
|
|
||
|
See https://en.wikipedia.org/wiki/Blinding_%28cryptography%29
|
||
|
"""
|
||
|
blindfac, blindfac_inverse = self._update_blinding_factor()
|
||
|
blinded = (message * pow(blindfac, self.e, self.n)) % self.n
|
||
|
return blinded, blindfac_inverse
|
||
|
|
||
|
def unblind(self, blinded: int, blindfac_inverse: int) -> int:
|
||
|
"""Performs blinding on the message using random number 'blindfac_inverse'.
|
||
|
|
||
|
:param blinded: the blinded message, as integer, to unblind.
|
||
|
:param blindfac: the factor to unblind with.
|
||
|
:return: the original message.
|
||
|
|
||
|
The blinding is such that message = unblind(decrypt(blind(encrypt(message))).
|
||
|
|
||
|
See https://en.wikipedia.org/wiki/Blinding_%28cryptography%29
|
||
|
"""
|
||
|
return (blindfac_inverse * blinded) % self.n
|
||
|
|
||
|
def _initial_blinding_factor(self) -> int:
|
||
|
for _ in range(1000):
|
||
|
blind_r = rsa.randnum.randint(self.n - 1)
|
||
|
if rsa.prime.are_relatively_prime(self.n, blind_r):
|
||
|
return blind_r
|
||
|
raise RuntimeError("unable to find blinding factor")
|
||
|
|
||
|
def _update_blinding_factor(self) -> typing.Tuple[int, int]:
|
||
|
"""Update blinding factors.
|
||
|
|
||
|
Computing a blinding factor is expensive, so instead this function
|
||
|
does this once, then updates the blinding factor as per section 9
|
||
|
of 'A Timing Attack against RSA with the Chinese Remainder Theorem'
|
||
|
by Werner Schindler.
|
||
|
See https://tls.mbed.org/public/WSchindler-RSA_Timing_Attack.pdf
|
||
|
|
||
|
:return: the new blinding factor and its inverse.
|
||
|
"""
|
||
|
|
||
|
with self.mutex:
|
||
|
if self.blindfac < 0:
|
||
|
# Compute initial blinding factor, which is rather slow to do.
|
||
|
self.blindfac = self._initial_blinding_factor()
|
||
|
self.blindfac_inverse = rsa.common.inverse(self.blindfac, self.n)
|
||
|
else:
|
||
|
# Reuse previous blinding factor.
|
||
|
self.blindfac = pow(self.blindfac, 2, self.n)
|
||
|
self.blindfac_inverse = pow(self.blindfac_inverse, 2, self.n)
|
||
|
|
||
|
return self.blindfac, self.blindfac_inverse
|
||
|
|
||
|
|
||
|
class PublicKey(AbstractKey):
|
||
|
"""Represents a public RSA key.
|
||
|
|
||
|
This key is also known as the 'encryption key'. It contains the 'n' and 'e'
|
||
|
values.
|
||
|
|
||
|
Supports attributes as well as dictionary-like access. Attribute access is
|
||
|
faster, though.
|
||
|
|
||
|
>>> PublicKey(5, 3)
|
||
|
PublicKey(5, 3)
|
||
|
|
||
|
>>> key = PublicKey(5, 3)
|
||
|
>>> key.n
|
||
|
5
|
||
|
>>> key['n']
|
||
|
5
|
||
|
>>> key.e
|
||
|
3
|
||
|
>>> key['e']
|
||
|
3
|
||
|
|
||
|
"""
|
||
|
|
||
|
__slots__ = ()
|
||
|
|
||
|
def __getitem__(self, key: str) -> int:
|
||
|
return getattr(self, key)
|
||
|
|
||
|
def __repr__(self) -> str:
|
||
|
return "PublicKey(%i, %i)" % (self.n, self.e)
|
||
|
|
||
|
def __getstate__(self) -> typing.Tuple[int, int]:
|
||
|
"""Returns the key as tuple for pickling."""
|
||
|
return self.n, self.e
|
||
|
|
||
|
def __setstate__(self, state: typing.Tuple[int, int]) -> None:
|
||
|
"""Sets the key from tuple."""
|
||
|
self.n, self.e = state
|
||
|
AbstractKey.__init__(self, self.n, self.e)
|
||
|
|
||
|
def __eq__(self, other: typing.Any) -> bool:
|
||
|
if other is None:
|
||
|
return False
|
||
|
|
||
|
if not isinstance(other, PublicKey):
|
||
|
return False
|
||
|
|
||
|
return self.n == other.n and self.e == other.e
|
||
|
|
||
|
def __ne__(self, other: typing.Any) -> bool:
|
||
|
return not (self == other)
|
||
|
|
||
|
def __hash__(self) -> int:
|
||
|
return hash((self.n, self.e))
|
||
|
|
||
|
@classmethod
|
||
|
def _load_pkcs1_der(cls, keyfile: bytes) -> "PublicKey":
|
||
|
"""Loads a key in PKCS#1 DER format.
|
||
|
|
||
|
:param keyfile: contents of a DER-encoded file that contains the public
|
||
|
key.
|
||
|
:return: a PublicKey object
|
||
|
|
||
|
First let's construct a DER encoded key:
|
||
|
|
||
|
>>> import base64
|
||
|
>>> b64der = 'MAwCBQCNGmYtAgMBAAE='
|
||
|
>>> der = base64.standard_b64decode(b64der)
|
||
|
|
||
|
This loads the file:
|
||
|
|
||
|
>>> PublicKey._load_pkcs1_der(der)
|
||
|
PublicKey(2367317549, 65537)
|
||
|
|
||
|
"""
|
||
|
|
||
|
from pyasn1.codec.der import decoder
|
||
|
from rsa.asn1 import AsnPubKey
|
||
|
|
||
|
(priv, _) = decoder.decode(keyfile, asn1Spec=AsnPubKey())
|
||
|
return cls(n=int(priv["modulus"]), e=int(priv["publicExponent"]))
|
||
|
|
||
|
def _save_pkcs1_der(self) -> bytes:
|
||
|
"""Saves the public key in PKCS#1 DER format.
|
||
|
|
||
|
:returns: the DER-encoded public key.
|
||
|
:rtype: bytes
|
||
|
"""
|
||
|
|
||
|
from pyasn1.codec.der import encoder
|
||
|
from rsa.asn1 import AsnPubKey
|
||
|
|
||
|
# Create the ASN object
|
||
|
asn_key = AsnPubKey()
|
||
|
asn_key.setComponentByName("modulus", self.n)
|
||
|
asn_key.setComponentByName("publicExponent", self.e)
|
||
|
|
||
|
return encoder.encode(asn_key)
|
||
|
|
||
|
@classmethod
|
||
|
def _load_pkcs1_pem(cls, keyfile: bytes) -> "PublicKey":
|
||
|
"""Loads a PKCS#1 PEM-encoded public key file.
|
||
|
|
||
|
The contents of the file before the "-----BEGIN RSA PUBLIC KEY-----" and
|
||
|
after the "-----END RSA PUBLIC KEY-----" lines is ignored.
|
||
|
|
||
|
:param keyfile: contents of a PEM-encoded file that contains the public
|
||
|
key.
|
||
|
:return: a PublicKey object
|
||
|
"""
|
||
|
|
||
|
der = rsa.pem.load_pem(keyfile, "RSA PUBLIC KEY")
|
||
|
return cls._load_pkcs1_der(der)
|
||
|
|
||
|
def _save_pkcs1_pem(self) -> bytes:
|
||
|
"""Saves a PKCS#1 PEM-encoded public key file.
|
||
|
|
||
|
:return: contents of a PEM-encoded file that contains the public key.
|
||
|
:rtype: bytes
|
||
|
"""
|
||
|
|
||
|
der = self._save_pkcs1_der()
|
||
|
return rsa.pem.save_pem(der, "RSA PUBLIC KEY")
|
||
|
|
||
|
@classmethod
|
||
|
def load_pkcs1_openssl_pem(cls, keyfile: bytes) -> "PublicKey":
|
||
|
"""Loads a PKCS#1.5 PEM-encoded public key file from OpenSSL.
|
||
|
|
||
|
These files can be recognised in that they start with BEGIN PUBLIC KEY
|
||
|
rather than BEGIN RSA PUBLIC KEY.
|
||
|
|
||
|
The contents of the file before the "-----BEGIN PUBLIC KEY-----" and
|
||
|
after the "-----END PUBLIC KEY-----" lines is ignored.
|
||
|
|
||
|
:param keyfile: contents of a PEM-encoded file that contains the public
|
||
|
key, from OpenSSL.
|
||
|
:type keyfile: bytes
|
||
|
:return: a PublicKey object
|
||
|
"""
|
||
|
|
||
|
der = rsa.pem.load_pem(keyfile, "PUBLIC KEY")
|
||
|
return cls.load_pkcs1_openssl_der(der)
|
||
|
|
||
|
@classmethod
|
||
|
def load_pkcs1_openssl_der(cls, keyfile: bytes) -> "PublicKey":
|
||
|
"""Loads a PKCS#1 DER-encoded public key file from OpenSSL.
|
||
|
|
||
|
:param keyfile: contents of a DER-encoded file that contains the public
|
||
|
key, from OpenSSL.
|
||
|
:return: a PublicKey object
|
||
|
"""
|
||
|
|
||
|
from rsa.asn1 import OpenSSLPubKey
|
||
|
from pyasn1.codec.der import decoder
|
||
|
from pyasn1.type import univ
|
||
|
|
||
|
(keyinfo, _) = decoder.decode(keyfile, asn1Spec=OpenSSLPubKey())
|
||
|
|
||
|
if keyinfo["header"]["oid"] != univ.ObjectIdentifier("1.2.840.113549.1.1.1"):
|
||
|
raise TypeError("This is not a DER-encoded OpenSSL-compatible public key")
|
||
|
|
||
|
return cls._load_pkcs1_der(keyinfo["key"][1:])
|
||
|
|
||
|
|
||
|
class PrivateKey(AbstractKey):
|
||
|
"""Represents a private RSA key.
|
||
|
|
||
|
This key is also known as the 'decryption key'. It contains the 'n', 'e',
|
||
|
'd', 'p', 'q' and other values.
|
||
|
|
||
|
Supports attributes as well as dictionary-like access. Attribute access is
|
||
|
faster, though.
|
||
|
|
||
|
>>> PrivateKey(3247, 65537, 833, 191, 17)
|
||
|
PrivateKey(3247, 65537, 833, 191, 17)
|
||
|
|
||
|
exp1, exp2 and coef will be calculated:
|
||
|
|
||
|
>>> pk = PrivateKey(3727264081, 65537, 3349121513, 65063, 57287)
|
||
|
>>> pk.exp1
|
||
|
55063
|
||
|
>>> pk.exp2
|
||
|
10095
|
||
|
>>> pk.coef
|
||
|
50797
|
||
|
|
||
|
"""
|
||
|
|
||
|
__slots__ = ("d", "p", "q", "exp1", "exp2", "coef")
|
||
|
|
||
|
def __init__(self, n: int, e: int, d: int, p: int, q: int) -> None:
|
||
|
AbstractKey.__init__(self, n, e)
|
||
|
self.d = d
|
||
|
self.p = p
|
||
|
self.q = q
|
||
|
|
||
|
# Calculate exponents and coefficient.
|
||
|
self.exp1 = int(d % (p - 1))
|
||
|
self.exp2 = int(d % (q - 1))
|
||
|
self.coef = rsa.common.inverse(q, p)
|
||
|
|
||
|
def __getitem__(self, key: str) -> int:
|
||
|
return getattr(self, key)
|
||
|
|
||
|
def __repr__(self) -> str:
|
||
|
return "PrivateKey(%i, %i, %i, %i, %i)" % (
|
||
|
self.n,
|
||
|
self.e,
|
||
|
self.d,
|
||
|
self.p,
|
||
|
self.q,
|
||
|
)
|
||
|
|
||
|
def __getstate__(self) -> typing.Tuple[int, int, int, int, int, int, int, int]:
|
||
|
"""Returns the key as tuple for pickling."""
|
||
|
return self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef
|
||
|
|
||
|
def __setstate__(self, state: typing.Tuple[int, int, int, int, int, int, int, int]) -> None:
|
||
|
"""Sets the key from tuple."""
|
||
|
self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef = state
|
||
|
AbstractKey.__init__(self, self.n, self.e)
|
||
|
|
||
|
def __eq__(self, other: typing.Any) -> bool:
|
||
|
if other is None:
|
||
|
return False
|
||
|
|
||
|
if not isinstance(other, PrivateKey):
|
||
|
return False
|
||
|
|
||
|
return (
|
||
|
self.n == other.n
|
||
|
and self.e == other.e
|
||
|
and self.d == other.d
|
||
|
and self.p == other.p
|
||
|
and self.q == other.q
|
||
|
and self.exp1 == other.exp1
|
||
|
and self.exp2 == other.exp2
|
||
|
and self.coef == other.coef
|
||
|
)
|
||
|
|
||
|
def __ne__(self, other: typing.Any) -> bool:
|
||
|
return not (self == other)
|
||
|
|
||
|
def __hash__(self) -> int:
|
||
|
return hash((self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef))
|
||
|
|
||
|
def blinded_decrypt(self, encrypted: int) -> int:
|
||
|
"""Decrypts the message using blinding to prevent side-channel attacks.
|
||
|
|
||
|
:param encrypted: the encrypted message
|
||
|
:type encrypted: int
|
||
|
|
||
|
:returns: the decrypted message
|
||
|
:rtype: int
|
||
|
"""
|
||
|
|
||
|
# Blinding and un-blinding should be using the same factor
|
||
|
blinded, blindfac_inverse = self.blind(encrypted)
|
||
|
|
||
|
# Instead of using the core functionality, use the Chinese Remainder
|
||
|
# Theorem and be 2-4x faster. This the same as:
|
||
|
#
|
||
|
# decrypted = rsa.core.decrypt_int(blinded, self.d, self.n)
|
||
|
s1 = pow(blinded, self.exp1, self.p)
|
||
|
s2 = pow(blinded, self.exp2, self.q)
|
||
|
h = ((s1 - s2) * self.coef) % self.p
|
||
|
decrypted = s2 + self.q * h
|
||
|
|
||
|
return self.unblind(decrypted, blindfac_inverse)
|
||
|
|
||
|
def blinded_encrypt(self, message: int) -> int:
|
||
|
"""Encrypts the message using blinding to prevent side-channel attacks.
|
||
|
|
||
|
:param message: the message to encrypt
|
||
|
:type message: int
|
||
|
|
||
|
:returns: the encrypted message
|
||
|
:rtype: int
|
||
|
"""
|
||
|
|
||
|
blinded, blindfac_inverse = self.blind(message)
|
||
|
encrypted = rsa.core.encrypt_int(blinded, self.d, self.n)
|
||
|
return self.unblind(encrypted, blindfac_inverse)
|
||
|
|
||
|
@classmethod
|
||
|
def _load_pkcs1_der(cls, keyfile: bytes) -> "PrivateKey":
|
||
|
"""Loads a key in PKCS#1 DER format.
|
||
|
|
||
|
:param keyfile: contents of a DER-encoded file that contains the private
|
||
|
key.
|
||
|
:type keyfile: bytes
|
||
|
:return: a PrivateKey object
|
||
|
|
||
|
First let's construct a DER encoded key:
|
||
|
|
||
|
>>> import base64
|
||
|
>>> b64der = 'MC4CAQACBQDeKYlRAgMBAAECBQDHn4npAgMA/icCAwDfxwIDANcXAgInbwIDAMZt'
|
||
|
>>> der = base64.standard_b64decode(b64der)
|
||
|
|
||
|
This loads the file:
|
||
|
|
||
|
>>> PrivateKey._load_pkcs1_der(der)
|
||
|
PrivateKey(3727264081, 65537, 3349121513, 65063, 57287)
|
||
|
|
||
|
"""
|
||
|
|
||
|
from pyasn1.codec.der import decoder
|
||
|
|
||
|
(priv, _) = decoder.decode(keyfile)
|
||
|
|
||
|
# ASN.1 contents of DER encoded private key:
|
||
|
#
|
||
|
# RSAPrivateKey ::= SEQUENCE {
|
||
|
# version Version,
|
||
|
# modulus INTEGER, -- n
|
||
|
# publicExponent INTEGER, -- e
|
||
|
# privateExponent INTEGER, -- d
|
||
|
# prime1 INTEGER, -- p
|
||
|
# prime2 INTEGER, -- q
|
||
|
# exponent1 INTEGER, -- d mod (p-1)
|
||
|
# exponent2 INTEGER, -- d mod (q-1)
|
||
|
# coefficient INTEGER, -- (inverse of q) mod p
|
||
|
# otherPrimeInfos OtherPrimeInfos OPTIONAL
|
||
|
# }
|
||
|
|
||
|
if priv[0] != 0:
|
||
|
raise ValueError("Unable to read this file, version %s != 0" % priv[0])
|
||
|
|
||
|
as_ints = map(int, priv[1:6])
|
||
|
key = cls(*as_ints)
|
||
|
|
||
|
exp1, exp2, coef = map(int, priv[6:9])
|
||
|
|
||
|
if (key.exp1, key.exp2, key.coef) != (exp1, exp2, coef):
|
||
|
warnings.warn(
|
||
|
"You have provided a malformed keyfile. Either the exponents "
|
||
|
"or the coefficient are incorrect. Using the correct values "
|
||
|
"instead.",
|
||
|
UserWarning,
|
||
|
)
|
||
|
|
||
|
return key
|
||
|
|
||
|
def _save_pkcs1_der(self) -> bytes:
|
||
|
"""Saves the private key in PKCS#1 DER format.
|
||
|
|
||
|
:returns: the DER-encoded private key.
|
||
|
:rtype: bytes
|
||
|
"""
|
||
|
|
||
|
from pyasn1.type import univ, namedtype
|
||
|
from pyasn1.codec.der import encoder
|
||
|
|
||
|
class AsnPrivKey(univ.Sequence):
|
||
|
componentType = namedtype.NamedTypes(
|
||
|
namedtype.NamedType("version", univ.Integer()),
|
||
|
namedtype.NamedType("modulus", univ.Integer()),
|
||
|
namedtype.NamedType("publicExponent", univ.Integer()),
|
||
|
namedtype.NamedType("privateExponent", univ.Integer()),
|
||
|
namedtype.NamedType("prime1", univ.Integer()),
|
||
|
namedtype.NamedType("prime2", univ.Integer()),
|
||
|
namedtype.NamedType("exponent1", univ.Integer()),
|
||
|
namedtype.NamedType("exponent2", univ.Integer()),
|
||
|
namedtype.NamedType("coefficient", univ.Integer()),
|
||
|
)
|
||
|
|
||
|
# Create the ASN object
|
||
|
asn_key = AsnPrivKey()
|
||
|
asn_key.setComponentByName("version", 0)
|
||
|
asn_key.setComponentByName("modulus", self.n)
|
||
|
asn_key.setComponentByName("publicExponent", self.e)
|
||
|
asn_key.setComponentByName("privateExponent", self.d)
|
||
|
asn_key.setComponentByName("prime1", self.p)
|
||
|
asn_key.setComponentByName("prime2", self.q)
|
||
|
asn_key.setComponentByName("exponent1", self.exp1)
|
||
|
asn_key.setComponentByName("exponent2", self.exp2)
|
||
|
asn_key.setComponentByName("coefficient", self.coef)
|
||
|
|
||
|
return encoder.encode(asn_key)
|
||
|
|
||
|
@classmethod
|
||
|
def _load_pkcs1_pem(cls, keyfile: bytes) -> "PrivateKey":
|
||
|
"""Loads a PKCS#1 PEM-encoded private key file.
|
||
|
|
||
|
The contents of the file before the "-----BEGIN RSA PRIVATE KEY-----" and
|
||
|
after the "-----END RSA PRIVATE KEY-----" lines is ignored.
|
||
|
|
||
|
:param keyfile: contents of a PEM-encoded file that contains the private
|
||
|
key.
|
||
|
:type keyfile: bytes
|
||
|
:return: a PrivateKey object
|
||
|
"""
|
||
|
|
||
|
der = rsa.pem.load_pem(keyfile, b"RSA PRIVATE KEY")
|
||
|
return cls._load_pkcs1_der(der)
|
||
|
|
||
|
def _save_pkcs1_pem(self) -> bytes:
|
||
|
"""Saves a PKCS#1 PEM-encoded private key file.
|
||
|
|
||
|
:return: contents of a PEM-encoded file that contains the private key.
|
||
|
:rtype: bytes
|
||
|
"""
|
||
|
|
||
|
der = self._save_pkcs1_der()
|
||
|
return rsa.pem.save_pem(der, b"RSA PRIVATE KEY")
|
||
|
|
||
|
|
||
|
def find_p_q(
|
||
|
nbits: int,
|
||
|
getprime_func: typing.Callable[[int], int] = rsa.prime.getprime,
|
||
|
accurate: bool = True,
|
||
|
) -> typing.Tuple[int, int]:
|
||
|
"""Returns a tuple of two different primes of nbits bits each.
|
||
|
|
||
|
The resulting p * q has exactly 2 * nbits bits, and the returned p and q
|
||
|
will not be equal.
|
||
|
|
||
|
:param nbits: the number of bits in each of p and q.
|
||
|
:param getprime_func: the getprime function, defaults to
|
||
|
:py:func:`rsa.prime.getprime`.
|
||
|
|
||
|
*Introduced in Python-RSA 3.1*
|
||
|
|
||
|
:param accurate: whether to enable accurate mode or not.
|
||
|
:returns: (p, q), where p > q
|
||
|
|
||
|
>>> (p, q) = find_p_q(128)
|
||
|
>>> from rsa import common
|
||
|
>>> common.bit_size(p * q)
|
||
|
256
|
||
|
|
||
|
When not in accurate mode, the number of bits can be slightly less
|
||
|
|
||
|
>>> (p, q) = find_p_q(128, accurate=False)
|
||
|
>>> from rsa import common
|
||
|
>>> common.bit_size(p * q) <= 256
|
||
|
True
|
||
|
>>> common.bit_size(p * q) > 240
|
||
|
True
|
||
|
|
||
|
"""
|
||
|
|
||
|
total_bits = nbits * 2
|
||
|
|
||
|
# Make sure that p and q aren't too close or the factoring programs can
|
||
|
# factor n.
|
||
|
shift = nbits // 16
|
||
|
pbits = nbits + shift
|
||
|
qbits = nbits - shift
|
||
|
|
||
|
# Choose the two initial primes
|
||
|
p = getprime_func(pbits)
|
||
|
q = getprime_func(qbits)
|
||
|
|
||
|
def is_acceptable(p: int, q: int) -> bool:
|
||
|
"""Returns True iff p and q are acceptable:
|
||
|
|
||
|
- p and q differ
|
||
|
- (p * q) has the right nr of bits (when accurate=True)
|
||
|
"""
|
||
|
|
||
|
if p == q:
|
||
|
return False
|
||
|
|
||
|
if not accurate:
|
||
|
return True
|
||
|
|
||
|
# Make sure we have just the right amount of bits
|
||
|
found_size = rsa.common.bit_size(p * q)
|
||
|
return total_bits == found_size
|
||
|
|
||
|
# Keep choosing other primes until they match our requirements.
|
||
|
change_p = False
|
||
|
while not is_acceptable(p, q):
|
||
|
# Change p on one iteration and q on the other
|
||
|
if change_p:
|
||
|
p = getprime_func(pbits)
|
||
|
else:
|
||
|
q = getprime_func(qbits)
|
||
|
|
||
|
change_p = not change_p
|
||
|
|
||
|
# We want p > q as described on
|
||
|
# http://www.di-mgt.com.au/rsa_alg.html#crt
|
||
|
return max(p, q), min(p, q)
|
||
|
|
||
|
|
||
|
def calculate_keys_custom_exponent(p: int, q: int, exponent: int) -> typing.Tuple[int, int]:
|
||
|
"""Calculates an encryption and a decryption key given p, q and an exponent,
|
||
|
and returns them as a tuple (e, d)
|
||
|
|
||
|
:param p: the first large prime
|
||
|
:param q: the second large prime
|
||
|
:param exponent: the exponent for the key; only change this if you know
|
||
|
what you're doing, as the exponent influences how difficult your
|
||
|
private key can be cracked. A very common choice for e is 65537.
|
||
|
:type exponent: int
|
||
|
|
||
|
"""
|
||
|
|
||
|
phi_n = (p - 1) * (q - 1)
|
||
|
|
||
|
try:
|
||
|
d = rsa.common.inverse(exponent, phi_n)
|
||
|
except rsa.common.NotRelativePrimeError as ex:
|
||
|
raise rsa.common.NotRelativePrimeError(
|
||
|
exponent,
|
||
|
phi_n,
|
||
|
ex.d,
|
||
|
msg="e (%d) and phi_n (%d) are not relatively prime (divider=%i)"
|
||
|
% (exponent, phi_n, ex.d),
|
||
|
) from ex
|
||
|
|
||
|
if (exponent * d) % phi_n != 1:
|
||
|
raise ValueError(
|
||
|
"e (%d) and d (%d) are not mult. inv. modulo " "phi_n (%d)" % (exponent, d, phi_n)
|
||
|
)
|
||
|
|
||
|
return exponent, d
|
||
|
|
||
|
|
||
|
def calculate_keys(p: int, q: int) -> typing.Tuple[int, int]:
|
||
|
"""Calculates an encryption and a decryption key given p and q, and
|
||
|
returns them as a tuple (e, d)
|
||
|
|
||
|
:param p: the first large prime
|
||
|
:param q: the second large prime
|
||
|
|
||
|
:return: tuple (e, d) with the encryption and decryption exponents.
|
||
|
"""
|
||
|
|
||
|
return calculate_keys_custom_exponent(p, q, DEFAULT_EXPONENT)
|
||
|
|
||
|
|
||
|
def gen_keys(
|
||
|
nbits: int,
|
||
|
getprime_func: typing.Callable[[int], int],
|
||
|
accurate: bool = True,
|
||
|
exponent: int = DEFAULT_EXPONENT,
|
||
|
) -> typing.Tuple[int, int, int, int]:
|
||
|
"""Generate RSA keys of nbits bits. Returns (p, q, e, d).
|
||
|
|
||
|
Note: this can take a long time, depending on the key size.
|
||
|
|
||
|
:param nbits: the total number of bits in ``p`` and ``q``. Both ``p`` and
|
||
|
``q`` will use ``nbits/2`` bits.
|
||
|
:param getprime_func: either :py:func:`rsa.prime.getprime` or a function
|
||
|
with similar signature.
|
||
|
:param exponent: the exponent for the key; only change this if you know
|
||
|
what you're doing, as the exponent influences how difficult your
|
||
|
private key can be cracked. A very common choice for e is 65537.
|
||
|
:type exponent: int
|
||
|
"""
|
||
|
|
||
|
# Regenerate p and q values, until calculate_keys doesn't raise a
|
||
|
# ValueError.
|
||
|
while True:
|
||
|
(p, q) = find_p_q(nbits // 2, getprime_func, accurate)
|
||
|
try:
|
||
|
(e, d) = calculate_keys_custom_exponent(p, q, exponent=exponent)
|
||
|
break
|
||
|
except ValueError:
|
||
|
pass
|
||
|
|
||
|
return p, q, e, d
|
||
|
|
||
|
|
||
|
def newkeys(
|
||
|
nbits: int,
|
||
|
accurate: bool = True,
|
||
|
poolsize: int = 1,
|
||
|
exponent: int = DEFAULT_EXPONENT,
|
||
|
) -> typing.Tuple[PublicKey, PrivateKey]:
|
||
|
"""Generates public and private keys, and returns them as (pub, priv).
|
||
|
|
||
|
The public key is also known as the 'encryption key', and is a
|
||
|
:py:class:`rsa.PublicKey` object. The private key is also known as the
|
||
|
'decryption key' and is a :py:class:`rsa.PrivateKey` object.
|
||
|
|
||
|
:param nbits: the number of bits required to store ``n = p*q``.
|
||
|
:param accurate: when True, ``n`` will have exactly the number of bits you
|
||
|
asked for. However, this makes key generation much slower. When False,
|
||
|
`n`` may have slightly less bits.
|
||
|
:param poolsize: the number of processes to use to generate the prime
|
||
|
numbers. If set to a number > 1, a parallel algorithm will be used.
|
||
|
This requires Python 2.6 or newer.
|
||
|
:param exponent: the exponent for the key; only change this if you know
|
||
|
what you're doing, as the exponent influences how difficult your
|
||
|
private key can be cracked. A very common choice for e is 65537.
|
||
|
:type exponent: int
|
||
|
|
||
|
:returns: a tuple (:py:class:`rsa.PublicKey`, :py:class:`rsa.PrivateKey`)
|
||
|
|
||
|
The ``poolsize`` parameter was added in *Python-RSA 3.1* and requires
|
||
|
Python 2.6 or newer.
|
||
|
|
||
|
"""
|
||
|
|
||
|
if nbits < 16:
|
||
|
raise ValueError("Key too small")
|
||
|
|
||
|
if poolsize < 1:
|
||
|
raise ValueError("Pool size (%i) should be >= 1" % poolsize)
|
||
|
|
||
|
# Determine which getprime function to use
|
||
|
if poolsize > 1:
|
||
|
from rsa import parallel
|
||
|
|
||
|
def getprime_func(nbits: int) -> int:
|
||
|
return parallel.getprime(nbits, poolsize=poolsize)
|
||
|
|
||
|
else:
|
||
|
getprime_func = rsa.prime.getprime
|
||
|
|
||
|
# Generate the key components
|
||
|
(p, q, e, d) = gen_keys(nbits, getprime_func, accurate=accurate, exponent=exponent)
|
||
|
|
||
|
# Create the key objects
|
||
|
n = p * q
|
||
|
|
||
|
return (PublicKey(n, e), PrivateKey(n, e, d, p, q))
|
||
|
|
||
|
|
||
|
__all__ = ["PublicKey", "PrivateKey", "newkeys"]
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
import doctest
|
||
|
|
||
|
try:
|
||
|
for count in range(100):
|
||
|
(failures, tests) = doctest.testmod()
|
||
|
if failures:
|
||
|
break
|
||
|
|
||
|
if (count % 10 == 0 and count) or count == 1:
|
||
|
print("%i times" % count)
|
||
|
except KeyboardInterrupt:
|
||
|
print("Aborted")
|
||
|
else:
|
||
|
print("Doctests done")
|