Update rsa to 3.4.2

This commit is contained in:
shortcutme 2017-04-06 19:21:32 +02:00
parent fa65a6dc05
commit fbe96a0a6d
No known key found for this signature in database
GPG key ID: 5B63BAE6CB9613AE
20 changed files with 871 additions and 786 deletions

View file

@ -1,55 +0,0 @@
Python-RSA changelog
========================================
Version 3.1.1 - in development
----------------------------------------
- Fixed doctests for Python 2.7
- Removed obsolete unittest so all tests run fine on Python 3.2
Version 3.1 - released 2012-06-17
----------------------------------------
- Big, big credits to Yesudeep Mangalapilly for all the changes listed
below!
- Added ability to generate keys on multiple cores simultaneously.
- Massive speedup
- Partial Python 3.2 compatibility (core functionality works, but
saving or loading keys doesn't, for that the pyasn1 package needs to
be ported to Python 3 first)
- Lots of bug fixes
Version 3.0.1 - released 2011-08-07
----------------------------------------
- Removed unused import of abc module
Version 3.0 - released 2011-08-05
----------------------------------------
- Changed the meaning of the keysize to mean the size of ``n`` rather than
the size of both ``p`` and ``q``. This is the common interpretation of
RSA keysize. To get the old behaviour, double the keysize when generating a
new key.
- Added a lot of doctests
- Added random-padded encryption and decryption using PKCS#1 version 1.5
- Added hash-based signatures and verification using PKCS#1v1.5
- Modeling private and public key as real objects rather than dicts.
- Support for saving and loading keys as PEM and DER files.
- Ability to extract a public key from a private key (PEM+DER)
Version 2.0
----------------------------------------
- Security improvements by Barry Mead.

View file

@ -1,31 +0,0 @@
Pure Python RSA implementation
==============================
`Python-RSA`_ is a pure-Python RSA implementation. It supports
encryption and decryption, signing and verifying signatures, and key
generation according to PKCS#1 version 1.5. It can be used as a Python
library as well as on the commandline. The code was mostly written by
Sybren A. Stüvel.
Documentation can be found at the Python-RSA homepage:
http://stuvel.eu/rsa
Download and install using::
pip install rsa
or::
easy_install rsa
or download it from the `Python Package Index`_.
The source code is maintained in a `Mercurial repository`_ and is
licensed under the `Apache License, version 2.0`_
.. _`Python-RSA`: http://stuvel.eu/rsa
.. _`Mercurial repository`: https://bitbucket.org/sybren/python-rsa
.. _`Python Package Index`: http://pypi.python.org/pypi/rsa
.. _`Apache License, version 2.0`: http://www.apache.org/licenses/LICENSE-2.0

View file

@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
@ -22,24 +22,21 @@ WARNING: this implementation does not use random padding, compression of the
cleartext input to prevent repetitions, or other common security improvements. cleartext input to prevent repetitions, or other common security improvements.
Use with care. Use with care.
If you want to have a more secure implementation, use the functions from the
``rsa.pkcs1`` module.
""" """
__author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly"
__date__ = "2015-11-05"
__version__ = '3.2.3'
from rsa.key import newkeys, PrivateKey, PublicKey from rsa.key import newkeys, PrivateKey, PublicKey
from rsa.pkcs1 import encrypt, decrypt, sign, verify, DecryptionError, \ from rsa.pkcs1 import encrypt, decrypt, sign, verify, DecryptionError, \
VerificationError VerificationError
__author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly"
__date__ = "2016-03-29"
__version__ = '3.4.2'
# Do doctest if we're run directly # Do doctest if we're run directly
if __name__ == "__main__": if __name__ == "__main__":
import doctest import doctest
doctest.testmod() doctest.testmod()
__all__ = ["newkeys", "encrypt", "decrypt", "sign", "verify", 'PublicKey', __all__ = ["newkeys", "encrypt", "decrypt", "sign", "verify", 'PublicKey',
'PrivateKey', 'DecryptionError', 'VerificationError'] 'PrivateKey', 'DecryptionError', 'VerificationError']

View file

@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
@ -16,7 +16,6 @@
"""Python compatibility wrappers.""" """Python compatibility wrappers."""
from __future__ import absolute_import from __future__ import absolute_import
import sys import sys
@ -42,15 +41,12 @@ else:
# Else we just assume 64-bit processor keeping up with modern times. # Else we just assume 64-bit processor keeping up with modern times.
MACHINE_WORD_SIZE = 64 MACHINE_WORD_SIZE = 64
try: try:
# < Python3 # < Python3
unicode_type = unicode unicode_type = unicode
have_python3 = False
except NameError: except NameError:
# Python3. # Python3.
unicode_type = str unicode_type = str
have_python3 = True
# Fake byte literals. # Fake byte literals.
if str is unicode_type: if str is unicode_type:
@ -68,14 +64,6 @@ except NameError:
b = byte_literal b = byte_literal
try:
# Python 2.6 or higher.
bytes_type = bytes
except NameError:
# Python 2.5
bytes_type = str
# To avoid calling b() multiple times in tight loops. # To avoid calling b() multiple times in tight loops.
ZERO_BYTE = b('\x00') ZERO_BYTE = b('\x00')
EMPTY_BYTE = b('') EMPTY_BYTE = b('')
@ -90,7 +78,7 @@ def is_bytes(obj):
:returns: :returns:
``True`` if ``value`` is a byte string; ``False`` otherwise. ``True`` if ``value`` is a byte string; ``False`` otherwise.
""" """
return isinstance(obj, bytes_type) return isinstance(obj, bytes)
def is_integer(obj): def is_integer(obj):

View file

@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
@ -14,8 +14,11 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""RSA module """Deprecated version of the RSA module
pri = k[1] //Private part of keys d,p,q
.. deprecated:: 2.0
This submodule is deprecated and will be completely removed as of version 4.0.
Module for calculating large primes, and RSA encryption, decryption, Module for calculating large primes, and RSA encryption, decryption,
signing and verification. Includes generating public and private keys. signing and verification. Includes generating public and private keys.
@ -34,7 +37,11 @@ __version__ = '1.3.3'
# NOTE: Python's modulo can return negative numbers. We compensate for # NOTE: Python's modulo can return negative numbers. We compensate for
# this behaviour using the abs() function # this behaviour using the abs() function
from cPickle import dumps, loads try:
import cPickle as pickle
except ImportError:
import pickle
from pickle import dumps, loads
import base64 import base64
import math import math
import os import os
@ -49,6 +56,9 @@ from rsa._compat import byte
import warnings import warnings
warnings.warn('Insecure version of the RSA module is imported as %s, be careful' warnings.warn('Insecure version of the RSA module is imported as %s, be careful'
% __name__) % __name__)
warnings.warn('This submodule is deprecated and will be completely removed as of version 4.0.',
DeprecationWarning)
def gcd(p, q): def gcd(p, q):
"""Returns the greatest common divisor of p and q """Returns the greatest common divisor of p and q
@ -63,12 +73,6 @@ def gcd(p, q):
def bytes2int(bytes): def bytes2int(bytes):
"""Converts a list of bytes or a string to an integer """Converts a list of bytes or a string to an integer
>>> (128*256 + 64)*256 + + 15
8405007
>>> l = [128, 64, 15]
>>> bytes2int(l)
8405007
""" """
if not (type(bytes) is types.ListType or type(bytes) is types.StringType): if not (type(bytes) is types.ListType or type(bytes) is types.StringType):
@ -85,9 +89,6 @@ def bytes2int(bytes):
def int2bytes(number): def int2bytes(number):
"""Converts a number to a string of bytes """Converts a number to a string of bytes
>>> bytes2int(int2bytes(123456789))
123456789
""" """
if not (type(number) is types.LongType or type(number) is types.IntType): if not (type(number) is types.LongType or type(number) is types.IntType):
@ -204,11 +205,6 @@ def randomized_primality_testing(n, k):
def is_prime(number): def is_prime(number):
"""Returns True if the number is prime, and False otherwise. """Returns True if the number is prime, and False otherwise.
>>> is_prime(42)
0
>>> is_prime(41)
1
""" """
""" """
@ -228,14 +224,6 @@ def is_prime(number):
def getprime(nbits): def getprime(nbits):
"""Returns a prime number of max. 'math.ceil(nbits/8)*8' bits. In """Returns a prime number of max. 'math.ceil(nbits/8)*8' bits. In
other words: nbits is rounded up to whole bytes. other words: nbits is rounded up to whole bytes.
>>> p = getprime(8)
>>> is_prime(p-1)
0
>>> is_prime(p)
1
>>> is_prime(p+1)
0
""" """
nbytes = int(math.ceil(nbits/8.)) nbytes = int(math.ceil(nbits/8.))
@ -256,11 +244,6 @@ def getprime(nbits):
def are_relatively_prime(a, b): def are_relatively_prime(a, b):
"""Returns True if a and b are relatively prime, and False if they """Returns True if a and b are relatively prime, and False if they
are not. are not.
>>> are_relatively_prime(2, 3)
1
>>> are_relatively_prime(2, 4)
0
""" """
d = gcd(a, b) d = gcd(a, b)

View file

@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
@ -14,14 +14,11 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
"""RSA module """Deprecated version of the RSA module
Module for calculating large primes, and RSA encryption, decryption, .. deprecated:: 3.0
signing and verification. Includes generating public and private keys.
WARNING: this implementation does not use random padding, compression of the This submodule is deprecated and will be completely removed as of version 4.0.
cleartext input to prevent repetitions, or other common security improvements.
Use with care.
""" """
@ -39,6 +36,8 @@ from rsa._compat import byte
# Display a warning that this insecure version is imported. # Display a warning that this insecure version is imported.
import warnings import warnings
warnings.warn('Insecure version of the RSA module is imported as %s' % __name__) warnings.warn('Insecure version of the RSA module is imported as %s' % __name__)
warnings.warn('This submodule is deprecated and will be completely removed as of version 4.0.',
DeprecationWarning)
def bit_size(number): def bit_size(number):
@ -59,13 +58,7 @@ def gcd(p, q):
def bytes2int(bytes): def bytes2int(bytes):
"""Converts a list of bytes or a string to an integer r"""Converts a list of bytes or a string to an integer
>>> (((128 * 256) + 64) * 256) + 15
8405007
>>> l = [128, 64, 15]
>>> bytes2int(l) #same as bytes2int('\x80@\x0f')
8405007
""" """
if not (type(bytes) is types.ListType or type(bytes) is types.StringType): if not (type(bytes) is types.ListType or type(bytes) is types.StringType):
@ -99,9 +92,6 @@ def int2bytes(number):
def to64(number): def to64(number):
"""Converts a number in the range of 0 to 63 into base 64 digit """Converts a number in the range of 0 to 63 into base 64 digit
character in the range of '0'-'9', 'A'-'Z', 'a'-'z','-','_'. character in the range of '0'-'9', 'A'-'Z', 'a'-'z','-','_'.
>>> to64(10)
'A'
""" """
if not (type(number) is types.LongType or type(number) is types.IntType): if not (type(number) is types.LongType or type(number) is types.IntType):
@ -128,9 +118,6 @@ def to64(number):
def from64(number): def from64(number):
"""Converts an ordinal character value in the range of """Converts an ordinal character value in the range of
0-9,A-Z,a-z,-,_ to a number in the range of 0-63. 0-9,A-Z,a-z,-,_ to a number in the range of 0-63.
>>> from64(49)
1
""" """
if not (type(number) is types.LongType or type(number) is types.IntType): if not (type(number) is types.LongType or type(number) is types.IntType):
@ -157,9 +144,6 @@ def from64(number):
def int2str64(number): def int2str64(number):
"""Converts a number to a string of base64 encoded characters in """Converts a number to a string of base64 encoded characters in
the range of '0'-'9','A'-'Z,'a'-'z','-','_'. the range of '0'-'9','A'-'Z,'a'-'z','-','_'.
>>> int2str64(123456789)
'7MyqL'
""" """
if not (type(number) is types.LongType or type(number) is types.IntType): if not (type(number) is types.LongType or type(number) is types.IntType):
@ -177,9 +161,6 @@ def int2str64(number):
def str642int(string): def str642int(string):
"""Converts a base64 encoded string into an integer. """Converts a base64 encoded string into an integer.
The chars of this string in in the range '0'-'9','A'-'Z','a'-'z','-','_' The chars of this string in in the range '0'-'9','A'-'Z','a'-'z','-','_'
>>> str642int('7MyqL')
123456789
""" """
if not (type(string) is types.ListType or type(string) is types.StringType): if not (type(string) is types.ListType or type(string) is types.StringType):
@ -270,11 +251,6 @@ def randomized_primality_testing(n, k):
def is_prime(number): def is_prime(number):
"""Returns True if the number is prime, and False otherwise. """Returns True if the number is prime, and False otherwise.
>>> is_prime(42)
0
>>> is_prime(41)
1
""" """
if randomized_primality_testing(number, 6): if randomized_primality_testing(number, 6):
@ -288,14 +264,6 @@ def is_prime(number):
def getprime(nbits): def getprime(nbits):
"""Returns a prime number of max. 'math.ceil(nbits/8)*8' bits. In """Returns a prime number of max. 'math.ceil(nbits/8)*8' bits. In
other words: nbits is rounded up to whole bytes. other words: nbits is rounded up to whole bytes.
>>> p = getprime(8)
>>> is_prime(p-1)
0
>>> is_prime(p)
1
>>> is_prime(p+1)
0
""" """
while True: while True:

View file

@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
@ -14,19 +14,21 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
'''ASN.1 definitions. """ASN.1 definitions.
Not all ASN.1-handling code use these definitions, but when it does, they should be here. Not all ASN.1-handling code use these definitions, but when it does, they should be here.
''' """
from pyasn1.type import univ, namedtype, tag from pyasn1.type import univ, namedtype, tag
class PubKeyHeader(univ.Sequence): class PubKeyHeader(univ.Sequence):
componentType = namedtype.NamedTypes( componentType = namedtype.NamedTypes(
namedtype.NamedType('oid', univ.ObjectIdentifier()), namedtype.NamedType('oid', univ.ObjectIdentifier()),
namedtype.NamedType('parameters', univ.Null()), namedtype.NamedType('parameters', univ.Null()),
) )
class OpenSSLPubKey(univ.Sequence): class OpenSSLPubKey(univ.Sequence):
componentType = namedtype.NamedTypes( componentType = namedtype.NamedTypes(
namedtype.NamedType('header', PubKeyHeader()), namedtype.NamedType('header', PubKeyHeader()),
@ -38,12 +40,12 @@ class OpenSSLPubKey(univ.Sequence):
class AsnPubKey(univ.Sequence): class AsnPubKey(univ.Sequence):
'''ASN.1 contents of DER encoded public key: """ASN.1 contents of DER encoded public key:
RSAPublicKey ::= SEQUENCE { RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n modulus INTEGER, -- n
publicExponent INTEGER, -- e publicExponent INTEGER, -- e
''' """
componentType = namedtype.NamedTypes( componentType = namedtype.NamedTypes(
namedtype.NamedType('modulus', univ.Integer()), namedtype.NamedType('modulus', univ.Integer()),

View file

@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
@ -14,7 +14,27 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
'''Large file support """Large file support
.. deprecated:: 3.4
The VARBLOCK format is NOT recommended for general use, has been deprecated since
Python-RSA 3.4, and will be removed in a future release. It's vulnerable to a
number of attacks:
1. decrypt/encrypt_bigfile() does not implement `Authenticated encryption`_ nor
uses MACs to verify messages before decrypting public key encrypted messages.
2. decrypt/encrypt_bigfile() does not use hybrid encryption (it uses plain RSA)
and has no method for chaining, so block reordering is possible.
See `issue #19 on Github`_ for more information.
.. _Authenticated encryption: https://en.wikipedia.org/wiki/Authenticated_encryption
.. _issue #19 on Github: https://github.com/sybrenstuvel/python-rsa/issues/13
This module contains functions to:
- break a file into smaller blocks, and encrypt them, and store the - break a file into smaller blocks, and encrypt them, and store the
encrypted blocks in another file. encrypted blocks in another file.
@ -37,19 +57,34 @@ The encrypted file format is as follows, where || denotes byte concatenation:
This file format is called the VARBLOCK format, in line with the varint format This file format is called the VARBLOCK format, in line with the varint format
used to denote the block sizes. used to denote the block sizes.
''' """
import warnings
from rsa import key, common, pkcs1, varblock from rsa import key, common, pkcs1, varblock
from rsa._compat import byte from rsa._compat import byte
def encrypt_bigfile(infile, outfile, pub_key): def encrypt_bigfile(infile, outfile, pub_key):
'''Encrypts a file, writing it to 'outfile' in VARBLOCK format. """Encrypts a file, writing it to 'outfile' in VARBLOCK format.
.. deprecated:: 3.4
This function was deprecated in Python-RSA version 3.4 due to security issues
in the VARBLOCK format. See the documentation_ for more information.
.. _documentation: https://stuvel.eu/python-rsa-doc/usage.html#working-with-big-files
:param infile: file-like object to read the cleartext from :param infile: file-like object to read the cleartext from
:param outfile: file-like object to write the crypto in VARBLOCK format to :param outfile: file-like object to write the crypto in VARBLOCK format to
:param pub_key: :py:class:`rsa.PublicKey` to encrypt with :param pub_key: :py:class:`rsa.PublicKey` to encrypt with
''' """
warnings.warn("The 'rsa.bigfile.encrypt_bigfile' function was deprecated in Python-RSA version "
"3.4 due to security issues in the VARBLOCK format. See "
"https://stuvel.eu/python-rsa-doc/usage.html#working-with-big-files "
"for more information.",
DeprecationWarning, stacklevel=2)
if not isinstance(pub_key, key.PublicKey): if not isinstance(pub_key, key.PublicKey):
raise TypeError('Public key required, but got %r' % pub_key) raise TypeError('Public key required, but got %r' % pub_key)
@ -67,14 +102,27 @@ def encrypt_bigfile(infile, outfile, pub_key):
varblock.write_varint(outfile, len(crypto)) varblock.write_varint(outfile, len(crypto))
outfile.write(crypto) outfile.write(crypto)
def decrypt_bigfile(infile, outfile, priv_key): def decrypt_bigfile(infile, outfile, priv_key):
'''Decrypts an encrypted VARBLOCK file, writing it to 'outfile' """Decrypts an encrypted VARBLOCK file, writing it to 'outfile'
.. deprecated:: 3.4
This function was deprecated in Python-RSA version 3.4 due to security issues
in the VARBLOCK format. See the documentation_ for more information.
.. _documentation: https://stuvel.eu/python-rsa-doc/usage.html#working-with-big-files
:param infile: file-like object to read the crypto in VARBLOCK format from :param infile: file-like object to read the crypto in VARBLOCK format from
:param outfile: file-like object to write the cleartext to :param outfile: file-like object to write the cleartext to
:param priv_key: :py:class:`rsa.PrivateKey` to decrypt with :param priv_key: :py:class:`rsa.PrivateKey` to decrypt with
''' """
warnings.warn("The 'rsa.bigfile.decrypt_bigfile' function was deprecated in Python-RSA version "
"3.4 due to security issues in the VARBLOCK format. See "
"https://stuvel.eu/python-rsa-doc/usage.html#working-with-big-files "
"for more information.",
DeprecationWarning, stacklevel=2)
if not isinstance(priv_key, key.PrivateKey): if not isinstance(priv_key, key.PrivateKey):
raise TypeError('Private key required, but got %r' % priv_key) raise TypeError('Private key required, but got %r' % priv_key)
@ -83,5 +131,5 @@ def decrypt_bigfile(infile, outfile, priv_key):
cleartext = pkcs1.decrypt(block, priv_key) cleartext = pkcs1.decrypt(block, priv_key)
outfile.write(cleartext) outfile.write(cleartext)
__all__ = ['encrypt_bigfile', 'decrypt_bigfile']
__all__ = ['encrypt_bigfile', 'decrypt_bigfile']

View file

@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
@ -14,10 +14,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
'''Commandline scripts. """Commandline scripts.
These scripts are called by the executables defined in setup.py. These scripts are called by the executables defined in setup.py.
''' """
from __future__ import with_statement, print_function from __future__ import with_statement, print_function
@ -31,8 +31,9 @@ import rsa.pkcs1
HASH_METHODS = sorted(rsa.pkcs1.HASH_METHODS.keys()) HASH_METHODS = sorted(rsa.pkcs1.HASH_METHODS.keys())
def keygen(): def keygen():
'''Key generator.''' """Key generator."""
# Parse the CLI options # Parse the CLI options
parser = OptionParser(usage='usage: %prog [options] keysize', parser = OptionParser(usage='usage: %prog [options] keysize',
@ -67,7 +68,6 @@ def keygen():
print('Generating %i-bit key' % keysize, file=sys.stderr) print('Generating %i-bit key' % keysize, file=sys.stderr)
(pub_key, priv_key) = rsa.newkeys(keysize) (pub_key, priv_key) = rsa.newkeys(keysize)
# Save public key # Save public key
if cli.pubout: if cli.pubout:
print('Writing public key to %s' % cli.pubout, file=sys.stderr) print('Writing public key to %s' % cli.pubout, file=sys.stderr)
@ -88,7 +88,7 @@ def keygen():
class CryptoOperation(object): class CryptoOperation(object):
'''CLI callable that operates with input, output, and a key.''' """CLI callable that operates with input, output, and a key."""
__metaclass__ = abc.ABCMeta __metaclass__ = abc.ABCMeta
@ -114,15 +114,15 @@ class CryptoOperation(object):
@abc.abstractmethod @abc.abstractmethod
def perform_operation(self, indata, key, cli_args=None): def perform_operation(self, indata, key, cli_args=None):
'''Performs the program's operation. """Performs the program's operation.
Implement in a subclass. Implement in a subclass.
:returns: the data to write to the output. :returns: the data to write to the output.
''' """
def __call__(self): def __call__(self):
'''Runs the program.''' """Runs the program."""
(cli, cli_args) = self.parse_cli() (cli, cli_args) = self.parse_cli()
@ -137,10 +137,10 @@ class CryptoOperation(object):
self.write_outfile(outdata, cli.output) self.write_outfile(outdata, cli.output)
def parse_cli(self): def parse_cli(self):
'''Parse the CLI options """Parse the CLI options
:returns: (cli_opts, cli_args) :returns: (cli_opts, cli_args)
''' """
parser = OptionParser(usage=self.usage, description=self.description) parser = OptionParser(usage=self.usage, description=self.description)
@ -159,10 +159,10 @@ class CryptoOperation(object):
parser.print_help() parser.print_help()
raise SystemExit(1) raise SystemExit(1)
return (cli, cli_args) return cli, cli_args
def read_key(self, filename, keyform): def read_key(self, filename, keyform):
'''Reads a public or private key.''' """Reads a public or private key."""
print('Reading %s key from %s' % (self.keyname, filename), file=sys.stderr) print('Reading %s key from %s' % (self.keyname, filename), file=sys.stderr)
with open(filename, 'rb') as keyfile: with open(filename, 'rb') as keyfile:
@ -171,7 +171,7 @@ class CryptoOperation(object):
return self.key_class.load_pkcs1(keydata, keyform) return self.key_class.load_pkcs1(keydata, keyform)
def read_infile(self, inname): def read_infile(self, inname):
'''Read the input file''' """Read the input file"""
if inname: if inname:
print('Reading input from %s' % inname, file=sys.stderr) print('Reading input from %s' % inname, file=sys.stderr)
@ -182,7 +182,7 @@ class CryptoOperation(object):
return sys.stdin.read() return sys.stdin.read()
def write_outfile(self, outdata, outname): def write_outfile(self, outdata, outname):
'''Write the output file''' """Write the output file"""
if outname: if outname:
print('Writing output to %s' % outname, file=sys.stderr) print('Writing output to %s' % outname, file=sys.stderr)
@ -192,8 +192,9 @@ class CryptoOperation(object):
print('Writing output to stdout', file=sys.stderr) print('Writing output to stdout', file=sys.stderr)
sys.stdout.write(outdata) sys.stdout.write(outdata)
class EncryptOperation(CryptoOperation): class EncryptOperation(CryptoOperation):
'''Encrypts a file.''' """Encrypts a file."""
keyname = 'public' keyname = 'public'
description = ('Encrypts a file. The file must be shorter than the key ' description = ('Encrypts a file. The file must be shorter than the key '
@ -203,14 +204,14 @@ class EncryptOperation(CryptoOperation):
operation_past = 'encrypted' operation_past = 'encrypted'
operation_progressive = 'encrypting' operation_progressive = 'encrypting'
def perform_operation(self, indata, pub_key, cli_args=None): def perform_operation(self, indata, pub_key, cli_args=None):
'''Encrypts files.''' """Encrypts files."""
return rsa.encrypt(indata, pub_key) return rsa.encrypt(indata, pub_key)
class DecryptOperation(CryptoOperation): class DecryptOperation(CryptoOperation):
'''Decrypts a file.''' """Decrypts a file."""
keyname = 'private' keyname = 'private'
description = ('Decrypts a file. The original file must be shorter than ' description = ('Decrypts a file. The original file must be shorter than '
@ -222,12 +223,13 @@ class DecryptOperation(CryptoOperation):
key_class = rsa.PrivateKey key_class = rsa.PrivateKey
def perform_operation(self, indata, priv_key, cli_args=None): def perform_operation(self, indata, priv_key, cli_args=None):
'''Decrypts files.''' """Decrypts files."""
return rsa.decrypt(indata, priv_key) return rsa.decrypt(indata, priv_key)
class SignOperation(CryptoOperation): class SignOperation(CryptoOperation):
'''Signs a file.''' """Signs a file."""
keyname = 'private' keyname = 'private'
usage = 'usage: %%prog [options] private_key hash_method' usage = 'usage: %%prog [options] private_key hash_method'
@ -243,7 +245,7 @@ class SignOperation(CryptoOperation):
'to stdout if this option is not present.') 'to stdout if this option is not present.')
def perform_operation(self, indata, priv_key, cli_args): def perform_operation(self, indata, priv_key, cli_args):
'''Decrypts files.''' """Signs files."""
hash_method = cli_args[1] hash_method = cli_args[1]
if hash_method not in HASH_METHODS: if hash_method not in HASH_METHODS:
@ -252,8 +254,9 @@ class SignOperation(CryptoOperation):
return rsa.sign(indata, priv_key, hash_method) return rsa.sign(indata, priv_key, hash_method)
class VerifyOperation(CryptoOperation): class VerifyOperation(CryptoOperation):
'''Verify a signature.''' """Verify a signature."""
keyname = 'public' keyname = 'public'
usage = 'usage: %%prog [options] public_key signature_file' usage = 'usage: %%prog [options] public_key signature_file'
@ -267,7 +270,7 @@ class VerifyOperation(CryptoOperation):
has_output = False has_output = False
def perform_operation(self, indata, pub_key, cli_args): def perform_operation(self, indata, pub_key, cli_args):
'''Decrypts files.''' """Verifies files."""
signature_file = cli_args[1] signature_file = cli_args[1]
@ -283,7 +286,7 @@ class VerifyOperation(CryptoOperation):
class BigfileOperation(CryptoOperation): class BigfileOperation(CryptoOperation):
'''CryptoOperation that doesn't read the entire file into memory.''' """CryptoOperation that doesn't read the entire file into memory."""
def __init__(self): def __init__(self):
CryptoOperation.__init__(self) CryptoOperation.__init__(self)
@ -291,13 +294,13 @@ class BigfileOperation(CryptoOperation):
self.file_objects = [] self.file_objects = []
def __del__(self): def __del__(self):
'''Closes any open file handles.''' """Closes any open file handles."""
for fobj in self.file_objects: for fobj in self.file_objects:
fobj.close() fobj.close()
def __call__(self): def __call__(self):
'''Runs the program.''' """Runs the program."""
(cli, cli_args) = self.parse_cli() (cli, cli_args) = self.parse_cli()
@ -312,7 +315,7 @@ class BigfileOperation(CryptoOperation):
self.perform_operation(infile, outfile, key, cli_args) self.perform_operation(infile, outfile, key, cli_args)
def get_infile(self, inname): def get_infile(self, inname):
'''Returns the input file object''' """Returns the input file object"""
if inname: if inname:
print('Reading input from %s' % inname, file=sys.stderr) print('Reading input from %s' % inname, file=sys.stderr)
@ -325,7 +328,7 @@ class BigfileOperation(CryptoOperation):
return fobj return fobj
def get_outfile(self, outname): def get_outfile(self, outname):
'''Returns the output file object''' """Returns the output file object"""
if outname: if outname:
print('Will write output to %s' % outname, file=sys.stderr) print('Will write output to %s' % outname, file=sys.stderr)
@ -337,8 +340,9 @@ class BigfileOperation(CryptoOperation):
return fobj return fobj
class EncryptBigfileOperation(BigfileOperation): class EncryptBigfileOperation(BigfileOperation):
'''Encrypts a file to VARBLOCK format.''' """Encrypts a file to VARBLOCK format."""
keyname = 'public' keyname = 'public'
description = ('Encrypts a file to an encrypted VARBLOCK file. The file ' description = ('Encrypts a file to an encrypted VARBLOCK file. The file '
@ -349,12 +353,13 @@ class EncryptBigfileOperation(BigfileOperation):
operation_progressive = 'encrypting' operation_progressive = 'encrypting'
def perform_operation(self, infile, outfile, pub_key, cli_args=None): def perform_operation(self, infile, outfile, pub_key, cli_args=None):
'''Encrypts files to VARBLOCK.''' """Encrypts files to VARBLOCK."""
return rsa.bigfile.encrypt_bigfile(infile, outfile, pub_key) return rsa.bigfile.encrypt_bigfile(infile, outfile, pub_key)
class DecryptBigfileOperation(BigfileOperation): class DecryptBigfileOperation(BigfileOperation):
'''Decrypts a file in VARBLOCK format.''' """Decrypts a file in VARBLOCK format."""
keyname = 'private' keyname = 'private'
description = ('Decrypts an encrypted VARBLOCK file that was encrypted ' description = ('Decrypts an encrypted VARBLOCK file that was encrypted '
@ -365,7 +370,7 @@ class DecryptBigfileOperation(BigfileOperation):
key_class = rsa.PrivateKey key_class = rsa.PrivateKey
def perform_operation(self, infile, outfile, priv_key, cli_args=None): def perform_operation(self, infile, outfile, priv_key, cli_args=None):
'''Decrypts a VARBLOCK file.''' """Decrypts a VARBLOCK file."""
return rsa.bigfile.decrypt_bigfile(infile, outfile, priv_key) return rsa.bigfile.decrypt_bigfile(infile, outfile, priv_key)
@ -376,4 +381,3 @@ sign = SignOperation()
verify = VerifyOperation() verify = VerifyOperation()
encrypt_bigfile = EncryptBigfileOperation() encrypt_bigfile = EncryptBigfileOperation()
decrypt_bigfile = DecryptBigfileOperation() decrypt_bigfile = DecryptBigfileOperation()

View file

@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
@ -14,15 +14,15 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
'''Common functionality shared by several modules.''' """Common functionality shared by several modules."""
def bit_size(num): def bit_size(num):
''' """
Number of bits needed to represent a integer excluding any prefix Number of bits needed to represent a integer excluding any prefix
0 bits. 0 bits.
As per definition from http://wiki.python.org/moin/BitManipulation and As per definition from https://wiki.python.org/moin/BitManipulation and
to match the behavior of the Python 3 API. to match the behavior of the Python 3 API.
Usage:: Usage::
@ -40,7 +40,7 @@ def bit_size(num):
before the number's bit length is determined. before the number's bit length is determined.
:returns: :returns:
Returns the number of bits in the integer. Returns the number of bits in the integer.
''' """
if num == 0: if num == 0:
return 0 return 0
if num < 0: if num < 0:
@ -59,9 +59,9 @@ def bit_size(num):
def _bit_size(number): def _bit_size(number):
''' """
Returns the number of bits required to hold a specific long number. Returns the number of bits required to hold a specific long number.
''' """
if number < 0: if number < 0:
raise ValueError('Only nonnegative numbers possible: %s' % number) raise ValueError('Only nonnegative numbers possible: %s' % number)
@ -79,7 +79,7 @@ def _bit_size(number):
def byte_size(number): def byte_size(number):
''' """
Returns the number of bytes required to hold a specific long number. Returns the number of bytes required to hold a specific long number.
The number of bytes is rounded up. The number of bytes is rounded up.
@ -97,7 +97,7 @@ def byte_size(number):
An unsigned integer An unsigned integer
:returns: :returns:
The number of bytes required to hold a specific long number. The number of bytes required to hold a specific long number.
''' """
quanta, mod = divmod(bit_size(number), 8) quanta, mod = divmod(bit_size(number), 8)
if mod or number == 0: if mod or number == 0:
quanta += 1 quanta += 1
@ -106,8 +106,8 @@ def byte_size(number):
def extended_gcd(a, b): def extended_gcd(a, b):
'''Returns a tuple (r, i, j) such that r = gcd(a, b) = ia + jb """Returns a tuple (r, i, j) such that r = gcd(a, b) = ia + jb
''' """
# r = gcd(a,b) i = multiplicitive inverse of a mod b # r = gcd(a,b) i = multiplicitive inverse of a mod b
# or j = multiplicitive inverse of b mod a # or j = multiplicitive inverse of b mod a
# Neg return values for i or j are made positive mod b or a respectively # Neg return values for i or j are made positive mod b or a respectively
@ -123,19 +123,21 @@ def extended_gcd(a, b):
(a, b) = (b, a % b) (a, b) = (b, a % b)
(x, lx) = ((lx - (q * x)), x) (x, lx) = ((lx - (q * x)), x)
(y, ly) = ((ly - (q * y)), y) (y, ly) = ((ly - (q * y)), y)
if (lx < 0): lx += ob #If neg wrap modulo orignal b if lx < 0:
if (ly < 0): ly += oa #If neg wrap modulo orignal a lx += ob # If neg wrap modulo orignal b
return (a, lx, ly) #Return only positive values if ly < 0:
ly += oa # If neg wrap modulo orignal a
return a, lx, ly # Return only positive values
def inverse(x, n): def inverse(x, n):
'''Returns x^-1 (mod n) """Returns x^-1 (mod n)
>>> inverse(7, 4) >>> inverse(7, 4)
3 3
>>> (inverse(143, 4) * 143) % 4 >>> (inverse(143, 4) * 143) % 4
1 1
''' """
(divider, inv, _) = extended_gcd(x, n) (divider, inv, _) = extended_gcd(x, n)
@ -146,7 +148,7 @@ def inverse(x, n):
def crt(a_values, modulo_values): def crt(a_values, modulo_values):
'''Chinese Remainder Theorem. """Chinese Remainder Theorem.
Calculates x such that x = a[i] (mod m[i]) for each i. Calculates x such that x = a[i] (mod m[i]) for each i.
@ -163,7 +165,7 @@ def crt(a_values, modulo_values):
>>> crt([2, 3, 0], [7, 11, 15]) >>> crt([2, 3, 0], [7, 11, 15])
135 135
''' """
m = 1 m = 1
x = 0 x = 0
@ -179,7 +181,8 @@ def crt(a_values, modulo_values):
return x return x
if __name__ == '__main__': if __name__ == '__main__':
import doctest import doctest
doctest.testmod()
doctest.testmod()

View file

@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
@ -14,24 +14,24 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
'''Core mathematical operations. """Core mathematical operations.
This is the actual core RSA implementation, which is only defined This is the actual core RSA implementation, which is only defined
mathematically on integers. mathematically on integers.
''' """
from rsa._compat import is_integer from rsa._compat import is_integer
def assert_int(var, name):
def assert_int(var, name):
if is_integer(var): if is_integer(var):
return return
raise TypeError('%s should be an integer, not %s' % (name, var.__class__)) raise TypeError('%s should be an integer, not %s' % (name, var.__class__))
def encrypt_int(message, ekey, n): def encrypt_int(message, ekey, n):
'''Encrypts a message using encryption key 'ekey', working modulo n''' """Encrypts a message using encryption key 'ekey', working modulo n"""
assert_int(message, 'message') assert_int(message, 'message')
assert_int(ekey, 'ekey') assert_int(ekey, 'ekey')
@ -45,9 +45,9 @@ def encrypt_int(message, ekey, n):
return pow(message, ekey, n) return pow(message, ekey, n)
def decrypt_int(cyphertext, dkey, n): def decrypt_int(cyphertext, dkey, n):
'''Decrypts a cypher text using the decryption key 'dkey', working """Decrypts a cypher text using the decryption key 'dkey', working modulo n"""
modulo n'''
assert_int(cyphertext, 'cyphertext') assert_int(cyphertext, 'cyphertext')
assert_int(dkey, 'dkey') assert_int(dkey, 'dkey')
@ -55,4 +55,3 @@ def decrypt_int(cyphertext, dkey, n):
message = pow(cyphertext, dkey, n) message = pow(cyphertext, dkey, n)
return message return message

View file

@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
'''RSA key generation code. """RSA key generation code.
Create new keys with the newkeys() function. It will give you a PublicKey and a Create new keys with the newkeys() function. It will give you a PublicKey and a
PrivateKey object. PrivateKey object.
@ -23,70 +23,118 @@ 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 late as possible, such that other functionality will remain working in absence
of pyasn1. 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 logging import logging
from rsa._compat import b, bytes_type from rsa._compat import b
import rsa.prime import rsa.prime
import rsa.pem import rsa.pem
import rsa.common import rsa.common
import rsa.randnum
import rsa.core
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
DEFAULT_EXPONENT = 65537
class AbstractKey(object): class AbstractKey(object):
'''Abstract superclass for private and public keys.''' """Abstract superclass for private and public keys."""
__slots__ = ('n', 'e')
def __init__(self, n, e):
self.n = n
self.e = e
@classmethod @classmethod
def load_pkcs1(cls, keyfile, format='PEM'): def load_pkcs1(cls, keyfile, format='PEM'):
r'''Loads a key in PKCS#1 DER or PEM format. """Loads a key in PKCS#1 DER or PEM format.
:param keyfile: contents of a DER- or PEM-encoded file that contains :param keyfile: contents of a DER- or PEM-encoded file that contains
the public key. the public key.
:param format: the format of the file to load; 'PEM' or 'DER' :param format: the format of the file to load; 'PEM' or 'DER'
:return: a PublicKey object :return: a PublicKey object
"""
'''
methods = { methods = {
'PEM': cls._load_pkcs1_pem, 'PEM': cls._load_pkcs1_pem,
'DER': cls._load_pkcs1_der, 'DER': cls._load_pkcs1_der,
} }
if format not in methods: method = cls._assert_format_exists(format, methods)
formats = ', '.join(sorted(methods.keys()))
raise ValueError('Unsupported format: %r, try one of %s' % (format,
formats))
method = methods[format]
return method(keyfile) return method(keyfile)
@staticmethod
def _assert_format_exists(file_format, methods):
"""Checks whether the given file format exists in 'methods'.
"""
try:
return methods[file_format]
except KeyError:
formats = ', '.join(sorted(methods.keys()))
raise ValueError('Unsupported format: %r, try one of %s' % (file_format,
formats))
def save_pkcs1(self, format='PEM'): def save_pkcs1(self, format='PEM'):
'''Saves the public key in PKCS#1 DER or PEM format. """Saves the public key in PKCS#1 DER or PEM format.
:param format: the format to save; 'PEM' or 'DER' :param format: the format to save; 'PEM' or 'DER'
:returns: the DER- or PEM-encoded public key. :returns: the DER- or PEM-encoded public key.
"""
'''
methods = { methods = {
'PEM': self._save_pkcs1_pem, 'PEM': self._save_pkcs1_pem,
'DER': self._save_pkcs1_der, 'DER': self._save_pkcs1_der,
} }
if format not in methods: method = self._assert_format_exists(format, methods)
formats = ', '.join(sorted(methods.keys()))
raise ValueError('Unsupported format: %r, try one of %s' % (format,
formats))
method = methods[format]
return method() return method()
def blind(self, message, r):
"""Performs blinding on the message using random number 'r'.
:param message: the message, as integer, to blind.
:type message: int
:param r: the random number to blind with.
:type r: int
:return: the blinded message.
:rtype: int
The blinding is such that message = unblind(decrypt(blind(encrypt(message))).
See https://en.wikipedia.org/wiki/Blinding_%28cryptography%29
"""
return (message * pow(r, self.e, self.n)) % self.n
def unblind(self, blinded, r):
"""Performs blinding on the message using random number 'r'.
:param blinded: the blinded message, as integer, to unblind.
:param r: the random number 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 (rsa.common.inverse(r, self.n) * blinded) % self.n
class PublicKey(AbstractKey): class PublicKey(AbstractKey):
'''Represents a public RSA key. """Represents a public RSA key.
This key is also known as the 'encryption key'. It contains the 'n' and 'e' This key is also known as the 'encryption key'. It contains the 'n' and 'e'
values. values.
@ -107,20 +155,24 @@ class PublicKey(AbstractKey):
>>> key['e'] >>> key['e']
3 3
''' """
__slots__ = ('n', 'e') __slots__ = ('n', 'e')
def __init__(self, n, e):
self.n = n
self.e = e
def __getitem__(self, key): def __getitem__(self, key):
return getattr(self, key) return getattr(self, key)
def __repr__(self): def __repr__(self):
return 'PublicKey(%i, %i)' % (self.n, self.e) return 'PublicKey(%i, %i)' % (self.n, self.e)
def __getstate__(self):
"""Returns the key as tuple for pickling."""
return self.n, self.e
def __setstate__(self, state):
"""Sets the key from tuple."""
self.n, self.e = state
def __eq__(self, other): def __eq__(self, other):
if other is None: if other is None:
return False return False
@ -135,24 +187,24 @@ class PublicKey(AbstractKey):
@classmethod @classmethod
def _load_pkcs1_der(cls, keyfile): def _load_pkcs1_der(cls, keyfile):
r'''Loads a key in PKCS#1 DER format. """Loads a key in PKCS#1 DER format.
@param keyfile: contents of a DER-encoded file that contains the public :param keyfile: contents of a DER-encoded file that contains the public
key. key.
@return: a PublicKey object :return: a PublicKey object
First let's construct a DER encoded key: First let's construct a DER encoded key:
>>> import base64 >>> import base64
>>> b64der = 'MAwCBQCNGmYtAgMBAAE=' >>> b64der = 'MAwCBQCNGmYtAgMBAAE='
>>> der = base64.decodestring(b64der) >>> der = base64.standard_b64decode(b64der)
This loads the file: This loads the file:
>>> PublicKey._load_pkcs1_der(der) >>> PublicKey._load_pkcs1_der(der)
PublicKey(2367317549, 65537) PublicKey(2367317549, 65537)
''' """
from pyasn1.codec.der import decoder from pyasn1.codec.der import decoder
from rsa.asn1 import AsnPubKey from rsa.asn1 import AsnPubKey
@ -161,10 +213,10 @@ class PublicKey(AbstractKey):
return cls(n=int(priv['modulus']), e=int(priv['publicExponent'])) return cls(n=int(priv['modulus']), e=int(priv['publicExponent']))
def _save_pkcs1_der(self): def _save_pkcs1_der(self):
'''Saves the public key in PKCS#1 DER format. """Saves the public key in PKCS#1 DER format.
@returns: the DER-encoded public key. @returns: the DER-encoded public key.
''' """
from pyasn1.codec.der import encoder from pyasn1.codec.der import encoder
from rsa.asn1 import AsnPubKey from rsa.asn1 import AsnPubKey
@ -178,31 +230,31 @@ class PublicKey(AbstractKey):
@classmethod @classmethod
def _load_pkcs1_pem(cls, keyfile): def _load_pkcs1_pem(cls, keyfile):
'''Loads a PKCS#1 PEM-encoded public key file. """Loads a PKCS#1 PEM-encoded public key file.
The contents of the file before the "-----BEGIN RSA PUBLIC KEY-----" and The contents of the file before the "-----BEGIN RSA PUBLIC KEY-----" and
after the "-----END RSA PUBLIC KEY-----" lines is ignored. after the "-----END RSA PUBLIC KEY-----" lines is ignored.
@param keyfile: contents of a PEM-encoded file that contains the public :param keyfile: contents of a PEM-encoded file that contains the public
key. key.
@return: a PublicKey object :return: a PublicKey object
''' """
der = rsa.pem.load_pem(keyfile, 'RSA PUBLIC KEY') der = rsa.pem.load_pem(keyfile, 'RSA PUBLIC KEY')
return cls._load_pkcs1_der(der) return cls._load_pkcs1_der(der)
def _save_pkcs1_pem(self): def _save_pkcs1_pem(self):
'''Saves a PKCS#1 PEM-encoded public key file. """Saves a PKCS#1 PEM-encoded public key file.
@return: contents of a PEM-encoded file that contains the public key. :return: contents of a PEM-encoded file that contains the public key.
''' """
der = self._save_pkcs1_der() der = self._save_pkcs1_der()
return rsa.pem.save_pem(der, 'RSA PUBLIC KEY') return rsa.pem.save_pem(der, 'RSA PUBLIC KEY')
@classmethod @classmethod
def load_pkcs1_openssl_pem(cls, keyfile): def load_pkcs1_openssl_pem(cls, keyfile):
'''Loads a PKCS#1.5 PEM-encoded public key file from OpenSSL. """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 These files can be recognised in that they start with BEGIN PUBLIC KEY
rather than BEGIN RSA PUBLIC KEY. rather than BEGIN RSA PUBLIC KEY.
@ -210,22 +262,23 @@ class PublicKey(AbstractKey):
The contents of the file before the "-----BEGIN PUBLIC KEY-----" and The contents of the file before the "-----BEGIN PUBLIC KEY-----" and
after the "-----END PUBLIC KEY-----" lines is ignored. after the "-----END PUBLIC KEY-----" lines is ignored.
@param keyfile: contents of a PEM-encoded file that contains the public :param keyfile: contents of a PEM-encoded file that contains the public
key, from OpenSSL. key, from OpenSSL.
@return: a PublicKey object :return: a PublicKey object
''' """
der = rsa.pem.load_pem(keyfile, 'PUBLIC KEY') der = rsa.pem.load_pem(keyfile, 'PUBLIC KEY')
return cls.load_pkcs1_openssl_der(der) return cls.load_pkcs1_openssl_der(der)
@classmethod @classmethod
def load_pkcs1_openssl_der(cls, keyfile): def load_pkcs1_openssl_der(cls, keyfile):
'''Loads a PKCS#1 DER-encoded public key file from OpenSSL. """Loads a PKCS#1 DER-encoded public key file from OpenSSL.
@param keyfile: contents of a DER-encoded file that contains the public :param keyfile: contents of a DER-encoded file that contains the public
key, from OpenSSL. key, from OpenSSL.
@return: a PublicKey object :return: a PublicKey object
'''
"""
from rsa.asn1 import OpenSSLPubKey from rsa.asn1 import OpenSSLPubKey
from pyasn1.codec.der import decoder from pyasn1.codec.der import decoder
@ -239,10 +292,8 @@ class PublicKey(AbstractKey):
return cls._load_pkcs1_der(keyinfo['key'][1:]) return cls._load_pkcs1_der(keyinfo['key'][1:])
class PrivateKey(AbstractKey): class PrivateKey(AbstractKey):
'''Represents a private RSA key. """Represents a private RSA key.
This key is also known as the 'decryption key'. It contains the 'n', 'e', This key is also known as the 'decryption key'. It contains the 'n', 'e',
'd', 'p', 'q' and other values. 'd', 'p', 'q' and other values.
@ -253,13 +304,13 @@ class PrivateKey(AbstractKey):
>>> PrivateKey(3247, 65537, 833, 191, 17) >>> PrivateKey(3247, 65537, 833, 191, 17)
PrivateKey(3247, 65537, 833, 191, 17) PrivateKey(3247, 65537, 833, 191, 17)
exp1, exp2 and coef don't have to be given, they will be calculated: exp1, exp2 and coef can be given, but if None or omitted they will be calculated:
>>> pk = PrivateKey(3727264081, 65537, 3349121513, 65063, 57287) >>> pk = PrivateKey(3727264081, 65537, 3349121513, 65063, 57287, exp2=4)
>>> pk.exp1 >>> pk.exp1
55063 55063
>>> pk.exp2 >>> pk.exp2 # this is of course not a correct value, but it is the one we passed.
10095 4
>>> pk.coef >>> pk.coef
50797 50797
@ -273,13 +324,12 @@ class PrivateKey(AbstractKey):
>>> pk.coef >>> pk.coef
8 8
''' """
__slots__ = ('n', 'e', 'd', 'p', 'q', 'exp1', 'exp2', 'coef') __slots__ = ('n', 'e', 'd', 'p', 'q', 'exp1', 'exp2', 'coef')
def __init__(self, n, e, d, p, q, exp1=None, exp2=None, coef=None): def __init__(self, n, e, d, p, q, exp1=None, exp2=None, coef=None):
self.n = n AbstractKey.__init__(self, n, e)
self.e = e
self.d = d self.d = d
self.p = p self.p = p
self.q = q self.q = q
@ -290,7 +340,7 @@ class PrivateKey(AbstractKey):
else: else:
self.exp1 = exp1 self.exp1 = exp1
if exp1 is None: if exp2 is None:
self.exp2 = int(d % (q - 1)) self.exp2 = int(d % (q - 1))
else: else:
self.exp2 = exp2 self.exp2 = exp2
@ -306,6 +356,14 @@ class PrivateKey(AbstractKey):
def __repr__(self): def __repr__(self):
return 'PrivateKey(%(n)i, %(e)i, %(d)i, %(p)i, %(q)i)' % self return 'PrivateKey(%(n)i, %(e)i, %(d)i, %(p)i, %(q)i)' % self
def __getstate__(self):
"""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):
"""Sets the key from tuple."""
self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef = state
def __eq__(self, other): def __eq__(self, other):
if other is None: if other is None:
return False return False
@ -325,26 +383,57 @@ class PrivateKey(AbstractKey):
def __ne__(self, other): def __ne__(self, other):
return not (self == other) return not (self == other)
def blinded_decrypt(self, encrypted):
"""Decrypts the message using blinding to prevent side-channel attacks.
:param encrypted: the encrypted message
:type encrypted: int
:returns: the decrypted message
:rtype: int
"""
blind_r = rsa.randnum.randint(self.n - 1)
blinded = self.blind(encrypted, blind_r) # blind before decrypting
decrypted = rsa.core.decrypt_int(blinded, self.d, self.n)
return self.unblind(decrypted, blind_r)
def blinded_encrypt(self, message):
"""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
"""
blind_r = rsa.randnum.randint(self.n - 1)
blinded = self.blind(message, blind_r) # blind before encrypting
encrypted = rsa.core.encrypt_int(blinded, self.d, self.n)
return self.unblind(encrypted, blind_r)
@classmethod @classmethod
def _load_pkcs1_der(cls, keyfile): def _load_pkcs1_der(cls, keyfile):
r'''Loads a key in PKCS#1 DER format. """Loads a key in PKCS#1 DER format.
@param keyfile: contents of a DER-encoded file that contains the private :param keyfile: contents of a DER-encoded file that contains the private
key. key.
@return: a PrivateKey object :return: a PrivateKey object
First let's construct a DER encoded key: First let's construct a DER encoded key:
>>> import base64 >>> import base64
>>> b64der = 'MC4CAQACBQDeKYlRAgMBAAECBQDHn4npAgMA/icCAwDfxwIDANcXAgInbwIDAMZt' >>> b64der = 'MC4CAQACBQDeKYlRAgMBAAECBQDHn4npAgMA/icCAwDfxwIDANcXAgInbwIDAMZt'
>>> der = base64.decodestring(b64der) >>> der = base64.standard_b64decode(b64der)
This loads the file: This loads the file:
>>> PrivateKey._load_pkcs1_der(der) >>> PrivateKey._load_pkcs1_der(der)
PrivateKey(3727264081, 65537, 3349121513, 65063, 57287) PrivateKey(3727264081, 65537, 3349121513, 65063, 57287)
''' """
from pyasn1.codec.der import decoder from pyasn1.codec.der import decoder
(priv, _) = decoder.decode(keyfile) (priv, _) = decoder.decode(keyfile)
@ -371,10 +460,10 @@ class PrivateKey(AbstractKey):
return cls(*as_ints) return cls(*as_ints)
def _save_pkcs1_der(self): def _save_pkcs1_der(self):
'''Saves the private key in PKCS#1 DER format. """Saves the private key in PKCS#1 DER format.
@returns: the DER-encoded private key. @returns: the DER-encoded private key.
''' """
from pyasn1.type import univ, namedtype from pyasn1.type import univ, namedtype
from pyasn1.codec.der import encoder from pyasn1.codec.der import encoder
@ -408,30 +497,31 @@ class PrivateKey(AbstractKey):
@classmethod @classmethod
def _load_pkcs1_pem(cls, keyfile): def _load_pkcs1_pem(cls, keyfile):
'''Loads a PKCS#1 PEM-encoded private key file. """Loads a PKCS#1 PEM-encoded private key file.
The contents of the file before the "-----BEGIN RSA PRIVATE KEY-----" and The contents of the file before the "-----BEGIN RSA PRIVATE KEY-----" and
after the "-----END RSA PRIVATE KEY-----" lines is ignored. after the "-----END RSA PRIVATE KEY-----" lines is ignored.
@param keyfile: contents of a PEM-encoded file that contains the private :param keyfile: contents of a PEM-encoded file that contains the private
key. key.
@return: a PrivateKey object :return: a PrivateKey object
''' """
der = rsa.pem.load_pem(keyfile, b('RSA PRIVATE KEY')) der = rsa.pem.load_pem(keyfile, b('RSA PRIVATE KEY'))
return cls._load_pkcs1_der(der) return cls._load_pkcs1_der(der)
def _save_pkcs1_pem(self): def _save_pkcs1_pem(self):
'''Saves a PKCS#1 PEM-encoded private key file. """Saves a PKCS#1 PEM-encoded private key file.
@return: contents of a PEM-encoded file that contains the private key. :return: contents of a PEM-encoded file that contains the private key.
''' """
der = self._save_pkcs1_der() der = self._save_pkcs1_der()
return rsa.pem.save_pem(der, b('RSA PRIVATE KEY')) return rsa.pem.save_pem(der, b('RSA PRIVATE KEY'))
def find_p_q(nbits, getprime_func=rsa.prime.getprime, accurate=True): def find_p_q(nbits, getprime_func=rsa.prime.getprime, accurate=True):
''''Returns a tuple of two different primes of nbits bits each. """Returns a tuple of two different primes of nbits bits each.
The resulting p * q has exacty 2 * nbits bits, and the returned p and q The resulting p * q has exacty 2 * nbits bits, and the returned p and q
will not be equal. will not be equal.
@ -459,7 +549,7 @@ def find_p_q(nbits, getprime_func=rsa.prime.getprime, accurate=True):
>>> common.bit_size(p * q) > 240 >>> common.bit_size(p * q) > 240
True True
''' """
total_bits = nbits * 2 total_bits = nbits * 2
@ -476,11 +566,11 @@ def find_p_q(nbits, getprime_func=rsa.prime.getprime, accurate=True):
q = getprime_func(qbits) q = getprime_func(qbits)
def is_acceptable(p, q): def is_acceptable(p, q):
'''Returns True iff p and q are acceptable: """Returns True iff p and q are acceptable:
- p and q differ - p and q differ
- (p * q) has the right nr of bits (when accurate=True) - (p * q) has the right nr of bits (when accurate=True)
''' """
if p == q: if p == q:
return False return False
@ -505,33 +595,52 @@ def find_p_q(nbits, getprime_func=rsa.prime.getprime, accurate=True):
# We want p > q as described on # We want p > q as described on
# http://www.di-mgt.com.au/rsa_alg.html#crt # http://www.di-mgt.com.au/rsa_alg.html#crt
return (max(p, q), min(p, q)) return max(p, q), min(p, q)
def calculate_keys(p, q, nbits):
'''Calculates an encryption and a decryption key given p and q, and
returns them as a tuple (e, d)
''' def calculate_keys_custom_exponent(p, q, exponent):
"""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) phi_n = (p - 1) * (q - 1)
# A very common choice for e is 65537
e = 65537
try: try:
d = rsa.common.inverse(e, phi_n) d = rsa.common.inverse(exponent, phi_n)
except ValueError: except ValueError:
raise ValueError("e (%d) and phi_n (%d) are not relatively prime" % raise ValueError("e (%d) and phi_n (%d) are not relatively prime" %
(e, phi_n)) (exponent, phi_n))
if (e * d) % phi_n != 1: if (exponent * d) % phi_n != 1:
raise ValueError("e (%d) and d (%d) are not mult. inv. modulo " raise ValueError("e (%d) and d (%d) are not mult. inv. modulo "
"phi_n (%d)" % (e, d, phi_n)) "phi_n (%d)" % (exponent, d, phi_n))
return (e, d) return exponent, d
def gen_keys(nbits, getprime_func, accurate=True):
'''Generate RSA keys of nbits bits. Returns (p, q, e, d). def calculate_keys(p, q):
"""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, getprime_func, accurate=True, exponent=DEFAULT_EXPONENT):
"""Generate RSA keys of nbits bits. Returns (p, q, e, d).
Note: this can take a long time, depending on the key size. Note: this can take a long time, depending on the key size.
@ -539,15 +648,27 @@ def gen_keys(nbits, getprime_func, accurate=True):
``q`` will use ``nbits/2`` bits. ``q`` will use ``nbits/2`` bits.
:param getprime_func: either :py:func:`rsa.prime.getprime` or a function :param getprime_func: either :py:func:`rsa.prime.getprime` or a function
with similar signature. 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) (p, q) = find_p_q(nbits // 2, getprime_func, accurate)
(e, d) = calculate_keys(p, q, nbits // 2) try:
(e, d) = calculate_keys_custom_exponent(p, q, exponent=exponent)
break
except ValueError:
pass
return (p, q, e, d) return p, q, e, d
def newkeys(nbits, accurate=True, poolsize=1):
'''Generates public and private keys, and returns them as (pub, priv). def newkeys(nbits, accurate=True, poolsize=1, exponent=DEFAULT_EXPONENT):
"""Generates public and private keys, and returns them as (pub, priv).
The public key is also known as the 'encryption key', and is a 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 :py:class:`rsa.PublicKey` object. The private key is also known as the
@ -560,13 +681,17 @@ def newkeys(nbits, accurate=True, poolsize=1):
:param poolsize: the number of processes to use to generate the prime :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. numbers. If set to a number > 1, a parallel algorithm will be used.
This requires Python 2.6 or newer. 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`) :returns: a tuple (:py:class:`rsa.PublicKey`, :py:class:`rsa.PrivateKey`)
The ``poolsize`` parameter was added in *Python-RSA 3.1* and requires The ``poolsize`` parameter was added in *Python-RSA 3.1* and requires
Python 2.6 or newer. Python 2.6 or newer.
''' """
if nbits < 16: if nbits < 16:
raise ValueError('Key too small') raise ValueError('Key too small')
@ -580,10 +705,11 @@ def newkeys(nbits, accurate=True, poolsize=1):
import functools import functools
getprime_func = functools.partial(parallel.getprime, poolsize=poolsize) getprime_func = functools.partial(parallel.getprime, poolsize=poolsize)
else: getprime_func = rsa.prime.getprime else:
getprime_func = rsa.prime.getprime
# Generate the key components # Generate the key components
(p, q, e, d) = gen_keys(nbits, getprime_func) (p, q, e, d) = gen_keys(nbits, getprime_func, accurate=accurate, exponent=exponent)
# Create the key objects # Create the key objects
n = p * q n = p * q
@ -593,6 +719,7 @@ def newkeys(nbits, accurate=True, poolsize=1):
PrivateKey(n, e, d, p, q) PrivateKey(n, e, d, p, q)
) )
__all__ = ['PublicKey', 'PrivateKey', 'newkeys'] __all__ = ['PublicKey', 'PrivateKey', 'newkeys']
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
'''Functions for parallel computation on multiple cores. """Functions for parallel computation on multiple cores.
Introduced in Python-RSA 3.1. Introduced in Python-RSA 3.1.
@ -22,7 +22,7 @@ Introduced in Python-RSA 3.1.
Requires Python 2.6 or newer. Requires Python 2.6 or newer.
''' """
from __future__ import print_function from __future__ import print_function
@ -31,20 +31,19 @@ import multiprocessing as mp
import rsa.prime import rsa.prime
import rsa.randnum import rsa.randnum
def _find_prime(nbits, pipe): def _find_prime(nbits, pipe):
while True: while True:
integer = rsa.randnum.read_random_int(nbits) integer = rsa.randnum.read_random_odd_int(nbits)
# Make sure it's odd
integer |= 1
# Test for primeness # Test for primeness
if rsa.prime.is_prime(integer): if rsa.prime.is_prime(integer):
pipe.send(integer) pipe.send(integer)
return return
def getprime(nbits, poolsize): def getprime(nbits, poolsize):
'''Returns a prime number that can be stored in 'nbits' bits. """Returns a prime number that can be stored in 'nbits' bits.
Works in multiple threads at the same time. Works in multiple threads at the same time.
@ -60,23 +59,31 @@ def getprime(nbits, poolsize):
>>> common.bit_size(p) == 128 >>> common.bit_size(p) == 128
True True
''' """
(pipe_recv, pipe_send) = mp.Pipe(duplex=False) (pipe_recv, pipe_send) = mp.Pipe(duplex=False)
# Create processes # Create processes
try:
procs = [mp.Process(target=_find_prime, args=(nbits, pipe_send)) procs = [mp.Process(target=_find_prime, args=(nbits, pipe_send))
for _ in range(poolsize)] for _ in range(poolsize)]
[p.start() for p in procs] # Start processes
for p in procs:
p.start()
result = pipe_recv.recv() result = pipe_recv.recv()
finally:
pipe_recv.close()
pipe_send.close()
[p.terminate() for p in procs] # Terminate processes
for p in procs:
p.terminate()
return result return result
__all__ = ['getprime']
__all__ = ['getprime']
if __name__ == '__main__': if __name__ == '__main__':
print('Running doctests 1000x or until failure') print('Running doctests 1000x or until failure')
@ -91,4 +98,3 @@ if __name__ == '__main__':
print('%i times' % count) print('%i times' % count)
print('Doctests done') print('Doctests done')

View file

@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
@ -14,15 +14,16 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
'''Functions that load and write PEM-encoded files.''' """Functions that load and write PEM-encoded files."""
import base64 import base64
from rsa._compat import b, is_bytes from rsa._compat import b, is_bytes
def _markers(pem_marker): def _markers(pem_marker):
''' """
Returns the start and end PEM markers Returns the start and end PEM markers
''' """
if is_bytes(pem_marker): if is_bytes(pem_marker):
pem_marker = pem_marker.decode('utf-8') pem_marker = pem_marker.decode('utf-8')
@ -30,20 +31,25 @@ def _markers(pem_marker):
return (b('-----BEGIN %s-----' % pem_marker), return (b('-----BEGIN %s-----' % pem_marker),
b('-----END %s-----' % pem_marker)) b('-----END %s-----' % pem_marker))
def load_pem(contents, pem_marker):
'''Loads a PEM file.
@param contents: the contents of the file to interpret def load_pem(contents, pem_marker):
@param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY' """Loads a PEM file.
:param contents: the contents of the file to interpret
:param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY'
when your file has '-----BEGIN RSA PRIVATE KEY-----' and when your file has '-----BEGIN RSA PRIVATE KEY-----' and
'-----END RSA PRIVATE KEY-----' markers. '-----END RSA PRIVATE KEY-----' markers.
@return the base64-decoded content between the start and end markers. :return: the base64-decoded content between the start and end markers.
@raise ValueError: when the content is invalid, for example when the start @raise ValueError: when the content is invalid, for example when the start
marker cannot be found. marker cannot be found.
''' """
# We want bytes, not text. If it's text, it can be converted to ASCII bytes.
if not is_bytes(contents):
contents = contents.encode('ascii')
(pem_start, pem_end) = _markers(pem_marker) (pem_start, pem_end) = _markers(pem_marker)
@ -89,24 +95,24 @@ def load_pem(contents, pem_marker):
# Base64-decode the contents # Base64-decode the contents
pem = b('').join(pem_lines) pem = b('').join(pem_lines)
return base64.decodestring(pem) return base64.standard_b64decode(pem)
def save_pem(contents, pem_marker): def save_pem(contents, pem_marker):
'''Saves a PEM file. """Saves a PEM file.
@param contents: the contents to encode in PEM format :param contents: the contents to encode in PEM format
@param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY' :param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY'
when your file has '-----BEGIN RSA PRIVATE KEY-----' and when your file has '-----BEGIN RSA PRIVATE KEY-----' and
'-----END RSA PRIVATE KEY-----' markers. '-----END RSA PRIVATE KEY-----' markers.
@return the base64-encoded content between the start and end markers. :return: the base64-encoded content between the start and end markers.
''' """
(pem_start, pem_end) = _markers(pem_marker) (pem_start, pem_end) = _markers(pem_marker)
b64 = base64.encodestring(contents).replace(b('\n'), b('')) b64 = base64.standard_b64encode(contents).replace(b('\n'), b(''))
pem_lines = [pem_start] pem_lines = [pem_start]
for block_start in range(0, len(b64), 64): for block_start in range(0, len(b64), 64):
@ -117,4 +123,3 @@ def save_pem(contents, pem_marker):
pem_lines.append(b('')) pem_lines.append(b(''))
return b('\n').join(pem_lines) return b('\n').join(pem_lines)

View file

@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
'''Functions for PKCS#1 version 1.5 encryption and signing """Functions for PKCS#1 version 1.5 encryption and signing
This module implements certain functionality from PKCS#1 version 1.5. For a This module implements certain functionality from PKCS#1 version 1.5. For a
very clear example, read http://www.di-mgt.com.au/rsa_alg.html#pkcs1schemes very clear example, read http://www.di-mgt.com.au/rsa_alg.html#pkcs1schemes
@ -22,17 +22,17 @@ very clear example, read http://www.di-mgt.com.au/rsa_alg.html#pkcs1schemes
At least 8 bytes of random padding is used when encrypting a message. This makes At least 8 bytes of random padding is used when encrypting a message. This makes
these methods much more secure than the ones in the ``rsa`` module. these methods much more secure than the ones in the ``rsa`` module.
WARNING: this module leaks information when decryption or verification fails. WARNING: this module leaks information when decryption fails. The exceptions
The exceptions that are raised contain the Python traceback information, which that are raised contain the Python traceback information, which can be used to
can be used to deduce where in the process the failure occurred. DO NOT PASS deduce where in the process the failure occurred. DO NOT PASS SUCH INFORMATION
SUCH INFORMATION to your users. to your users.
''' """
import hashlib import hashlib
import os import os
from rsa._compat import b from rsa._compat import b
from rsa import common, transform, core, varblock from rsa import common, transform, core
# ASN.1 codes that describe the hash algorithm used. # ASN.1 codes that describe the hash algorithm used.
HASH_ASN1 = { HASH_ASN1 = {
@ -51,29 +51,33 @@ HASH_METHODS = {
'SHA-512': hashlib.sha512, 'SHA-512': hashlib.sha512,
} }
class CryptoError(Exception): class CryptoError(Exception):
'''Base class for all exceptions in this module.''' """Base class for all exceptions in this module."""
class DecryptionError(CryptoError): class DecryptionError(CryptoError):
'''Raised when decryption fails.''' """Raised when decryption fails."""
class VerificationError(CryptoError): class VerificationError(CryptoError):
'''Raised when verification fails.''' """Raised when verification fails."""
def _pad_for_encryption(message, target_length): def _pad_for_encryption(message, target_length):
r'''Pads the message for encryption, returning the padded message. r"""Pads the message for encryption, returning the padded message.
:return: 00 02 RANDOM_DATA 00 MESSAGE :return: 00 02 RANDOM_DATA 00 MESSAGE
>>> block = _pad_for_encryption('hello', 16) >>> block = _pad_for_encryption(b'hello', 16)
>>> len(block) >>> len(block)
16 16
>>> block[0:2] >>> block[0:2]
'\x00\x02' b'\x00\x02'
>>> block[-6:] >>> block[-6:]
'\x00hello' b'\x00hello'
''' """
max_msglength = target_length - 11 max_msglength = target_length - 11
msglength = len(message) msglength = len(message)
@ -107,23 +111,23 @@ def _pad_for_encryption(message, target_length):
def _pad_for_signing(message, target_length): def _pad_for_signing(message, target_length):
r'''Pads the message for signing, returning the padded message. r"""Pads the message for signing, returning the padded message.
The padding is always a repetition of FF bytes. The padding is always a repetition of FF bytes.
:return: 00 01 PADDING 00 MESSAGE :return: 00 01 PADDING 00 MESSAGE
>>> block = _pad_for_signing('hello', 16) >>> block = _pad_for_signing(b'hello', 16)
>>> len(block) >>> len(block)
16 16
>>> block[0:2] >>> block[0:2]
'\x00\x01' b'\x00\x01'
>>> block[-6:] >>> block[-6:]
'\x00hello' b'\x00hello'
>>> block[2:-6] >>> block[2:-6]
'\xff\xff\xff\xff\xff\xff\xff\xff' b'\xff\xff\xff\xff\xff\xff\xff\xff'
''' """
max_msglength = target_length - 11 max_msglength = target_length - 11
msglength = len(message) msglength = len(message)
@ -141,7 +145,7 @@ def _pad_for_signing(message, target_length):
def encrypt(message, pub_key): def encrypt(message, pub_key):
'''Encrypts the given message using PKCS#1 v1.5 """Encrypts the given message using PKCS#1 v1.5
:param message: the message to encrypt. Must be a byte string no longer than :param message: the message to encrypt. Must be a byte string no longer than
``k-11`` bytes, where ``k`` is the number of bytes needed to encode ``k-11`` bytes, where ``k`` is the number of bytes needed to encode
@ -152,7 +156,7 @@ def encrypt(message, pub_key):
>>> from rsa import key, common >>> from rsa import key, common
>>> (pub_key, priv_key) = key.newkeys(256) >>> (pub_key, priv_key) = key.newkeys(256)
>>> message = 'hello' >>> message = b'hello'
>>> crypto = encrypt(message, pub_key) >>> crypto = encrypt(message, pub_key)
The crypto text should be just as long as the public key 'n' component: The crypto text should be just as long as the public key 'n' component:
@ -160,7 +164,7 @@ def encrypt(message, pub_key):
>>> len(crypto) == common.byte_size(pub_key.n) >>> len(crypto) == common.byte_size(pub_key.n)
True True
''' """
keylength = common.byte_size(pub_key.n) keylength = common.byte_size(pub_key.n)
padded = _pad_for_encryption(message, keylength) padded = _pad_for_encryption(message, keylength)
@ -171,8 +175,9 @@ def encrypt(message, pub_key):
return block return block
def decrypt(crypto, priv_key): def decrypt(crypto, priv_key):
r'''Decrypts the given message using PKCS#1 v1.5 r"""Decrypts the given message using PKCS#1 v1.5
The decryption is considered 'failed' when the resulting cleartext doesn't The decryption is considered 'failed' when the resulting cleartext doesn't
start with the bytes 00 02, or when the 00 byte between the padding and start with the bytes 00 02, or when the 00 byte between the padding and
@ -190,15 +195,15 @@ def decrypt(crypto, priv_key):
It works with strings: It works with strings:
>>> crypto = encrypt('hello', pub_key) >>> crypto = encrypt(b'hello', pub_key)
>>> decrypt(crypto, priv_key) >>> decrypt(crypto, priv_key)
'hello' b'hello'
And with binary data: And with binary data:
>>> crypto = encrypt('\x00\x00\x00\x00\x01', pub_key) >>> crypto = encrypt(b'\x00\x00\x00\x00\x01', pub_key)
>>> decrypt(crypto, priv_key) >>> decrypt(crypto, priv_key)
'\x00\x00\x00\x00\x01' b'\x00\x00\x00\x00\x01'
Altering the encrypted information will *likely* cause a Altering the encrypted information will *likely* cause a
:py:class:`rsa.pkcs1.DecryptionError`. If you want to be *sure*, use :py:class:`rsa.pkcs1.DecryptionError`. If you want to be *sure*, use
@ -213,18 +218,18 @@ def decrypt(crypto, priv_key):
It's only a tiny bit of information, but every bit makes cracking the It's only a tiny bit of information, but every bit makes cracking the
keys easier. keys easier.
>>> crypto = encrypt('hello', pub_key) >>> crypto = encrypt(b'hello', pub_key)
>>> crypto = crypto[0:5] + 'X' + crypto[6:] # change a byte >>> crypto = crypto[0:5] + b'X' + crypto[6:] # change a byte
>>> decrypt(crypto, priv_key) >>> decrypt(crypto, priv_key)
Traceback (most recent call last): Traceback (most recent call last):
... ...
DecryptionError: Decryption failed rsa.pkcs1.DecryptionError: Decryption failed
''' """
blocksize = common.byte_size(priv_key.n) blocksize = common.byte_size(priv_key.n)
encrypted = transform.bytes2int(crypto) encrypted = transform.bytes2int(crypto)
decrypted = core.decrypt_int(encrypted, priv_key.d, priv_key.n) decrypted = priv_key.blinded_decrypt(encrypted)
cleartext = transform.int2bytes(decrypted, blocksize) cleartext = transform.int2bytes(decrypted, blocksize)
# If we can't find the cleartext marker, decryption failed. # If we can't find the cleartext marker, decryption failed.
@ -239,8 +244,9 @@ def decrypt(crypto, priv_key):
return cleartext[sep_idx + 1:] return cleartext[sep_idx + 1:]
def sign(message, priv_key, hash): def sign(message, priv_key, hash):
'''Signs the message with the private key. """Signs the message with the private key.
Hashes the message, then signs the hash with the given key. This is known Hashes the message, then signs the hash with the given key. This is known
as a "detached signature", because the message itself isn't altered. as a "detached signature", because the message itself isn't altered.
@ -255,7 +261,7 @@ def sign(message, priv_key, hash):
:raise OverflowError: if the private key is too small to contain the :raise OverflowError: if the private key is too small to contain the
requested hash. requested hash.
''' """
# Get the ASN1 code for this hash method # Get the ASN1 code for this hash method
if hash not in HASH_ASN1: if hash not in HASH_ASN1:
@ -271,13 +277,14 @@ def sign(message, priv_key, hash):
padded = _pad_for_signing(cleartext, keylength) padded = _pad_for_signing(cleartext, keylength)
payload = transform.bytes2int(padded) payload = transform.bytes2int(padded)
encrypted = core.encrypt_int(payload, priv_key.d, priv_key.n) encrypted = priv_key.blinded_encrypt(payload)
block = transform.int2bytes(encrypted, keylength) block = transform.int2bytes(encrypted, keylength)
return block return block
def verify(message, signature, pub_key): def verify(message, signature, pub_key):
'''Verifies that the signature matches the message. """Verifies that the signature matches the message.
The hash method is detected automatically from the signature. The hash method is detected automatically from the signature.
@ -288,43 +295,30 @@ def verify(message, signature, pub_key):
:param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message. :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
:raise VerificationError: when the signature doesn't match the message. :raise VerificationError: when the signature doesn't match the message.
.. warning:: """
Never display the stack trace of a keylength = common.byte_size(pub_key.n)
:py:class:`rsa.pkcs1.VerificationError` exception. It shows where in
the code the exception occurred, and thus leaks information about the
key. It's only a tiny bit of information, but every bit makes cracking
the keys easier.
'''
blocksize = common.byte_size(pub_key.n)
encrypted = transform.bytes2int(signature) encrypted = transform.bytes2int(signature)
decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n) decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)
clearsig = transform.int2bytes(decrypted, blocksize) clearsig = transform.int2bytes(decrypted, keylength)
# If we can't find the signature marker, verification failed. # Get the hash method
if clearsig[0:2] != b('\x00\x01'): method_name = _find_method_hash(clearsig)
raise VerificationError('Verification failed')
# Find the 00 separator between the padding and the payload
try:
sep_idx = clearsig.index(b('\x00'), 2)
except ValueError:
raise VerificationError('Verification failed')
# Get the hash and the hash method
(method_name, signature_hash) = _find_method_hash(clearsig[sep_idx+1:])
message_hash = _hash(message, method_name) message_hash = _hash(message, method_name)
# Compare the real hash to the hash in the signature # Reconstruct the expected padded hash
if message_hash != signature_hash: cleartext = HASH_ASN1[method_name] + message_hash
expected = _pad_for_signing(cleartext, keylength)
# Compare with the signed one
if expected != clearsig:
raise VerificationError('Verification failed') raise VerificationError('Verification failed')
return True return True
def _hash(message, method_name): def _hash(message, method_name):
'''Returns the message digest. """Returns the message digest.
:param message: the signed message. Can be an 8-bit string or a file-like :param message: the signed message. Can be an 8-bit string or a file-like
object. If ``message`` has a ``read()`` method, it is assumed to be a object. If ``message`` has a ``read()`` method, it is assumed to be a
@ -332,7 +326,7 @@ def _hash(message, method_name):
:param method_name: the hash method, must be a key of :param method_name: the hash method, must be a key of
:py:const:`HASH_METHODS`. :py:const:`HASH_METHODS`.
''' """
if method_name not in HASH_METHODS: if method_name not in HASH_METHODS:
raise ValueError('Invalid hash method: %s' % method_name) raise ValueError('Invalid hash method: %s' % method_name)
@ -341,6 +335,9 @@ def _hash(message, method_name):
hasher = method() hasher = method()
if hasattr(message, 'read') and hasattr(message.read, '__call__'): if hasattr(message, 'read') and hasattr(message.read, '__call__'):
# Late import to prevent DeprecationWarnings.
from . import varblock
# read as 1K blocks # read as 1K blocks
for block in varblock.yield_fixedblocks(message, 1024): for block in varblock.yield_fixedblocks(message, 1024):
hasher.update(block) hasher.update(block)
@ -351,24 +348,17 @@ def _hash(message, method_name):
return hasher.digest() return hasher.digest()
def _find_method_hash(method_hash): def _find_method_hash(clearsig):
'''Finds the hash method and the hash itself. """Finds the hash method.
:param method_hash: ASN1 code for the hash method concatenated with the
hash itself.
:return: tuple (method, hash) where ``method`` is the used hash method, and
``hash`` is the hash itself.
:param clearsig: full padded ASN1 and hash.
:return: the used hash method.
:raise VerificationFailed: when the hash method cannot be found :raise VerificationFailed: when the hash method cannot be found
"""
'''
for (hashname, asn1code) in HASH_ASN1.items(): for (hashname, asn1code) in HASH_ASN1.items():
if not method_hash.startswith(asn1code): if asn1code in clearsig:
continue return hashname
return (hashname, method_hash[len(asn1code):])
raise VerificationError('Verification failed') raise VerificationError('Verification failed')

View file

@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
@ -14,102 +14,115 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
'''Numerical functions related to primes. """Numerical functions related to primes.
Implementation based on the book Algorithm Design by Michael T. Goodrich and Implementation based on the book Algorithm Design by Michael T. Goodrich and
Roberto Tamassia, 2002. Roberto Tamassia, 2002.
''' """
__all__ = [ 'getprime', 'are_relatively_prime']
import rsa.randnum import rsa.randnum
__all__ = ['getprime', 'are_relatively_prime']
def gcd(p, q): def gcd(p, q):
'''Returns the greatest common divisor of p and q """Returns the greatest common divisor of p and q
>>> gcd(48, 180) >>> gcd(48, 180)
12 12
''' """
while q != 0: while q != 0:
if p < q: (p,q) = (q,p)
(p, q) = (q, p % q) (p, q) = (q, p % q)
return p return p
def jacobi(a, b): def miller_rabin_primality_testing(n, k):
'''Calculates the value of the Jacobi symbol (a/b) where both a and b are """Calculates whether n is composite (which is always correct) or prime
positive integers, and b is odd (which theoretically is incorrect with error probability 4**-k), by
applying Miller-Rabin primality testing.
:returns: -1, 0 or 1 For reference and implementation example, see:
''' https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test
assert a > 0 :param n: Integer to be tested for primality.
assert b > 0 :type n: int
:param k: Number of rounds (witnesses) of Miller-Rabin testing.
:type k: int
:return: False if the number is composite, True if it's probably prime.
:rtype: bool
"""
if a == 0: return 0 # prevent potential infinite loop when d = 0
result = 1 if n < 2:
while a > 1: return False
if a & 1:
if ((a-1)*(b-1) >> 2) & 1:
result = -result
a, b = b % a, a
else:
if (((b * b) - 1) >> 3) & 1:
result = -result
a >>= 1
if a == 0: return 0
return result
def jacobi_witness(x, n): # Decompose (n - 1) to write it as (2 ** r) * d
'''Returns False if n is an Euler pseudo-prime with base x, and # While d is even, divide it by 2 and increase the exponent.
True otherwise. d = n - 1
''' r = 0
j = jacobi(x, n) % n while not (d & 1):
r += 1
f = pow(x, n >> 1, n) d >>= 1
if j == f: return False
return True
def randomized_primality_testing(n, k):
'''Calculates whether n is composite (which is always correct) or
prime (which is incorrect with error probability 2**-k)
Returns False if the number is composite, and True if it's
probably prime.
'''
# 50% of Jacobi-witnesses can report compositness of non-prime numbers
# The implemented algorithm using the Jacobi witness function has error
# probability q <= 0.5, according to Goodrich et. al
#
# q = 0.5
# t = int(math.ceil(k / log(1 / q, 2)))
# So t = k / log(2, 2) = k / 1 = k
# this means we can use range(k) rather than range(t)
# Test k witnesses.
for _ in range(k): for _ in range(k):
x = rsa.randnum.randint(n-1) # Generate random integer a, where 2 <= a <= (n - 2)
if jacobi_witness(x, n): return False a = rsa.randnum.randint(n - 4) + 2
x = pow(a, d, n)
if x == 1 or x == n - 1:
continue
for _ in range(r - 1):
x = pow(x, 2, n)
if x == 1:
# n is composite.
return False
if x == n - 1:
# Exit inner loop and continue with next witness.
break
else:
# If loop doesn't break, n is composite.
return False
return True return True
def is_prime(number): def is_prime(number):
'''Returns True if the number is prime, and False otherwise. """Returns True if the number is prime, and False otherwise.
>>> is_prime(2)
True
>>> is_prime(42) >>> is_prime(42)
False False
>>> is_prime(41) >>> is_prime(41)
True True
''' >>> [x for x in range(901, 1000) if is_prime(x)]
[907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997]
"""
# Check for small numbers.
if number < 10:
return number in [2, 3, 5, 7]
# Check for even numbers.
if not (number & 1):
return False
# According to NIST FIPS 186-4, Appendix C, Table C.3, minimum number of
# rounds of M-R testing, using an error probability of 2 ** (-100), for
# different p, q bitsizes are:
# * p, q bitsize: 512; rounds: 7
# * p, q bitsize: 1024; rounds: 4
# * p, q bitsize: 1536; rounds: 3
# See: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
return miller_rabin_primality_testing(number, 7)
return randomized_primality_testing(number, 6)
def getprime(nbits): def getprime(nbits):
'''Returns a prime number that can be stored in 'nbits' bits. """Returns a prime number that can be stored in 'nbits' bits.
>>> p = getprime(128) >>> p = getprime(128)
>>> is_prime(p-1) >>> is_prime(p-1)
@ -122,14 +135,12 @@ def getprime(nbits):
>>> from rsa import common >>> from rsa import common
>>> common.bit_size(p) == 128 >>> common.bit_size(p) == 128
True True
"""
''' assert nbits > 3 # the loop wil hang on too small numbers
while True: while True:
integer = rsa.randnum.read_random_int(nbits) integer = rsa.randnum.read_random_odd_int(nbits)
# Make sure it's odd
integer |= 1
# Test for primeness # Test for primeness
if is_prime(integer): if is_prime(integer):
@ -139,17 +150,18 @@ def getprime(nbits):
def are_relatively_prime(a, b): def are_relatively_prime(a, b):
'''Returns True if a and b are relatively prime, and False if they """Returns True if a and b are relatively prime, and False if they
are not. are not.
>>> are_relatively_prime(2, 3) >>> are_relatively_prime(2, 3)
1 True
>>> are_relatively_prime(2, 4) >>> are_relatively_prime(2, 4)
0 False
''' """
d = gcd(a, b) d = gcd(a, b)
return (d == 1) return d == 1
if __name__ == '__main__': if __name__ == '__main__':
print('Running doctests 1000x or until failure') print('Running doctests 1000x or until failure')

View file

@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
'''Functions for generating random numbers.''' """Functions for generating random numbers."""
# Source inspired by code by Yesudeep Mangalapilly <yesudeep@gmail.com> # Source inspired by code by Yesudeep Mangalapilly <yesudeep@gmail.com>
@ -23,12 +23,13 @@ import os
from rsa import common, transform from rsa import common, transform
from rsa._compat import byte from rsa._compat import byte
def read_random_bits(nbits): def read_random_bits(nbits):
'''Reads 'nbits' random bits. """Reads 'nbits' random bits.
If nbits isn't a whole number of bytes, an extra byte will be appended with If nbits isn't a whole number of bytes, an extra byte will be appended with
only the lower bits set. only the lower bits set.
''' """
nbytes, rbits = divmod(nbits, 8) nbytes, rbits = divmod(nbits, 8)
@ -45,8 +46,8 @@ def read_random_bits(nbits):
def read_random_int(nbits): def read_random_int(nbits):
'''Reads a random integer of approximately nbits bits. """Reads a random integer of approximately nbits bits.
''' """
randomdata = read_random_bits(nbits) randomdata = read_random_bits(nbits)
value = transform.bytes2int(randomdata) value = transform.bytes2int(randomdata)
@ -57,13 +58,27 @@ def read_random_int(nbits):
return value return value
def read_random_odd_int(nbits):
"""Reads a random odd integer of approximately nbits bits.
>>> read_random_odd_int(512) & 1
1
"""
value = read_random_int(nbits)
# Make sure it's odd
return value | 1
def randint(maxvalue): def randint(maxvalue):
'''Returns a random integer x with 1 <= x <= maxvalue """Returns a random integer x with 1 <= x <= maxvalue
May take a very long time in specific situations. If maxvalue needs N bits May take a very long time in specific situations. If maxvalue needs N bits
to store, the closer maxvalue is to (2 ** N) - 1, the faster this function to store, the closer maxvalue is to (2 ** N) - 1, the faster this function
is. is.
''' """
bit_size = common.bit_size(maxvalue) bit_size = common.bit_size(maxvalue)
@ -81,5 +96,3 @@ def randint(maxvalue):
tries += 1 tries += 1
return value return value

View file

@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
@ -14,10 +14,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
'''Data transformation functions. """Data transformation functions.
From bytes to a number, number to bytes, etc. From bytes to a number, number to bytes, etc.
''' """
from __future__ import absolute_import from __future__ import absolute_import
@ -26,6 +26,7 @@ try:
# Using psyco (if available) cuts down the execution time on Python 2.5 # Using psyco (if available) cuts down the execution time on Python 2.5
# at least by half. # at least by half.
import psyco import psyco
psyco.full() psyco.full()
except ImportError: except ImportError:
pass pass
@ -37,32 +38,32 @@ from rsa._compat import is_integer, b, byte, get_word_alignment, ZERO_BYTE, EMPT
def bytes2int(raw_bytes): def bytes2int(raw_bytes):
r'''Converts a list of bytes or an 8-bit string to an integer. r"""Converts a list of bytes or an 8-bit string to an integer.
When using unicode strings, encode it to some encoding like UTF8 first. When using unicode strings, encode it to some encoding like UTF8 first.
>>> (((128 * 256) + 64) * 256) + 15 >>> (((128 * 256) + 64) * 256) + 15
8405007 8405007
>>> bytes2int('\x80@\x0f') >>> bytes2int(b'\x80@\x0f')
8405007 8405007
''' """
return int(binascii.hexlify(raw_bytes), 16) return int(binascii.hexlify(raw_bytes), 16)
def _int2bytes(number, block_size=None): def _int2bytes(number, block_size=None):
r'''Converts a number to a string of bytes. r"""Converts a number to a string of bytes.
Usage:: Usage::
>>> _int2bytes(123456789) >>> _int2bytes(123456789)
'\x07[\xcd\x15' b'\x07[\xcd\x15'
>>> bytes2int(_int2bytes(123456789)) >>> bytes2int(_int2bytes(123456789))
123456789 123456789
>>> _int2bytes(123456789, 6) >>> _int2bytes(123456789, 6)
'\x00\x00\x07[\xcd\x15' b'\x00\x00\x07[\xcd\x15'
>>> bytes2int(_int2bytes(123456789, 128)) >>> bytes2int(_int2bytes(123456789, 128))
123456789 123456789
@ -78,7 +79,8 @@ def _int2bytes(number, block_size=None):
@throws OverflowError when block_size is given and the number takes up more @throws OverflowError when block_size is given and the number takes up more
bytes than fit into the block. bytes than fit into the block.
''' """
# Type checking # Type checking
if not is_integer(number): if not is_integer(number):
raise TypeError("You must pass an integer for 'number', not %s" % raise TypeError("You must pass an integer for 'number', not %s" %
@ -116,7 +118,7 @@ def _int2bytes(number, block_size=None):
def bytes_leading(raw_bytes, needle=ZERO_BYTE): def bytes_leading(raw_bytes, needle=ZERO_BYTE):
''' """
Finds the number of prefixed byte occurrences in the haystack. Finds the number of prefixed byte occurrences in the haystack.
Useful when you want to deal with padding. Useful when you want to deal with padding.
@ -127,7 +129,8 @@ def bytes_leading(raw_bytes, needle=ZERO_BYTE):
The byte to count. Default \000. The byte to count. Default \000.
:returns: :returns:
The number of leading needle bytes. The number of leading needle bytes.
''' """
leading = 0 leading = 0
# Indexing keeps compatibility between Python 2.x and Python 3.x # Indexing keeps compatibility between Python 2.x and Python 3.x
_byte = needle[0] _byte = needle[0]
@ -140,7 +143,7 @@ def bytes_leading(raw_bytes, needle=ZERO_BYTE):
def int2bytes(number, fill_size=None, chunk_size=None, overflow=False): def int2bytes(number, fill_size=None, chunk_size=None, overflow=False):
''' """
Convert an unsigned integer to bytes (base-256 representation):: Convert an unsigned integer to bytes (base-256 representation)::
Does not preserve leading zeros if you don't specify a chunk size or Does not preserve leading zeros if you don't specify a chunk size or
@ -172,7 +175,8 @@ def int2bytes(number, fill_size=None, chunk_size=None, overflow=False):
bytes than fit into the block. This requires the ``overflow`` bytes than fit into the block. This requires the ``overflow``
argument to this function to be set to ``False`` otherwise, no argument to this function to be set to ``False`` otherwise, no
error will be raised. error will be raised.
''' """
if number < 0: if number < 0:
raise ValueError("Number must be an unsigned integer: %d" % number) raise ValueError("Number must be an unsigned integer: %d" % number)
@ -216,5 +220,5 @@ def int2bytes(number, fill_size=None, chunk_size=None, overflow=False):
if __name__ == '__main__': if __name__ == '__main__':
import doctest import doctest
doctest.testmod()
doctest.testmod()

View file

@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
@ -14,7 +14,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
'''Utility functions.''' """Utility functions."""
from __future__ import with_statement, print_function from __future__ import with_statement, print_function
@ -23,8 +23,9 @@ from optparse import OptionParser
import rsa.key import rsa.key
def private_to_public(): def private_to_public():
'''Reads a private key and outputs the corresponding public key.''' """Reads a private key and outputs the corresponding public key."""
# Parse the CLI options # Parse the CLI options
parser = OptionParser(usage='usage: %prog [options]', parser = OptionParser(usage='usage: %prog [options]',
@ -49,7 +50,7 @@ def private_to_public():
# Read the input data # Read the input data
if cli.infilename: if cli.infilename:
print('Reading private key from %s in %s format' % \ print('Reading private key from %s in %s format' %
(cli.infilename, cli.inform), file=sys.stderr) (cli.infilename, cli.inform), file=sys.stderr)
with open(cli.infilename, 'rb') as infile: with open(cli.infilename, 'rb') as infile:
in_data = infile.read() in_data = infile.read()
@ -60,7 +61,6 @@ def private_to_public():
assert type(in_data) == bytes, type(in_data) assert type(in_data) == bytes, type(in_data)
# Take the public fields and create a public key # Take the public fields and create a public key
priv_key = rsa.key.PrivateKey.load_pkcs1(in_data, cli.inform) priv_key = rsa.key.PrivateKey.load_pkcs1(in_data, cli.inform)
pub_key = rsa.key.PublicKey(priv_key.n, priv_key.e) pub_key = rsa.key.PublicKey(priv_key.n, priv_key.e)
@ -69,7 +69,7 @@ def private_to_public():
out_data = pub_key.save_pkcs1(cli.outform) out_data = pub_key.save_pkcs1(cli.outform)
if cli.outfilename: if cli.outfilename:
print('Writing public key to %s in %s format' % \ print('Writing public key to %s in %s format' %
(cli.outfilename, cli.outform), file=sys.stderr) (cli.outfilename, cli.outform), file=sys.stderr)
with open(cli.outfilename, 'wb') as outfile: with open(cli.outfilename, 'wb') as outfile:
outfile.write(out_data) outfile.write(out_data)
@ -77,5 +77,3 @@ def private_to_public():
print('Writing public key to stdout in %s format' % cli.outform, print('Writing public key to stdout in %s format' % cli.outform,
file=sys.stderr) file=sys.stderr)
sys.stdout.write(out_data.decode('ascii')) sys.stdout.write(out_data.decode('ascii'))

View file

@ -6,7 +6,7 @@
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # https://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
@ -14,7 +14,25 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
'''VARBLOCK file support """VARBLOCK file support
.. deprecated:: 3.4
The VARBLOCK format is NOT recommended for general use, has been deprecated since
Python-RSA 3.4, and will be removed in a future release. It's vulnerable to a
number of attacks:
1. decrypt/encrypt_bigfile() does not implement `Authenticated encryption`_ nor
uses MACs to verify messages before decrypting public key encrypted messages.
2. decrypt/encrypt_bigfile() does not use hybrid encryption (it uses plain RSA)
and has no method for chaining, so block reordering is possible.
See `issue #19 on Github`_ for more information.
.. _Authenticated encryption: https://en.wikipedia.org/wiki/Authenticated_encryption
.. _issue #19 on Github: https://github.com/sybrenstuvel/python-rsa/issues/13
The VARBLOCK file format is as follows, where || denotes byte concatenation: The VARBLOCK file format is as follows, where || denotes byte concatenation:
@ -31,25 +49,32 @@ The VARBLOCK file format is as follows, where || denotes byte concatenation:
This file format is called the VARBLOCK format, in line with the varint format This file format is called the VARBLOCK format, in line with the varint format
used to denote the block sizes. used to denote the block sizes.
''' """
import warnings
from rsa._compat import byte, b from rsa._compat import byte, b
ZERO_BYTE = b('\x00') ZERO_BYTE = b('\x00')
VARBLOCK_VERSION = 1 VARBLOCK_VERSION = 1
warnings.warn("The 'rsa.varblock' module was deprecated in Python-RSA version "
"3.4 due to security issues in the VARBLOCK format. See "
"https://github.com/sybrenstuvel/python-rsa/issues/13 for more information.",
DeprecationWarning)
def read_varint(infile): def read_varint(infile):
'''Reads a varint from the file. """Reads a varint from the file.
When the first byte to be read indicates EOF, (0, 0) is returned. When an When the first byte to be read indicates EOF, (0, 0) is returned. When an
EOF occurs when at least one byte has been read, an EOFError exception is EOF occurs when at least one byte has been read, an EOFError exception is
raised. raised.
@param infile: the file-like object to read from. It should have a read() :param infile: the file-like object to read from. It should have a read()
method. method.
@returns (varint, length), the read varint and the number of read bytes. :returns: (varint, length), the read varint and the number of read bytes.
''' """
varint = 0 varint = 0
read_bytes = 0 read_bytes = 0
@ -58,7 +83,7 @@ def read_varint(infile):
char = infile.read(1) char = infile.read(1)
if len(char) == 0: if len(char) == 0:
if read_bytes == 0: if read_bytes == 0:
return (0, 0) return 0, 0
raise EOFError('EOF while reading varint, value is %i so far' % raise EOFError('EOF while reading varint, value is %i so far' %
varint) varint)
@ -68,16 +93,16 @@ def read_varint(infile):
read_bytes += 1 read_bytes += 1
if not byte & 0x80: if not byte & 0x80:
return (varint, read_bytes) return varint, read_bytes
def write_varint(outfile, value): def write_varint(outfile, value):
'''Writes a varint to a file. """Writes a varint to a file.
@param outfile: the file-like object to write to. It should have a write() :param outfile: the file-like object to write to. It should have a write()
method. method.
@returns the number of written bytes. :returns: the number of written bytes.
''' """
# there is a big difference between 'write the value 0' (this case) and # there is a big difference between 'write the value 0' (this case) and
# 'there is nothing left to write' (the false-case of the while loop) # 'there is nothing left to write' (the false-case of the while loop)
@ -89,7 +114,7 @@ def write_varint(outfile, value):
written_bytes = 0 written_bytes = 0
while value > 0: while value > 0:
to_write = value & 0x7f to_write = value & 0x7f
value = value >> 7 value >>= 7
if value > 0: if value > 0:
to_write |= 0x80 to_write |= 0x80
@ -101,12 +126,12 @@ def write_varint(outfile, value):
def yield_varblocks(infile): def yield_varblocks(infile):
'''Generator, yields each block in the input file. """Generator, yields each block in the input file.
@param infile: file to read, is expected to have the VARBLOCK format as :param infile: file to read, is expected to have the VARBLOCK format as
described in the module's docstring. described in the module's docstring.
@yields the contents of each block. @yields the contents of each block.
''' """
# Check the version number # Check the version number
first_char = infile.read(1) first_char = infile.read(1)
@ -135,11 +160,11 @@ def yield_varblocks(infile):
def yield_fixedblocks(infile, blocksize): def yield_fixedblocks(infile, blocksize):
'''Generator, yields each block of ``blocksize`` bytes in the input file. """Generator, yields each block of ``blocksize`` bytes in the input file.
:param infile: file to read and separate in blocks. :param infile: file to read and separate in blocks.
:returns: a generator that yields the contents of each block :returns: a generator that yields the contents of each block
''' """
while True: while True:
block = infile.read(blocksize) block = infile.read(blocksize)
@ -152,4 +177,3 @@ def yield_fixedblocks(infile, blocksize):
if read_bytes < blocksize: if read_bytes < blocksize:
break break