diff --git a/rsa/__init__.py b/rsa/__init__.py deleted file mode 100644 index 8fb5e00a..00000000 --- a/rsa/__init__.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""RSA module - -Module for calculating large primes, and RSA encryption, decryption, signing -and verification. Includes generating public and private keys. - -WARNING: this implementation does not use random padding, compression of the -cleartext input to prevent repetitions, or other common security improvements. -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__ = "2012-06-17" -__version__ = '3.1.1' - -from rsa.key import newkeys, PrivateKey, PublicKey -from rsa.pkcs1 import encrypt, decrypt, sign, verify, DecryptionError, \ - VerificationError - -# Do doctest if we're run directly -if __name__ == "__main__": - import doctest - doctest.testmod() - -__all__ = ["newkeys", "encrypt", "decrypt", "sign", "verify", 'PublicKey', - 'PrivateKey', 'DecryptionError', 'VerificationError'] - diff --git a/rsa/_compat.py b/rsa/_compat.py deleted file mode 100644 index 3c4eb81b..00000000 --- a/rsa/_compat.py +++ /dev/null @@ -1,160 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Python compatibility wrappers.""" - - -from __future__ import absolute_import - -import sys -from struct import pack - -try: - MAX_INT = sys.maxsize -except AttributeError: - MAX_INT = sys.maxint - -MAX_INT64 = (1 << 63) - 1 -MAX_INT32 = (1 << 31) - 1 -MAX_INT16 = (1 << 15) - 1 - -# Determine the word size of the processor. -if MAX_INT == MAX_INT64: - # 64-bit processor. - MACHINE_WORD_SIZE = 64 -elif MAX_INT == MAX_INT32: - # 32-bit processor. - MACHINE_WORD_SIZE = 32 -else: - # Else we just assume 64-bit processor keeping up with modern times. - MACHINE_WORD_SIZE = 64 - - -try: - # < Python3 - unicode_type = unicode - have_python3 = False -except NameError: - # Python3. - unicode_type = str - have_python3 = True - -# Fake byte literals. -if str is unicode_type: - def byte_literal(s): - return s.encode('latin1') -else: - def byte_literal(s): - return s - -# ``long`` is no more. Do type detection using this instead. -try: - integer_types = (int, long) -except NameError: - integer_types = (int,) - -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. -ZERO_BYTE = b('\x00') -EMPTY_BYTE = b('') - - -def is_bytes(obj): - """ - Determines whether the given value is a byte string. - - :param obj: - The value to test. - :returns: - ``True`` if ``value`` is a byte string; ``False`` otherwise. - """ - return isinstance(obj, bytes_type) - - -def is_integer(obj): - """ - Determines whether the given value is an integer. - - :param obj: - The value to test. - :returns: - ``True`` if ``value`` is an integer; ``False`` otherwise. - """ - return isinstance(obj, integer_types) - - -def byte(num): - """ - Converts a number between 0 and 255 (both inclusive) to a base-256 (byte) - representation. - - Use it as a replacement for ``chr`` where you are expecting a byte - because this will work on all current versions of Python:: - - :param num: - An unsigned integer between 0 and 255 (both inclusive). - :returns: - A single byte. - """ - return pack("B", num) - - -def get_word_alignment(num, force_arch=64, - _machine_word_size=MACHINE_WORD_SIZE): - """ - Returns alignment details for the given number based on the platform - Python is running on. - - :param num: - Unsigned integral number. - :param force_arch: - If you don't want to use 64-bit unsigned chunks, set this to - anything other than 64. 32-bit chunks will be preferred then. - Default 64 will be used when on a 64-bit machine. - :param _machine_word_size: - (Internal) The machine word size used for alignment. - :returns: - 4-tuple:: - - (word_bits, word_bytes, - max_uint, packing_format_type) - """ - max_uint64 = 0xffffffffffffffff - max_uint32 = 0xffffffff - max_uint16 = 0xffff - max_uint8 = 0xff - - if force_arch == 64 and _machine_word_size >= 64 and num > max_uint32: - # 64-bit unsigned integer. - return 64, 8, max_uint64, "Q" - elif num > max_uint16: - # 32-bit unsigned integer - return 32, 4, max_uint32, "L" - elif num > max_uint8: - # 16-bit unsigned integer. - return 16, 2, max_uint16, "H" - else: - # 8-bit unsigned integer. - return 8, 1, max_uint8, "B" diff --git a/rsa/_version133.py b/rsa/_version133.py deleted file mode 100644 index 230a03c8..00000000 --- a/rsa/_version133.py +++ /dev/null @@ -1,442 +0,0 @@ -"""RSA module -pri = k[1] //Private part of keys d,p,q - -Module for calculating large primes, and RSA encryption, decryption, -signing and verification. Includes generating public and private keys. - -WARNING: this code implements the mathematics of RSA. It is not suitable for -real-world secure cryptography purposes. It has not been reviewed by a security -expert. It does not include padding of data. There are many ways in which the -output of this module, when used without any modification, can be sucessfully -attacked. -""" - -__author__ = "Sybren Stuvel, Marloes de Boer and Ivo Tamboer" -__date__ = "2010-02-05" -__version__ = '1.3.3' - -# NOTE: Python's modulo can return negative numbers. We compensate for -# this behaviour using the abs() function - -from cPickle import dumps, loads -import base64 -import math -import os -import random -import sys -import types -import zlib - -from rsa._compat import byte - -# Display a warning that this insecure version is imported. -import warnings -warnings.warn('Insecure version of the RSA module is imported as %s, be careful' - % __name__) - -def gcd(p, q): - """Returns the greatest common divisor of p and q - - - >>> gcd(42, 6) - 6 - """ - if p>> (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): - raise TypeError("You must pass a string or a list") - - # Convert byte stream to integer - integer = 0 - for byte in bytes: - integer *= 256 - if type(byte) is types.StringType: byte = ord(byte) - integer += byte - - return integer - -def int2bytes(number): - """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): - raise TypeError("You must pass a long or an int") - - string = "" - - while number > 0: - string = "%s%s" % (byte(number & 0xFF), string) - number /= 256 - - return string - -def fast_exponentiation(a, p, n): - """Calculates r = a^p mod n - """ - result = a % n - remainders = [] - while p != 1: - remainders.append(p & 1) - p = p >> 1 - while remainders: - rem = remainders.pop() - result = ((a ** rem) * result ** 2) % n - return result - -def read_random_int(nbits): - """Reads a random integer of approximately nbits bits rounded up - to whole bytes""" - - nbytes = ceil(nbits/8.) - randomdata = os.urandom(nbytes) - return bytes2int(randomdata) - -def ceil(x): - """ceil(x) -> int(math.ceil(x))""" - - return int(math.ceil(x)) - -def randint(minvalue, maxvalue): - """Returns a random integer x with minvalue <= x <= maxvalue""" - - # Safety - get a lot of random data even if the range is fairly - # small - min_nbits = 32 - - # The range of the random numbers we need to generate - range = maxvalue - minvalue - - # Which is this number of bytes - rangebytes = ceil(math.log(range, 2) / 8.) - - # Convert to bits, but make sure it's always at least min_nbits*2 - rangebits = max(rangebytes * 8, min_nbits * 2) - - # Take a random number of bits between min_nbits and rangebits - nbits = random.randint(min_nbits, rangebits) - - return (read_random_int(nbits) % range) + minvalue - -def fermat_little_theorem(p): - """Returns 1 if p may be prime, and something else if p definitely - is not prime""" - - a = randint(1, p-1) - return fast_exponentiation(a, p-1, p) - -def jacobi(a, b): - """Calculates the value of the Jacobi symbol (a/b) - """ - - if a % b == 0: - return 0 - result = 1 - while a > 1: - if a & 1: - if ((a-1)*(b-1) >> 2) & 1: - result = -result - b, a = a, b % a - else: - if ((b ** 2 - 1) >> 3) & 1: - result = -result - a = a >> 1 - return result - -def jacobi_witness(x, n): - """Returns False if n is an Euler pseudo-prime with base x, and - True otherwise. - """ - - j = jacobi(x, n) % n - f = fast_exponentiation(x, (n-1)/2, n) - - 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 if composite, and True if it's - probably prime. - """ - - q = 0.5 # Property of the jacobi_witness function - - # t = int(math.ceil(k / math.log(1/q, 2))) - t = ceil(k / math.log(1/q, 2)) - for i in range(t+1): - x = randint(1, n-1) - if jacobi_witness(x, n): return False - - return True - -def is_prime(number): - """Returns True if the number is prime, and False otherwise. - - >>> is_prime(42) - 0 - >>> is_prime(41) - 1 - """ - - """ - if not fermat_little_theorem(number) == 1: - # Not prime, according to Fermat's little theorem - return False - """ - - if randomized_primality_testing(number, 5): - # Prime, according to Jacobi - return True - - # Not prime - return False - - -def getprime(nbits): - """Returns a prime number of max. 'math.ceil(nbits/8)*8' bits. In - 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.)) - - while True: - integer = read_random_int(nbits) - - # Make sure it's odd - integer |= 1 - - # Test for primeness - if is_prime(integer): break - - # Retry if not prime - - return integer - -def are_relatively_prime(a, b): - """Returns True if a and b are relatively prime, and False if they - are not. - - >>> are_relatively_prime(2, 3) - 1 - >>> are_relatively_prime(2, 4) - 0 - """ - - d = gcd(a, b) - return (d == 1) - -def find_p_q(nbits): - """Returns a tuple of two different primes of nbits bits""" - - p = getprime(nbits) - while True: - q = getprime(nbits) - if not q == p: break - - return (p, q) - -def extended_euclid_gcd(a, b): - """Returns a tuple (d, i, j) such that d = gcd(a, b) = ia + jb - """ - - if b == 0: - return (a, 1, 0) - - q = abs(a % b) - r = long(a / b) - (d, k, l) = extended_euclid_gcd(b, q) - - return (d, l, k - l*r) - -# Main function: calculate encryption and decryption keys -def calculate_keys(p, q, nbits): - """Calculates an encryption and a decryption key for p and q, and - returns them as a tuple (e, d)""" - - n = p * q - phi_n = (p-1) * (q-1) - - while True: - # Make sure e has enough bits so we ensure "wrapping" through - # modulo n - e = getprime(max(8, nbits/2)) - if are_relatively_prime(e, n) and are_relatively_prime(e, phi_n): break - - (d, i, j) = extended_euclid_gcd(e, phi_n) - - if not d == 1: - raise Exception("e (%d) and phi_n (%d) are not relatively prime" % (e, phi_n)) - - if not (e * i) % phi_n == 1: - raise Exception("e (%d) and i (%d) are not mult. inv. modulo phi_n (%d)" % (e, i, phi_n)) - - return (e, i) - - -def gen_keys(nbits): - """Generate RSA keys of nbits bits. Returns (p, q, e, d). - - Note: this can take a long time, depending on the key size. - """ - - while True: - (p, q) = find_p_q(nbits) - (e, d) = calculate_keys(p, q, nbits) - - # For some reason, d is sometimes negative. We don't know how - # to fix it (yet), so we keep trying until everything is shiny - if d > 0: break - - return (p, q, e, d) - -def gen_pubpriv_keys(nbits): - """Generates public and private keys, and returns them as (pub, - priv). - - The public key consists of a dict {e: ..., , n: ....). The private - key consists of a dict {d: ...., p: ...., q: ....). - """ - - (p, q, e, d) = gen_keys(nbits) - - return ( {'e': e, 'n': p*q}, {'d': d, 'p': p, 'q': q} ) - -def encrypt_int(message, ekey, n): - """Encrypts a message using encryption key 'ekey', working modulo - n""" - - if type(message) is types.IntType: - return encrypt_int(long(message), ekey, n) - - if not type(message) is types.LongType: - raise TypeError("You must pass a long or an int") - - if message > 0 and \ - math.floor(math.log(message, 2)) > math.floor(math.log(n, 2)): - raise OverflowError("The message is too long") - - return fast_exponentiation(message, ekey, n) - -def decrypt_int(cyphertext, dkey, n): - """Decrypts a cypher text using the decryption key 'dkey', working - modulo n""" - - return encrypt_int(cyphertext, dkey, n) - -def sign_int(message, dkey, n): - """Signs 'message' using key 'dkey', working modulo n""" - - return decrypt_int(message, dkey, n) - -def verify_int(signed, ekey, n): - """verifies 'signed' using key 'ekey', working modulo n""" - - return encrypt_int(signed, ekey, n) - -def picklechops(chops): - """Pickles and base64encodes it's argument chops""" - - value = zlib.compress(dumps(chops)) - encoded = base64.encodestring(value) - return encoded.strip() - -def unpicklechops(string): - """base64decodes and unpickes it's argument string into chops""" - - return loads(zlib.decompress(base64.decodestring(string))) - -def chopstring(message, key, n, funcref): - """Splits 'message' into chops that are at most as long as n, - converts these into integers, and calls funcref(integer, key, n) - for each chop. - - Used by 'encrypt' and 'sign'. - """ - - msglen = len(message) - mbits = msglen * 8 - nbits = int(math.floor(math.log(n, 2))) - nbytes = nbits / 8 - blocks = msglen / nbytes - - if msglen % nbytes > 0: - blocks += 1 - - cypher = [] - - for bindex in range(blocks): - offset = bindex * nbytes - block = message[offset:offset+nbytes] - value = bytes2int(block) - cypher.append(funcref(value, key, n)) - - return picklechops(cypher) - -def gluechops(chops, key, n, funcref): - """Glues chops back together into a string. calls - funcref(integer, key, n) for each chop. - - Used by 'decrypt' and 'verify'. - """ - message = "" - - chops = unpicklechops(chops) - - for cpart in chops: - mpart = funcref(cpart, key, n) - message += int2bytes(mpart) - - return message - -def encrypt(message, key): - """Encrypts a string 'message' with the public key 'key'""" - - return chopstring(message, key['e'], key['n'], encrypt_int) - -def sign(message, key): - """Signs a string 'message' with the private key 'key'""" - - return chopstring(message, key['d'], key['p']*key['q'], decrypt_int) - -def decrypt(cypher, key): - """Decrypts a cypher with the private key 'key'""" - - return gluechops(cypher, key['d'], key['p']*key['q'], decrypt_int) - -def verify(cypher, key): - """Verifies a cypher with the public key 'key'""" - - return gluechops(cypher, key['e'], key['n'], encrypt_int) - -# Do doctest if we're not imported -if __name__ == "__main__": - import doctest - doctest.testmod() - -__all__ = ["gen_pubpriv_keys", "encrypt", "decrypt", "sign", "verify"] - diff --git a/rsa/_version200.py b/rsa/_version200.py deleted file mode 100644 index f9156538..00000000 --- a/rsa/_version200.py +++ /dev/null @@ -1,529 +0,0 @@ -"""RSA module - -Module for calculating large primes, and RSA encryption, decryption, -signing and verification. Includes generating public and private keys. - -WARNING: this implementation does not use random padding, compression of the -cleartext input to prevent repetitions, or other common security improvements. -Use with care. - -""" - -__author__ = "Sybren Stuvel, Marloes de Boer, Ivo Tamboer, and Barry Mead" -__date__ = "2010-02-08" -__version__ = '2.0' - -import math -import os -import random -import sys -import types -from rsa._compat import byte - -# Display a warning that this insecure version is imported. -import warnings -warnings.warn('Insecure version of the RSA module is imported as %s' % __name__) - - -def bit_size(number): - """Returns the number of bits required to hold a specific long number""" - - return int(math.ceil(math.log(number,2))) - -def gcd(p, q): - """Returns the greatest common divisor of p and q - >>> gcd(48, 180) - 12 - """ - # Iterateive Version is faster and uses much less stack space - while q != 0: - if p < q: (p,q) = (q,p) - (p,q) = (q, p % q) - return p - - -def bytes2int(bytes): - """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): - raise TypeError("You must pass a string or a list") - - # Convert byte stream to integer - integer = 0 - for byte in bytes: - integer *= 256 - if type(byte) is types.StringType: byte = ord(byte) - integer += byte - - return integer - -def int2bytes(number): - """ - Converts a number to a string of bytes - """ - - if not (type(number) is types.LongType or type(number) is types.IntType): - raise TypeError("You must pass a long or an int") - - string = "" - - while number > 0: - string = "%s%s" % (byte(number & 0xFF), string) - number /= 256 - - return string - -def to64(number): - """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','-','_'. - - >>> to64(10) - 'A' - """ - - if not (type(number) is types.LongType or type(number) is types.IntType): - raise TypeError("You must pass a long or an int") - - if 0 <= number <= 9: #00-09 translates to '0' - '9' - return byte(number + 48) - - if 10 <= number <= 35: - return byte(number + 55) #10-35 translates to 'A' - 'Z' - - if 36 <= number <= 61: - return byte(number + 61) #36-61 translates to 'a' - 'z' - - if number == 62: # 62 translates to '-' (minus) - return byte(45) - - if number == 63: # 63 translates to '_' (underscore) - return byte(95) - - raise ValueError('Invalid Base64 value: %i' % number) - - -def from64(number): - """Converts an ordinal character value in the range of - 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): - raise TypeError("You must pass a long or an int") - - if 48 <= number <= 57: #ord('0') - ord('9') translates to 0-9 - return(number - 48) - - if 65 <= number <= 90: #ord('A') - ord('Z') translates to 10-35 - return(number - 55) - - if 97 <= number <= 122: #ord('a') - ord('z') translates to 36-61 - return(number - 61) - - if number == 45: #ord('-') translates to 62 - return(62) - - if number == 95: #ord('_') translates to 63 - return(63) - - raise ValueError('Invalid Base64 value: %i' % number) - - -def int2str64(number): - """Converts a number to a string of base64 encoded characters in - 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): - raise TypeError("You must pass a long or an int") - - string = "" - - while number > 0: - string = "%s%s" % (to64(number & 0x3F), string) - number /= 64 - - return string - - -def str642int(string): - """Converts a base64 encoded string into an integer. - 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): - raise TypeError("You must pass a string or a list") - - integer = 0 - for byte in string: - integer *= 64 - if type(byte) is types.StringType: byte = ord(byte) - integer += from64(byte) - - return integer - -def read_random_int(nbits): - """Reads a random integer of approximately nbits bits rounded up - to whole bytes""" - - nbytes = int(math.ceil(nbits/8.)) - randomdata = os.urandom(nbytes) - return bytes2int(randomdata) - -def randint(minvalue, maxvalue): - """Returns a random integer x with minvalue <= x <= maxvalue""" - - # Safety - get a lot of random data even if the range is fairly - # small - min_nbits = 32 - - # The range of the random numbers we need to generate - range = (maxvalue - minvalue) + 1 - - # Which is this number of bytes - rangebytes = ((bit_size(range) + 7) / 8) - - # Convert to bits, but make sure it's always at least min_nbits*2 - rangebits = max(rangebytes * 8, min_nbits * 2) - - # Take a random number of bits between min_nbits and rangebits - nbits = random.randint(min_nbits, rangebits) - - return (read_random_int(nbits) % range) + minvalue - -def jacobi(a, b): - """Calculates the value of the Jacobi symbol (a/b) - where both a and b are positive integers, and b is odd - """ - - if a == 0: return 0 - result = 1 - while a > 1: - 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): - """Returns False if n is an Euler pseudo-prime with base x, and - True otherwise. - """ - - j = jacobi(x, n) % n - f = pow(x, (n-1)/2, n) - - 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 - - for i in range(k): - x = randint(1, n-1) - if jacobi_witness(x, n): return False - - return True - -def is_prime(number): - """Returns True if the number is prime, and False otherwise. - - >>> is_prime(42) - 0 - >>> is_prime(41) - 1 - """ - - if randomized_primality_testing(number, 6): - # Prime, according to Jacobi - return True - - # Not prime - return False - - -def getprime(nbits): - """Returns a prime number of max. 'math.ceil(nbits/8)*8' bits. In - 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: - integer = read_random_int(nbits) - - # Make sure it's odd - integer |= 1 - - # Test for primeness - if is_prime(integer): break - - # Retry if not prime - - return integer - -def are_relatively_prime(a, b): - """Returns True if a and b are relatively prime, and False if they - are not. - - >>> are_relatively_prime(2, 3) - 1 - >>> are_relatively_prime(2, 4) - 0 - """ - - d = gcd(a, b) - return (d == 1) - -def find_p_q(nbits): - """Returns a tuple of two different primes of nbits bits""" - pbits = nbits + (nbits/16) #Make sure that p and q aren't too close - qbits = nbits - (nbits/16) #or the factoring programs can factor n - p = getprime(pbits) - while True: - q = getprime(qbits) - #Make sure p and q are different. - if not q == p: break - return (p, q) - -def extended_gcd(a, b): - """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 - # or j = multiplicitive inverse of b mod a - # Neg return values for i or j are made positive mod b or a respectively - # Iterateive Version is faster and uses much less stack space - x = 0 - y = 1 - lx = 1 - ly = 0 - oa = a #Remember original a/b to remove - ob = b #negative values from return results - while b != 0: - q = long(a/b) - (a, b) = (b, a % b) - (x, lx) = ((lx - (q * x)),x) - (y, ly) = ((ly - (q * y)),y) - if (lx < 0): lx += ob #If neg wrap modulo orignal b - if (ly < 0): ly += oa #If neg wrap modulo orignal a - return (a, lx, ly) #Return only positive values - -# Main function: calculate encryption and decryption keys -def calculate_keys(p, q, nbits): - """Calculates an encryption and a decryption key for p and q, and - returns them as a tuple (e, d)""" - - n = p * q - phi_n = (p-1) * (q-1) - - while True: - # Make sure e has enough bits so we ensure "wrapping" through - # modulo n - e = max(65537,getprime(nbits/4)) - if are_relatively_prime(e, n) and are_relatively_prime(e, phi_n): break - - (d, i, j) = extended_gcd(e, phi_n) - - if not d == 1: - raise Exception("e (%d) and phi_n (%d) are not relatively prime" % (e, phi_n)) - if (i < 0): - raise Exception("New extended_gcd shouldn't return negative values") - if not (e * i) % phi_n == 1: - raise Exception("e (%d) and i (%d) are not mult. inv. modulo phi_n (%d)" % (e, i, phi_n)) - - return (e, i) - - -def gen_keys(nbits): - """Generate RSA keys of nbits bits. Returns (p, q, e, d). - - Note: this can take a long time, depending on the key size. - """ - - (p, q) = find_p_q(nbits) - (e, d) = calculate_keys(p, q, nbits) - - return (p, q, e, d) - -def newkeys(nbits): - """Generates public and private keys, and returns them as (pub, - priv). - - The public key consists of a dict {e: ..., , n: ....). The private - key consists of a dict {d: ...., p: ...., q: ....). - """ - nbits = max(9,nbits) # Don't let nbits go below 9 bits - (p, q, e, d) = gen_keys(nbits) - - return ( {'e': e, 'n': p*q}, {'d': d, 'p': p, 'q': q} ) - -def encrypt_int(message, ekey, n): - """Encrypts a message using encryption key 'ekey', working modulo n""" - - if type(message) is types.IntType: - message = long(message) - - if not type(message) is types.LongType: - raise TypeError("You must pass a long or int") - - if message < 0 or message > n: - raise OverflowError("The message is too long") - - #Note: Bit exponents start at zero (bit counts start at 1) this is correct - safebit = bit_size(n) - 2 #compute safe bit (MSB - 1) - message += (1 << safebit) #add safebit to ensure folding - - return pow(message, ekey, n) - -def decrypt_int(cyphertext, dkey, n): - """Decrypts a cypher text using the decryption key 'dkey', working - modulo n""" - - message = pow(cyphertext, dkey, n) - - safebit = bit_size(n) - 2 #compute safe bit (MSB - 1) - message -= (1 << safebit) #remove safebit before decode - - return message - -def encode64chops(chops): - """base64encodes chops and combines them into a ',' delimited string""" - - chips = [] #chips are character chops - - for value in chops: - chips.append(int2str64(value)) - - #delimit chops with comma - encoded = ','.join(chips) - - return encoded - -def decode64chops(string): - """base64decodes and makes a ',' delimited string into chops""" - - chips = string.split(',') #split chops at commas - - chops = [] - - for string in chips: #make char chops (chips) into chops - chops.append(str642int(string)) - - return chops - -def chopstring(message, key, n, funcref): - """Chops the 'message' into integers that fit into n, - leaving room for a safebit to be added to ensure that all - messages fold during exponentiation. The MSB of the number n - is not independant modulo n (setting it could cause overflow), so - use the next lower bit for the safebit. Therefore reserve 2-bits - in the number n for non-data bits. Calls specified encryption - function for each chop. - - Used by 'encrypt' and 'sign'. - """ - - msglen = len(message) - mbits = msglen * 8 - #Set aside 2-bits so setting of safebit won't overflow modulo n. - nbits = bit_size(n) - 2 # leave room for safebit - nbytes = nbits / 8 - blocks = msglen / nbytes - - if msglen % nbytes > 0: - blocks += 1 - - cypher = [] - - for bindex in range(blocks): - offset = bindex * nbytes - block = message[offset:offset+nbytes] - value = bytes2int(block) - cypher.append(funcref(value, key, n)) - - return encode64chops(cypher) #Encode encrypted ints to base64 strings - -def gluechops(string, key, n, funcref): - """Glues chops back together into a string. calls - funcref(integer, key, n) for each chop. - - Used by 'decrypt' and 'verify'. - """ - message = "" - - chops = decode64chops(string) #Decode base64 strings into integer chops - - for cpart in chops: - mpart = funcref(cpart, key, n) #Decrypt each chop - message += int2bytes(mpart) #Combine decrypted strings into a msg - - return message - -def encrypt(message, key): - """Encrypts a string 'message' with the public key 'key'""" - if 'n' not in key: - raise Exception("You must use the public key with encrypt") - - return chopstring(message, key['e'], key['n'], encrypt_int) - -def sign(message, key): - """Signs a string 'message' with the private key 'key'""" - if 'p' not in key: - raise Exception("You must use the private key with sign") - - return chopstring(message, key['d'], key['p']*key['q'], encrypt_int) - -def decrypt(cypher, key): - """Decrypts a string 'cypher' with the private key 'key'""" - if 'p' not in key: - raise Exception("You must use the private key with decrypt") - - return gluechops(cypher, key['d'], key['p']*key['q'], decrypt_int) - -def verify(cypher, key): - """Verifies a string 'cypher' with the public key 'key'""" - if 'n' not in key: - raise Exception("You must use the public key with verify") - - return gluechops(cypher, key['e'], key['n'], decrypt_int) - -# Do doctest if we're not imported -if __name__ == "__main__": - import doctest - doctest.testmod() - -__all__ = ["newkeys", "encrypt", "decrypt", "sign", "verify"] - diff --git a/rsa/bigfile.py b/rsa/bigfile.py deleted file mode 100644 index 516cf56b..00000000 --- a/rsa/bigfile.py +++ /dev/null @@ -1,87 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -'''Large file support - - - break a file into smaller blocks, and encrypt them, and store the - encrypted blocks in another file. - - - take such an encrypted files, decrypt its blocks, and reconstruct the - original file. - -The encrypted file format is as follows, where || denotes byte concatenation: - - FILE := VERSION || BLOCK || BLOCK ... - - BLOCK := LENGTH || DATA - - LENGTH := varint-encoded length of the subsequent data. Varint comes from - Google Protobuf, and encodes an integer into a variable number of bytes. - Each byte uses the 7 lowest bits to encode the value. The highest bit set - to 1 indicates the next byte is also part of the varint. The last byte will - have this bit set to 0. - -This file format is called the VARBLOCK format, in line with the varint format -used to denote the block sizes. - -''' - -from rsa import key, common, pkcs1, varblock -from rsa._compat import byte - -def encrypt_bigfile(infile, outfile, pub_key): - '''Encrypts a file, writing it to 'outfile' in VARBLOCK format. - - :param infile: file-like object to read the cleartext from - :param outfile: file-like object to write the crypto in VARBLOCK format to - :param pub_key: :py:class:`rsa.PublicKey` to encrypt with - - ''' - - if not isinstance(pub_key, key.PublicKey): - raise TypeError('Public key required, but got %r' % pub_key) - - key_bytes = common.bit_size(pub_key.n) // 8 - blocksize = key_bytes - 11 # keep space for PKCS#1 padding - - # Write the version number to the VARBLOCK file - outfile.write(byte(varblock.VARBLOCK_VERSION)) - - # Encrypt and write each block - for block in varblock.yield_fixedblocks(infile, blocksize): - crypto = pkcs1.encrypt(block, pub_key) - - varblock.write_varint(outfile, len(crypto)) - outfile.write(crypto) - -def decrypt_bigfile(infile, outfile, priv_key): - '''Decrypts an encrypted VARBLOCK file, writing it to 'outfile' - - :param infile: file-like object to read the crypto in VARBLOCK format from - :param outfile: file-like object to write the cleartext to - :param priv_key: :py:class:`rsa.PrivateKey` to decrypt with - - ''' - - if not isinstance(priv_key, key.PrivateKey): - raise TypeError('Private key required, but got %r' % priv_key) - - for block in varblock.yield_varblocks(infile): - cleartext = pkcs1.decrypt(block, priv_key) - outfile.write(cleartext) - -__all__ = ['encrypt_bigfile', 'decrypt_bigfile'] - diff --git a/rsa/cli.py b/rsa/cli.py deleted file mode 100644 index 2441955a..00000000 --- a/rsa/cli.py +++ /dev/null @@ -1,379 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -'''Commandline scripts. - -These scripts are called by the executables defined in setup.py. -''' - -from __future__ import with_statement, print_function - -import abc -import sys -from optparse import OptionParser - -import rsa -import rsa.bigfile -import rsa.pkcs1 - -HASH_METHODS = sorted(rsa.pkcs1.HASH_METHODS.keys()) - -def keygen(): - '''Key generator.''' - - # Parse the CLI options - parser = OptionParser(usage='usage: %prog [options] keysize', - description='Generates a new RSA keypair of "keysize" bits.') - - parser.add_option('--pubout', type='string', - help='Output filename for the public key. The public key is ' - 'not saved if this option is not present. You can use ' - 'pyrsa-priv2pub to create the public key file later.') - - parser.add_option('-o', '--out', type='string', - help='Output filename for the private key. The key is ' - 'written to stdout if this option is not present.') - - parser.add_option('--form', - help='key format of the private and public keys - default PEM', - choices=('PEM', 'DER'), default='PEM') - - (cli, cli_args) = parser.parse_args(sys.argv[1:]) - - if len(cli_args) != 1: - parser.print_help() - raise SystemExit(1) - - try: - keysize = int(cli_args[0]) - except ValueError: - parser.print_help() - print('Not a valid number: %s' % cli_args[0], file=sys.stderr) - raise SystemExit(1) - - print('Generating %i-bit key' % keysize, file=sys.stderr) - (pub_key, priv_key) = rsa.newkeys(keysize) - - - # Save public key - if cli.pubout: - print('Writing public key to %s' % cli.pubout, file=sys.stderr) - data = pub_key.save_pkcs1(format=cli.form) - with open(cli.pubout, 'wb') as outfile: - outfile.write(data) - - # Save private key - data = priv_key.save_pkcs1(format=cli.form) - - if cli.out: - print('Writing private key to %s' % cli.out, file=sys.stderr) - with open(cli.out, 'wb') as outfile: - outfile.write(data) - else: - print('Writing private key to stdout', file=sys.stderr) - sys.stdout.write(data) - - -class CryptoOperation(object): - '''CLI callable that operates with input, output, and a key.''' - - __metaclass__ = abc.ABCMeta - - keyname = 'public' # or 'private' - usage = 'usage: %%prog [options] %(keyname)s_key' - description = None - operation = 'decrypt' - operation_past = 'decrypted' - operation_progressive = 'decrypting' - input_help = 'Name of the file to %(operation)s. Reads from stdin if ' \ - 'not specified.' - output_help = 'Name of the file to write the %(operation_past)s file ' \ - 'to. Written to stdout if this option is not present.' - expected_cli_args = 1 - has_output = True - - key_class = rsa.PublicKey - - def __init__(self): - self.usage = self.usage % self.__class__.__dict__ - self.input_help = self.input_help % self.__class__.__dict__ - self.output_help = self.output_help % self.__class__.__dict__ - - @abc.abstractmethod - def perform_operation(self, indata, key, cli_args=None): - '''Performs the program's operation. - - Implement in a subclass. - - :returns: the data to write to the output. - ''' - - def __call__(self): - '''Runs the program.''' - - (cli, cli_args) = self.parse_cli() - - key = self.read_key(cli_args[0], cli.keyform) - - indata = self.read_infile(cli.input) - - print(self.operation_progressive.title(), file=sys.stderr) - outdata = self.perform_operation(indata, key, cli_args) - - if self.has_output: - self.write_outfile(outdata, cli.output) - - def parse_cli(self): - '''Parse the CLI options - - :returns: (cli_opts, cli_args) - ''' - - parser = OptionParser(usage=self.usage, description=self.description) - - parser.add_option('-i', '--input', type='string', help=self.input_help) - - if self.has_output: - parser.add_option('-o', '--output', type='string', help=self.output_help) - - parser.add_option('--keyform', - help='Key format of the %s key - default PEM' % self.keyname, - choices=('PEM', 'DER'), default='PEM') - - (cli, cli_args) = parser.parse_args(sys.argv[1:]) - - if len(cli_args) != self.expected_cli_args: - parser.print_help() - raise SystemExit(1) - - return (cli, cli_args) - - def read_key(self, filename, keyform): - '''Reads a public or private key.''' - - print('Reading %s key from %s' % (self.keyname, filename), file=sys.stderr) - with open(filename, 'rb') as keyfile: - keydata = keyfile.read() - - return self.key_class.load_pkcs1(keydata, keyform) - - def read_infile(self, inname): - '''Read the input file''' - - if inname: - print('Reading input from %s' % inname, file=sys.stderr) - with open(inname, 'rb') as infile: - return infile.read() - - print('Reading input from stdin', file=sys.stderr) - return sys.stdin.read() - - def write_outfile(self, outdata, outname): - '''Write the output file''' - - if outname: - print('Writing output to %s' % outname, file=sys.stderr) - with open(outname, 'wb') as outfile: - outfile.write(outdata) - else: - print('Writing output to stdout', file=sys.stderr) - sys.stdout.write(outdata) - -class EncryptOperation(CryptoOperation): - '''Encrypts a file.''' - - keyname = 'public' - description = ('Encrypts a file. The file must be shorter than the key ' - 'length in order to be encrypted. For larger files, use the ' - 'pyrsa-encrypt-bigfile command.') - operation = 'encrypt' - operation_past = 'encrypted' - operation_progressive = 'encrypting' - - - def perform_operation(self, indata, pub_key, cli_args=None): - '''Encrypts files.''' - - return rsa.encrypt(indata, pub_key) - -class DecryptOperation(CryptoOperation): - '''Decrypts a file.''' - - keyname = 'private' - description = ('Decrypts a file. The original file must be shorter than ' - 'the key length in order to have been encrypted. For larger ' - 'files, use the pyrsa-decrypt-bigfile command.') - operation = 'decrypt' - operation_past = 'decrypted' - operation_progressive = 'decrypting' - key_class = rsa.PrivateKey - - def perform_operation(self, indata, priv_key, cli_args=None): - '''Decrypts files.''' - - return rsa.decrypt(indata, priv_key) - -class SignOperation(CryptoOperation): - '''Signs a file.''' - - keyname = 'private' - usage = 'usage: %%prog [options] private_key hash_method' - description = ('Signs a file, outputs the signature. Choose the hash ' - 'method from %s' % ', '.join(HASH_METHODS)) - operation = 'sign' - operation_past = 'signature' - operation_progressive = 'Signing' - key_class = rsa.PrivateKey - expected_cli_args = 2 - - output_help = ('Name of the file to write the signature to. Written ' - 'to stdout if this option is not present.') - - def perform_operation(self, indata, priv_key, cli_args): - '''Decrypts files.''' - - hash_method = cli_args[1] - if hash_method not in HASH_METHODS: - raise SystemExit('Invalid hash method, choose one of %s' % - ', '.join(HASH_METHODS)) - - return rsa.sign(indata, priv_key, hash_method) - -class VerifyOperation(CryptoOperation): - '''Verify a signature.''' - - keyname = 'public' - usage = 'usage: %%prog [options] private_key signature_file' - description = ('Verifies a signature, exits with status 0 upon success, ' - 'prints an error message and exits with status 1 upon error.') - operation = 'verify' - operation_past = 'verified' - operation_progressive = 'Verifying' - key_class = rsa.PublicKey - expected_cli_args = 2 - has_output = False - - def perform_operation(self, indata, pub_key, cli_args): - '''Decrypts files.''' - - signature_file = cli_args[1] - - with open(signature_file, 'rb') as sigfile: - signature = sigfile.read() - - try: - rsa.verify(indata, signature, pub_key) - except rsa.VerificationError: - raise SystemExit('Verification failed.') - - print('Verification OK', file=sys.stderr) - - -class BigfileOperation(CryptoOperation): - '''CryptoOperation that doesn't read the entire file into memory.''' - - def __init__(self): - CryptoOperation.__init__(self) - - self.file_objects = [] - - def __del__(self): - '''Closes any open file handles.''' - - for fobj in self.file_objects: - fobj.close() - - def __call__(self): - '''Runs the program.''' - - (cli, cli_args) = self.parse_cli() - - key = self.read_key(cli_args[0], cli.keyform) - - # Get the file handles - infile = self.get_infile(cli.input) - outfile = self.get_outfile(cli.output) - - # Call the operation - print(self.operation_progressive.title(), file=sys.stderr) - self.perform_operation(infile, outfile, key, cli_args) - - def get_infile(self, inname): - '''Returns the input file object''' - - if inname: - print('Reading input from %s' % inname, file=sys.stderr) - fobj = open(inname, 'rb') - self.file_objects.append(fobj) - else: - print('Reading input from stdin', file=sys.stderr) - fobj = sys.stdin - - return fobj - - def get_outfile(self, outname): - '''Returns the output file object''' - - if outname: - print('Will write output to %s' % outname, file=sys.stderr) - fobj = open(outname, 'wb') - self.file_objects.append(fobj) - else: - print('Will write output to stdout', file=sys.stderr) - fobj = sys.stdout - - return fobj - -class EncryptBigfileOperation(BigfileOperation): - '''Encrypts a file to VARBLOCK format.''' - - keyname = 'public' - description = ('Encrypts a file to an encrypted VARBLOCK file. The file ' - 'can be larger than the key length, but the output file is only ' - 'compatible with Python-RSA.') - operation = 'encrypt' - operation_past = 'encrypted' - operation_progressive = 'encrypting' - - def perform_operation(self, infile, outfile, pub_key, cli_args=None): - '''Encrypts files to VARBLOCK.''' - - return rsa.bigfile.encrypt_bigfile(infile, outfile, pub_key) - -class DecryptBigfileOperation(BigfileOperation): - '''Decrypts a file in VARBLOCK format.''' - - keyname = 'private' - description = ('Decrypts an encrypted VARBLOCK file that was encrypted ' - 'with pyrsa-encrypt-bigfile') - operation = 'decrypt' - operation_past = 'decrypted' - operation_progressive = 'decrypting' - key_class = rsa.PrivateKey - - def perform_operation(self, infile, outfile, priv_key, cli_args=None): - '''Decrypts a VARBLOCK file.''' - - return rsa.bigfile.decrypt_bigfile(infile, outfile, priv_key) - - -encrypt = EncryptOperation() -decrypt = DecryptOperation() -sign = SignOperation() -verify = VerifyOperation() -encrypt_bigfile = EncryptBigfileOperation() -decrypt_bigfile = DecryptBigfileOperation() - diff --git a/rsa/common.py b/rsa/common.py deleted file mode 100644 index 39feb8c2..00000000 --- a/rsa/common.py +++ /dev/null @@ -1,185 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -'''Common functionality shared by several modules.''' - - -def bit_size(num): - ''' - Number of bits needed to represent a integer excluding any prefix - 0 bits. - - As per definition from http://wiki.python.org/moin/BitManipulation and - to match the behavior of the Python 3 API. - - Usage:: - - >>> bit_size(1023) - 10 - >>> bit_size(1024) - 11 - >>> bit_size(1025) - 11 - - :param num: - Integer value. If num is 0, returns 0. Only the absolute value of the - number is considered. Therefore, signed integers will be abs(num) - before the number's bit length is determined. - :returns: - Returns the number of bits in the integer. - ''' - if num == 0: - return 0 - if num < 0: - num = -num - - # Make sure this is an int and not a float. - num & 1 - - hex_num = "%x" % num - return ((len(hex_num) - 1) * 4) + { - '0':0, '1':1, '2':2, '3':2, - '4':3, '5':3, '6':3, '7':3, - '8':4, '9':4, 'a':4, 'b':4, - 'c':4, 'd':4, 'e':4, 'f':4, - }[hex_num[0]] - - -def _bit_size(number): - ''' - Returns the number of bits required to hold a specific long number. - ''' - if number < 0: - raise ValueError('Only nonnegative numbers possible: %s' % number) - - if number == 0: - return 0 - - # This works, even with very large numbers. When using math.log(number, 2), - # you'll get rounding errors and it'll fail. - bits = 0 - while number: - bits += 1 - number >>= 1 - - return bits - - -def byte_size(number): - ''' - Returns the number of bytes required to hold a specific long number. - - The number of bytes is rounded up. - - Usage:: - - >>> byte_size(1 << 1023) - 128 - >>> byte_size((1 << 1024) - 1) - 128 - >>> byte_size(1 << 1024) - 129 - - :param number: - An unsigned integer - :returns: - The number of bytes required to hold a specific long number. - ''' - quanta, mod = divmod(bit_size(number), 8) - if mod or number == 0: - quanta += 1 - return quanta - #return int(math.ceil(bit_size(number) / 8.0)) - - -def extended_gcd(a, b): - '''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 - # or j = multiplicitive inverse of b mod a - # Neg return values for i or j are made positive mod b or a respectively - # Iterateive Version is faster and uses much less stack space - x = 0 - y = 1 - lx = 1 - ly = 0 - oa = a #Remember original a/b to remove - ob = b #negative values from return results - while b != 0: - q = a // b - (a, b) = (b, a % b) - (x, lx) = ((lx - (q * x)),x) - (y, ly) = ((ly - (q * y)),y) - if (lx < 0): lx += ob #If neg wrap modulo orignal b - if (ly < 0): ly += oa #If neg wrap modulo orignal a - return (a, lx, ly) #Return only positive values - - -def inverse(x, n): - '''Returns x^-1 (mod n) - - >>> inverse(7, 4) - 3 - >>> (inverse(143, 4) * 143) % 4 - 1 - ''' - - (divider, inv, _) = extended_gcd(x, n) - - if divider != 1: - raise ValueError("x (%d) and n (%d) are not relatively prime" % (x, n)) - - return inv - - -def crt(a_values, modulo_values): - '''Chinese Remainder Theorem. - - Calculates x such that x = a[i] (mod m[i]) for each i. - - :param a_values: the a-values of the above equation - :param modulo_values: the m-values of the above equation - :returns: x such that x = a[i] (mod m[i]) for each i - - - >>> crt([2, 3], [3, 5]) - 8 - - >>> crt([2, 3, 2], [3, 5, 7]) - 23 - - >>> crt([2, 3, 0], [7, 11, 15]) - 135 - ''' - - m = 1 - x = 0 - - for modulo in modulo_values: - m *= modulo - - for (m_i, a_i) in zip(modulo_values, a_values): - M_i = m // m_i - inv = inverse(M_i, m_i) - - x = (x + a_i * M_i * inv) % m - - return x - -if __name__ == '__main__': - import doctest - doctest.testmod() - diff --git a/rsa/core.py b/rsa/core.py deleted file mode 100644 index 90dfee8e..00000000 --- a/rsa/core.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -'''Core mathematical operations. - -This is the actual core RSA implementation, which is only defined -mathematically on integers. -''' - - -from rsa._compat import is_integer - -def assert_int(var, name): - - if is_integer(var): - return - - raise TypeError('%s should be an integer, not %s' % (name, var.__class__)) - -def encrypt_int(message, ekey, n): - '''Encrypts a message using encryption key 'ekey', working modulo n''' - - assert_int(message, 'message') - assert_int(ekey, 'ekey') - assert_int(n, 'n') - - if message < 0: - raise ValueError('Only non-negative numbers are supported') - - if message > n: - raise OverflowError("The message %i is too long for n=%i" % (message, n)) - - return pow(message, ekey, n) - -def decrypt_int(cyphertext, dkey, n): - '''Decrypts a cypher text using the decryption key 'dkey', working - modulo n''' - - assert_int(cyphertext, 'cyphertext') - assert_int(dkey, 'dkey') - assert_int(n, 'n') - - message = pow(cyphertext, dkey, n) - return message - diff --git a/rsa/key.py b/rsa/key.py deleted file mode 100644 index 3870541a..00000000 --- a/rsa/key.py +++ /dev/null @@ -1,581 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -'''RSA key generation code. - -Create new keys with the newkeys() function. It will give you a PublicKey and a -PrivateKey object. - -Loading and saving keys requires the pyasn1 module. This module is imported as -late as possible, such that other functionality will remain working in absence -of pyasn1. - -''' - -import logging -from rsa._compat import b - -import rsa.prime -import rsa.pem -import rsa.common - -log = logging.getLogger(__name__) - -class AbstractKey(object): - '''Abstract superclass for private and public keys.''' - - @classmethod - def load_pkcs1(cls, keyfile, format='PEM'): - r'''Loads a key in PKCS#1 DER or PEM format. - - :param keyfile: contents of a DER- or PEM-encoded file that contains - the public key. - :param format: the format of the file to load; 'PEM' or 'DER' - - :return: a PublicKey object - - ''' - - methods = { - 'PEM': cls._load_pkcs1_pem, - 'DER': cls._load_pkcs1_der, - } - - if format not in methods: - formats = ', '.join(sorted(methods.keys())) - raise ValueError('Unsupported format: %r, try one of %s' % (format, - formats)) - - method = methods[format] - return method(keyfile) - - def save_pkcs1(self, format='PEM'): - '''Saves the public key in PKCS#1 DER or PEM format. - - :param format: the format to save; 'PEM' or 'DER' - :returns: the DER- or PEM-encoded public key. - - ''' - - methods = { - 'PEM': self._save_pkcs1_pem, - 'DER': self._save_pkcs1_der, - } - - if format not in methods: - formats = ', '.join(sorted(methods.keys())) - raise ValueError('Unsupported format: %r, try one of %s' % (format, - formats)) - - method = methods[format] - return method() - -class PublicKey(AbstractKey): - '''Represents a public RSA key. - - This key is also known as the 'encryption key'. It contains the 'n' and 'e' - values. - - Supports attributes as well as dictionary-like access. Attribute accesss is - faster, though. - - >>> PublicKey(5, 3) - PublicKey(5, 3) - - >>> key = PublicKey(5, 3) - >>> key.n - 5 - >>> key['n'] - 5 - >>> key.e - 3 - >>> key['e'] - 3 - - ''' - - __slots__ = ('n', 'e') - - def __init__(self, n, e): - self.n = n - self.e = e - - def __getitem__(self, key): - return getattr(self, key) - - def __repr__(self): - return 'PublicKey(%i, %i)' % (self.n, self.e) - - def __eq__(self, other): - if other is None: - return False - - if not isinstance(other, PublicKey): - return False - - return self.n == other.n and self.e == other.e - - def __ne__(self, other): - return not (self == other) - - @classmethod - def _load_pkcs1_der(cls, keyfile): - r'''Loads a key in PKCS#1 DER format. - - @param keyfile: contents of a DER-encoded file that contains the public - key. - @return: a PublicKey object - - First let's construct a DER encoded key: - - >>> import base64 - >>> b64der = 'MAwCBQCNGmYtAgMBAAE=' - >>> der = base64.decodestring(b64der) - - This loads the file: - - >>> PublicKey._load_pkcs1_der(der) - PublicKey(2367317549, 65537) - - ''' - - from pyasn1.codec.der import decoder - (priv, _) = decoder.decode(keyfile) - - # ASN.1 contents of DER encoded public key: - # - # RSAPublicKey ::= SEQUENCE { - # modulus INTEGER, -- n - # publicExponent INTEGER, -- e - - as_ints = tuple(int(x) for x in priv) - return cls(*as_ints) - - def _save_pkcs1_der(self): - '''Saves the public key in PKCS#1 DER format. - - @returns: the DER-encoded public key. - ''' - - from pyasn1.type import univ, namedtype - from pyasn1.codec.der import encoder - - class AsnPubKey(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('modulus', univ.Integer()), - namedtype.NamedType('publicExponent', univ.Integer()), - ) - - # Create the ASN object - asn_key = AsnPubKey() - asn_key.setComponentByName('modulus', self.n) - asn_key.setComponentByName('publicExponent', self.e) - - return encoder.encode(asn_key) - - @classmethod - def _load_pkcs1_pem(cls, keyfile): - '''Loads a PKCS#1 PEM-encoded public key file. - - The contents of the file before the "-----BEGIN RSA PUBLIC KEY-----" and - after the "-----END RSA PUBLIC KEY-----" lines is ignored. - - @param keyfile: contents of a PEM-encoded file that contains the public - key. - @return: a PublicKey object - ''' - - der = rsa.pem.load_pem(keyfile, 'RSA PUBLIC KEY') - return cls._load_pkcs1_der(der) - - def _save_pkcs1_pem(self): - '''Saves a PKCS#1 PEM-encoded public key file. - - @return: contents of a PEM-encoded file that contains the public key. - ''' - - der = self._save_pkcs1_der() - return rsa.pem.save_pem(der, 'RSA PUBLIC KEY') - -class PrivateKey(AbstractKey): - '''Represents a private RSA key. - - This key is also known as the 'decryption key'. It contains the 'n', 'e', - 'd', 'p', 'q' and other values. - - Supports attributes as well as dictionary-like access. Attribute accesss is - faster, though. - - >>> 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: - - >>> pk = PrivateKey(3727264081, 65537, 3349121513, 65063, 57287) - >>> pk.exp1 - 55063 - >>> pk.exp2 - 10095 - >>> pk.coef - 50797 - - If you give exp1, exp2 or coef, they will be used as-is: - - >>> pk = PrivateKey(1, 2, 3, 4, 5, 6, 7, 8) - >>> pk.exp1 - 6 - >>> pk.exp2 - 7 - >>> pk.coef - 8 - - ''' - - __slots__ = ('n', 'e', 'd', 'p', 'q', 'exp1', 'exp2', 'coef') - - def __init__(self, n, e, d, p, q, exp1=None, exp2=None, coef=None): - self.n = n - self.e = e - self.d = d - self.p = p - self.q = q - - # Calculate the other values if they aren't supplied - if exp1 is None: - self.exp1 = int(d % (p - 1)) - else: - self.exp1 = exp1 - - if exp1 is None: - self.exp2 = int(d % (q - 1)) - else: - self.exp2 = exp2 - - if coef is None: - self.coef = rsa.common.inverse(q, p) - else: - self.coef = coef - - def __getitem__(self, key): - return getattr(self, key) - - def __repr__(self): - return 'PrivateKey(%(n)i, %(e)i, %(d)i, %(p)i, %(q)i)' % self - - def __eq__(self, other): - if other is None: - return False - - if not isinstance(other, PrivateKey): - return False - - return (self.n == other.n and - self.e == other.e and - self.d == other.d and - self.p == other.p and - self.q == other.q and - self.exp1 == other.exp1 and - self.exp2 == other.exp2 and - self.coef == other.coef) - - def __ne__(self, other): - return not (self == other) - - @classmethod - def _load_pkcs1_der(cls, keyfile): - r'''Loads a key in PKCS#1 DER format. - - @param keyfile: contents of a DER-encoded file that contains the private - key. - @return: a PrivateKey object - - First let's construct a DER encoded key: - - >>> import base64 - >>> b64der = 'MC4CAQACBQDeKYlRAgMBAAECBQDHn4npAgMA/icCAwDfxwIDANcXAgInbwIDAMZt' - >>> der = base64.decodestring(b64der) - - This loads the file: - - >>> PrivateKey._load_pkcs1_der(der) - PrivateKey(3727264081, 65537, 3349121513, 65063, 57287) - - ''' - - from pyasn1.codec.der import decoder - (priv, _) = decoder.decode(keyfile) - - # ASN.1 contents of DER encoded private key: - # - # RSAPrivateKey ::= SEQUENCE { - # version Version, - # modulus INTEGER, -- n - # publicExponent INTEGER, -- e - # privateExponent INTEGER, -- d - # prime1 INTEGER, -- p - # prime2 INTEGER, -- q - # exponent1 INTEGER, -- d mod (p-1) - # exponent2 INTEGER, -- d mod (q-1) - # coefficient INTEGER, -- (inverse of q) mod p - # otherPrimeInfos OtherPrimeInfos OPTIONAL - # } - - if priv[0] != 0: - raise ValueError('Unable to read this file, version %s != 0' % priv[0]) - - as_ints = tuple(int(x) for x in priv[1:9]) - return cls(*as_ints) - - def _save_pkcs1_der(self): - '''Saves the private key in PKCS#1 DER format. - - @returns: the DER-encoded private key. - ''' - - from pyasn1.type import univ, namedtype - from pyasn1.codec.der import encoder - - class AsnPrivKey(univ.Sequence): - componentType = namedtype.NamedTypes( - namedtype.NamedType('version', univ.Integer()), - namedtype.NamedType('modulus', univ.Integer()), - namedtype.NamedType('publicExponent', univ.Integer()), - namedtype.NamedType('privateExponent', univ.Integer()), - namedtype.NamedType('prime1', univ.Integer()), - namedtype.NamedType('prime2', univ.Integer()), - namedtype.NamedType('exponent1', univ.Integer()), - namedtype.NamedType('exponent2', univ.Integer()), - namedtype.NamedType('coefficient', univ.Integer()), - ) - - # Create the ASN object - asn_key = AsnPrivKey() - asn_key.setComponentByName('version', 0) - asn_key.setComponentByName('modulus', self.n) - asn_key.setComponentByName('publicExponent', self.e) - asn_key.setComponentByName('privateExponent', self.d) - asn_key.setComponentByName('prime1', self.p) - asn_key.setComponentByName('prime2', self.q) - asn_key.setComponentByName('exponent1', self.exp1) - asn_key.setComponentByName('exponent2', self.exp2) - asn_key.setComponentByName('coefficient', self.coef) - - return encoder.encode(asn_key) - - @classmethod - def _load_pkcs1_pem(cls, keyfile): - '''Loads a PKCS#1 PEM-encoded private key file. - - The contents of the file before the "-----BEGIN RSA PRIVATE KEY-----" and - after the "-----END RSA PRIVATE KEY-----" lines is ignored. - - @param keyfile: contents of a PEM-encoded file that contains the private - key. - @return: a PrivateKey object - ''' - - der = rsa.pem.load_pem(keyfile, b('RSA PRIVATE KEY')) - return cls._load_pkcs1_der(der) - - def _save_pkcs1_pem(self): - '''Saves a PKCS#1 PEM-encoded private key file. - - @return: contents of a PEM-encoded file that contains the private key. - ''' - - der = self._save_pkcs1_der() - return rsa.pem.save_pem(der, b('RSA PRIVATE KEY')) - -def find_p_q(nbits, getprime_func=rsa.prime.getprime, accurate=True): - ''''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 - will not be equal. - - :param nbits: the number of bits in each of p and q. - :param getprime_func: the getprime function, defaults to - :py:func:`rsa.prime.getprime`. - - *Introduced in Python-RSA 3.1* - - :param accurate: whether to enable accurate mode or not. - :returns: (p, q), where p > q - - >>> (p, q) = find_p_q(128) - >>> from rsa import common - >>> common.bit_size(p * q) - 256 - - When not in accurate mode, the number of bits can be slightly less - - >>> (p, q) = find_p_q(128, accurate=False) - >>> from rsa import common - >>> common.bit_size(p * q) <= 256 - True - >>> common.bit_size(p * q) > 240 - True - - ''' - - total_bits = nbits * 2 - - # Make sure that p and q aren't too close or the factoring programs can - # factor n. - shift = nbits // 16 - pbits = nbits + shift - qbits = nbits - shift - - # Choose the two initial primes - log.debug('find_p_q(%i): Finding p', nbits) - p = getprime_func(pbits) - log.debug('find_p_q(%i): Finding q', nbits) - q = getprime_func(qbits) - - def is_acceptable(p, q): - '''Returns True iff p and q are acceptable: - - - p and q differ - - (p * q) has the right nr of bits (when accurate=True) - ''' - - if p == q: - return False - - if not accurate: - return True - - # Make sure we have just the right amount of bits - found_size = rsa.common.bit_size(p * q) - return total_bits == found_size - - # Keep choosing other primes until they match our requirements. - change_p = False - while not is_acceptable(p, q): - # Change p on one iteration and q on the other - if change_p: - p = getprime_func(pbits) - else: - q = getprime_func(qbits) - - change_p = not change_p - - # We want p > q as described on - # http://www.di-mgt.com.au/rsa_alg.html#crt - return (max(p, q), min(p, q)) - -def calculate_keys(p, q, nbits): - '''Calculates an encryption and a decryption key given p and q, and - returns them as a tuple (e, d) - - ''' - - phi_n = (p - 1) * (q - 1) - - # A very common choice for e is 65537 - e = 65537 - - try: - d = rsa.common.inverse(e, phi_n) - except ValueError: - raise ValueError("e (%d) and phi_n (%d) are not relatively prime" % - (e, phi_n)) - - if (e * d) % phi_n != 1: - raise ValueError("e (%d) and d (%d) are not mult. inv. modulo " - "phi_n (%d)" % (e, d, phi_n)) - - return (e, d) - -def gen_keys(nbits, getprime_func, accurate=True): - '''Generate RSA keys of nbits bits. Returns (p, q, e, d). - - Note: this can take a long time, depending on the key size. - - :param nbits: the total number of bits in ``p`` and ``q``. Both ``p`` and - ``q`` will use ``nbits/2`` bits. - :param getprime_func: either :py:func:`rsa.prime.getprime` or a function - with similar signature. - ''' - - (p, q) = find_p_q(nbits // 2, getprime_func, accurate) - (e, d) = calculate_keys(p, q, nbits // 2) - - return (p, q, e, d) - -def newkeys(nbits, accurate=True, poolsize=1): - '''Generates public and private keys, and returns them as (pub, priv). - - The public key is also known as the 'encryption key', and is a - :py:class:`rsa.PublicKey` object. The private key is also known as the - 'decryption key' and is a :py:class:`rsa.PrivateKey` object. - - :param nbits: the number of bits required to store ``n = p*q``. - :param accurate: when True, ``n`` will have exactly the number of bits you - asked for. However, this makes key generation much slower. When False, - `n`` may have slightly less bits. - :param poolsize: the number of processes to use to generate the prime - numbers. If set to a number > 1, a parallel algorithm will be used. - This requires Python 2.6 or newer. - - :returns: a tuple (:py:class:`rsa.PublicKey`, :py:class:`rsa.PrivateKey`) - - The ``poolsize`` parameter was added in *Python-RSA 3.1* and requires - Python 2.6 or newer. - - ''' - - if nbits < 16: - raise ValueError('Key too small') - - if poolsize < 1: - raise ValueError('Pool size (%i) should be >= 1' % poolsize) - - # Determine which getprime function to use - if poolsize > 1: - from rsa import parallel - import functools - - getprime_func = functools.partial(parallel.getprime, poolsize=poolsize) - else: getprime_func = rsa.prime.getprime - - # Generate the key components - (p, q, e, d) = gen_keys(nbits, getprime_func) - - # Create the key objects - n = p * q - - return ( - PublicKey(n, e), - PrivateKey(n, e, d, p, q) - ) - -__all__ = ['PublicKey', 'PrivateKey', 'newkeys'] - -if __name__ == '__main__': - import doctest - - try: - for count in range(100): - (failures, tests) = doctest.testmod() - if failures: - break - - if (count and count % 10 == 0) or count == 1: - print('%i times' % count) - except KeyboardInterrupt: - print('Aborted') - else: - print('Doctests done') diff --git a/rsa/parallel.py b/rsa/parallel.py deleted file mode 100644 index e5034ac7..00000000 --- a/rsa/parallel.py +++ /dev/null @@ -1,94 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -'''Functions for parallel computation on multiple cores. - -Introduced in Python-RSA 3.1. - -.. note:: - - Requires Python 2.6 or newer. - -''' - -from __future__ import print_function - -import multiprocessing as mp - -import rsa.prime -import rsa.randnum - -def _find_prime(nbits, pipe): - while True: - integer = rsa.randnum.read_random_int(nbits) - - # Make sure it's odd - integer |= 1 - - # Test for primeness - if rsa.prime.is_prime(integer): - pipe.send(integer) - return - -def getprime(nbits, poolsize): - '''Returns a prime number that can be stored in 'nbits' bits. - - Works in multiple threads at the same time. - - >>> p = getprime(128, 3) - >>> rsa.prime.is_prime(p-1) - False - >>> rsa.prime.is_prime(p) - True - >>> rsa.prime.is_prime(p+1) - False - - >>> from rsa import common - >>> common.bit_size(p) == 128 - True - - ''' - - (pipe_recv, pipe_send) = mp.Pipe(duplex=False) - - # Create processes - procs = [mp.Process(target=_find_prime, args=(nbits, pipe_send)) - for _ in range(poolsize)] - [p.start() for p in procs] - - result = pipe_recv.recv() - - [p.terminate() for p in procs] - - return result - -__all__ = ['getprime'] - - -if __name__ == '__main__': - print('Running doctests 1000x or until failure') - import doctest - - for count in range(100): - (failures, tests) = doctest.testmod() - if failures: - break - - if count and count % 10 == 0: - print('%i times' % count) - - print('Doctests done') - diff --git a/rsa/parallel.pyc b/rsa/parallel.pyc deleted file mode 100644 index 0da0084b..00000000 Binary files a/rsa/parallel.pyc and /dev/null differ diff --git a/rsa/pem.py b/rsa/pem.py deleted file mode 100644 index b1c3a0ed..00000000 --- a/rsa/pem.py +++ /dev/null @@ -1,120 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -'''Functions that load and write PEM-encoded files.''' - -import base64 -from rsa._compat import b, is_bytes - -def _markers(pem_marker): - ''' - Returns the start and end PEM markers - ''' - - if is_bytes(pem_marker): - pem_marker = pem_marker.decode('utf-8') - - return (b('-----BEGIN %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 - @param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY' - when your file has '-----BEGIN RSA PRIVATE KEY-----' and - '-----END RSA PRIVATE KEY-----' markers. - - @return the base64-decoded content between the start and end markers. - - @raise ValueError: when the content is invalid, for example when the start - marker cannot be found. - - ''' - - (pem_start, pem_end) = _markers(pem_marker) - - pem_lines = [] - in_pem_part = False - - for line in contents.splitlines(): - line = line.strip() - - # Skip empty lines - if not line: - continue - - # Handle start marker - if line == pem_start: - if in_pem_part: - raise ValueError('Seen start marker "%s" twice' % pem_start) - - in_pem_part = True - continue - - # Skip stuff before first marker - if not in_pem_part: - continue - - # Handle end marker - if in_pem_part and line == pem_end: - in_pem_part = False - break - - # Load fields - if b(':') in line: - continue - - pem_lines.append(line) - - # Do some sanity checks - if not pem_lines: - raise ValueError('No PEM start marker "%s" found' % pem_start) - - if in_pem_part: - raise ValueError('No PEM end marker "%s" found' % pem_end) - - # Base64-decode the contents - pem = b('').join(pem_lines) - return base64.decodestring(pem) - - -def save_pem(contents, pem_marker): - '''Saves a PEM file. - - @param contents: the contents to encode in PEM format - @param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY' - when your file has '-----BEGIN RSA PRIVATE KEY-----' and - '-----END RSA PRIVATE KEY-----' markers. - - @return the base64-encoded content between the start and end markers. - - ''' - - (pem_start, pem_end) = _markers(pem_marker) - - b64 = base64.encodestring(contents).replace(b('\n'), b('')) - pem_lines = [pem_start] - - for block_start in range(0, len(b64), 64): - block = b64[block_start:block_start + 64] - pem_lines.append(block) - - pem_lines.append(pem_end) - pem_lines.append(b('')) - - return b('\n').join(pem_lines) - diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py deleted file mode 100644 index 1274fe39..00000000 --- a/rsa/pkcs1.py +++ /dev/null @@ -1,389 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -'''Functions for PKCS#1 version 1.5 encryption and signing - -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 - -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. - -WARNING: this module leaks information when decryption or verification fails. -The exceptions that are raised contain the Python traceback information, which -can be used to deduce where in the process the failure occurred. DO NOT PASS -SUCH INFORMATION to your users. -''' - -import hashlib -import os - -from rsa._compat import b -from rsa import common, transform, core, varblock - -# ASN.1 codes that describe the hash algorithm used. -HASH_ASN1 = { - 'MD5': b('\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10'), - 'SHA-1': b('\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14'), - 'SHA-256': b('\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20'), - 'SHA-384': b('\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30'), - 'SHA-512': b('\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40'), -} - -HASH_METHODS = { - 'MD5': hashlib.md5, - 'SHA-1': hashlib.sha1, - 'SHA-256': hashlib.sha256, - 'SHA-384': hashlib.sha384, - 'SHA-512': hashlib.sha512, -} - -class CryptoError(Exception): - '''Base class for all exceptions in this module.''' - -class DecryptionError(CryptoError): - '''Raised when decryption fails.''' - -class VerificationError(CryptoError): - '''Raised when verification fails.''' - -def _pad_for_encryption(message, target_length): - r'''Pads the message for encryption, returning the padded message. - - :return: 00 02 RANDOM_DATA 00 MESSAGE - - >>> block = _pad_for_encryption('hello', 16) - >>> len(block) - 16 - >>> block[0:2] - '\x00\x02' - >>> block[-6:] - '\x00hello' - - ''' - - max_msglength = target_length - 11 - msglength = len(message) - - if msglength > max_msglength: - raise OverflowError('%i bytes needed for message, but there is only' - ' space for %i' % (msglength, max_msglength)) - - # Get random padding - padding = b('') - padding_length = target_length - msglength - 3 - - # We remove 0-bytes, so we'll end up with less padding than we've asked for, - # so keep adding data until we're at the correct length. - while len(padding) < padding_length: - needed_bytes = padding_length - len(padding) - - # Always read at least 8 bytes more than we need, and trim off the rest - # after removing the 0-bytes. This increases the chance of getting - # enough bytes, especially when needed_bytes is small - new_padding = os.urandom(needed_bytes + 5) - new_padding = new_padding.replace(b('\x00'), b('')) - padding = padding + new_padding[:needed_bytes] - - assert len(padding) == padding_length - - return b('').join([b('\x00\x02'), - padding, - b('\x00'), - message]) - - -def _pad_for_signing(message, target_length): - r'''Pads the message for signing, returning the padded message. - - The padding is always a repetition of FF bytes. - - :return: 00 01 PADDING 00 MESSAGE - - >>> block = _pad_for_signing('hello', 16) - >>> len(block) - 16 - >>> block[0:2] - '\x00\x01' - >>> block[-6:] - '\x00hello' - >>> block[2:-6] - '\xff\xff\xff\xff\xff\xff\xff\xff' - - ''' - - max_msglength = target_length - 11 - msglength = len(message) - - if msglength > max_msglength: - raise OverflowError('%i bytes needed for message, but there is only' - ' space for %i' % (msglength, max_msglength)) - - padding_length = target_length - msglength - 3 - - return b('').join([b('\x00\x01'), - padding_length * b('\xff'), - b('\x00'), - message]) - - -def encrypt(message, pub_key): - '''Encrypts the given message using PKCS#1 v1.5 - - :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 - the ``n`` component of the public key. - :param pub_key: the :py:class:`rsa.PublicKey` to encrypt with. - :raise OverflowError: when the message is too large to fit in the padded - block. - - >>> from rsa import key, common - >>> (pub_key, priv_key) = key.newkeys(256) - >>> message = 'hello' - >>> crypto = encrypt(message, pub_key) - - The crypto text should be just as long as the public key 'n' component: - - >>> len(crypto) == common.byte_size(pub_key.n) - True - - ''' - - keylength = common.byte_size(pub_key.n) - padded = _pad_for_encryption(message, keylength) - - payload = transform.bytes2int(padded) - encrypted = core.encrypt_int(payload, pub_key.e, pub_key.n) - block = transform.int2bytes(encrypted, keylength) - - return block - -def decrypt(crypto, priv_key): - r'''Decrypts the given message using PKCS#1 v1.5 - - 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 - the message cannot be found. - - :param crypto: the crypto text as returned by :py:func:`rsa.encrypt` - :param priv_key: the :py:class:`rsa.PrivateKey` to decrypt with. - :raise DecryptionError: when the decryption fails. No details are given as - to why the code thinks the decryption fails, as this would leak - information about the private key. - - - >>> import rsa - >>> (pub_key, priv_key) = rsa.newkeys(256) - - It works with strings: - - >>> crypto = encrypt('hello', pub_key) - >>> decrypt(crypto, priv_key) - 'hello' - - And with binary data: - - >>> crypto = encrypt('\x00\x00\x00\x00\x01', pub_key) - >>> decrypt(crypto, priv_key) - '\x00\x00\x00\x00\x01' - - Altering the encrypted information will *likely* cause a - :py:class:`rsa.pkcs1.DecryptionError`. If you want to be *sure*, use - :py:func:`rsa.sign`. - - - .. warning:: - - Never display the stack trace of a - :py:class:`rsa.pkcs1.DecryptionError` 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. - - >>> crypto = encrypt('hello', pub_key) - >>> crypto = crypto[0:5] + 'X' + crypto[6:] # change a byte - >>> decrypt(crypto, priv_key) - Traceback (most recent call last): - ... - DecryptionError: Decryption failed - - ''' - - blocksize = common.byte_size(priv_key.n) - encrypted = transform.bytes2int(crypto) - decrypted = core.decrypt_int(encrypted, priv_key.d, priv_key.n) - cleartext = transform.int2bytes(decrypted, blocksize) - - # If we can't find the cleartext marker, decryption failed. - if cleartext[0:2] != b('\x00\x02'): - raise DecryptionError('Decryption failed') - - # Find the 00 separator between the padding and the message - try: - sep_idx = cleartext.index(b('\x00'), 2) - except ValueError: - raise DecryptionError('Decryption failed') - - return cleartext[sep_idx+1:] - -def sign(message, priv_key, hash): - '''Signs the message with the private key. - - 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. - - :param message: the message to sign. Can be an 8-bit string or a file-like - object. If ``message`` has a ``read()`` method, it is assumed to be a - file-like object. - :param priv_key: the :py:class:`rsa.PrivateKey` to sign with - :param hash: the hash method used on the message. Use 'MD5', 'SHA-1', - 'SHA-256', 'SHA-384' or 'SHA-512'. - :return: a message signature block. - :raise OverflowError: if the private key is too small to contain the - requested hash. - - ''' - - # Get the ASN1 code for this hash method - if hash not in HASH_ASN1: - raise ValueError('Invalid hash method: %s' % hash) - asn1code = HASH_ASN1[hash] - - # Calculate the hash - hash = _hash(message, hash) - - # Encrypt the hash with the private key - cleartext = asn1code + hash - keylength = common.byte_size(priv_key.n) - padded = _pad_for_signing(cleartext, keylength) - - payload = transform.bytes2int(padded) - encrypted = core.encrypt_int(payload, priv_key.d, priv_key.n) - block = transform.int2bytes(encrypted, keylength) - - return block - -def verify(message, signature, pub_key): - '''Verifies that the signature matches the message. - - The hash method is detected automatically from the signature. - - :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 - file-like object. - :param signature: the signature block, as created with :py:func:`rsa.sign`. - :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message. - :raise VerificationError: when the signature doesn't match the message. - - .. warning:: - - Never display the stack trace of a - :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) - decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n) - clearsig = transform.int2bytes(decrypted, blocksize) - - # If we can't find the signature marker, verification failed. - if clearsig[0:2] != b('\x00\x01'): - 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) - - # Compare the real hash to the hash in the signature - if message_hash != signature_hash: - raise VerificationError('Verification failed') - -def _hash(message, method_name): - '''Returns the message digest. - - :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 - file-like object. - :param method_name: the hash method, must be a key of - :py:const:`HASH_METHODS`. - - ''' - - if method_name not in HASH_METHODS: - raise ValueError('Invalid hash method: %s' % method_name) - - method = HASH_METHODS[method_name] - hasher = method() - - if hasattr(message, 'read') and hasattr(message.read, '__call__'): - # read as 1K blocks - for block in varblock.yield_fixedblocks(message, 1024): - hasher.update(block) - else: - # hash the message object itself. - hasher.update(message) - - return hasher.digest() - - -def _find_method_hash(method_hash): - '''Finds the hash method and the hash itself. - - :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. - - :raise VerificationFailed: when the hash method cannot be found - - ''' - - for (hashname, asn1code) in HASH_ASN1.items(): - if not method_hash.startswith(asn1code): - continue - - return (hashname, method_hash[len(asn1code):]) - - raise VerificationError('Verification failed') - - -__all__ = ['encrypt', 'decrypt', 'sign', 'verify', - 'DecryptionError', 'VerificationError', 'CryptoError'] - -if __name__ == '__main__': - print('Running doctests 1000x or until failure') - import doctest - - for count in range(1000): - (failures, tests) = doctest.testmod() - if failures: - break - - if count and count % 100 == 0: - print('%i times' % count) - - print('Doctests done') diff --git a/rsa/prime.py b/rsa/prime.py deleted file mode 100644 index 7422eb1d..00000000 --- a/rsa/prime.py +++ /dev/null @@ -1,166 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -'''Numerical functions related to primes. - -Implementation based on the book Algorithm Design by Michael T. Goodrich and -Roberto Tamassia, 2002. -''' - -__all__ = [ 'getprime', 'are_relatively_prime'] - -import rsa.randnum - -def gcd(p, q): - '''Returns the greatest common divisor of p and q - - >>> gcd(48, 180) - 12 - ''' - - while q != 0: - if p < q: (p,q) = (q,p) - (p,q) = (q, p % q) - return p - - -def jacobi(a, b): - '''Calculates the value of the Jacobi symbol (a/b) where both a and b are - positive integers, and b is odd - - :returns: -1, 0 or 1 - ''' - - assert a > 0 - assert b > 0 - - if a == 0: return 0 - result = 1 - while a > 1: - 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): - '''Returns False if n is an Euler pseudo-prime with base x, and - True otherwise. - ''' - - j = jacobi(x, n) % n - - f = pow(x, n >> 1, n) - - 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) - - for _ in range(k): - x = rsa.randnum.randint(n-1) - if jacobi_witness(x, n): return False - - return True - -def is_prime(number): - '''Returns True if the number is prime, and False otherwise. - - >>> is_prime(42) - False - >>> is_prime(41) - True - ''' - - return randomized_primality_testing(number, 6) - -def getprime(nbits): - '''Returns a prime number that can be stored in 'nbits' bits. - - >>> p = getprime(128) - >>> is_prime(p-1) - False - >>> is_prime(p) - True - >>> is_prime(p+1) - False - - >>> from rsa import common - >>> common.bit_size(p) == 128 - True - - ''' - - while True: - integer = rsa.randnum.read_random_int(nbits) - - # Make sure it's odd - integer |= 1 - - # Test for primeness - if is_prime(integer): - return integer - - # Retry if not prime - - -def are_relatively_prime(a, b): - '''Returns True if a and b are relatively prime, and False if they - are not. - - >>> are_relatively_prime(2, 3) - 1 - >>> are_relatively_prime(2, 4) - 0 - ''' - - d = gcd(a, b) - return (d == 1) - -if __name__ == '__main__': - print('Running doctests 1000x or until failure') - import doctest - - for count in range(1000): - (failures, tests) = doctest.testmod() - if failures: - break - - if count and count % 100 == 0: - print('%i times' % count) - - print('Doctests done') diff --git a/rsa/randnum.py b/rsa/randnum.py deleted file mode 100644 index 0e782744..00000000 --- a/rsa/randnum.py +++ /dev/null @@ -1,85 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -'''Functions for generating random numbers.''' - -# Source inspired by code by Yesudeep Mangalapilly - -import os - -from rsa import common, transform -from rsa._compat import byte - -def read_random_bits(nbits): - '''Reads 'nbits' random bits. - - If nbits isn't a whole number of bytes, an extra byte will be appended with - only the lower bits set. - ''' - - nbytes, rbits = divmod(nbits, 8) - - # Get the random bytes - randomdata = os.urandom(nbytes) - - # Add the remaining random bits - if rbits > 0: - randomvalue = ord(os.urandom(1)) - randomvalue >>= (8 - rbits) - randomdata = byte(randomvalue) + randomdata - - return randomdata - - -def read_random_int(nbits): - '''Reads a random integer of approximately nbits bits. - ''' - - randomdata = read_random_bits(nbits) - value = transform.bytes2int(randomdata) - - # Ensure that the number is large enough to just fill out the required - # number of bits. - value |= 1 << (nbits - 1) - - return value - -def randint(maxvalue): - '''Returns a random integer x with 1 <= x <= maxvalue - - 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 - is. - ''' - - bit_size = common.bit_size(maxvalue) - - tries = 0 - while True: - value = read_random_int(bit_size) - if value <= maxvalue: - break - - if tries and tries % 10 == 0: - # After a lot of tries to get the right number of bits but still - # smaller than maxvalue, decrease the number of bits by 1. That'll - # dramatically increase the chances to get a large enough number. - bit_size -= 1 - tries += 1 - - return value - - diff --git a/rsa/transform.py b/rsa/transform.py deleted file mode 100644 index c740b2d2..00000000 --- a/rsa/transform.py +++ /dev/null @@ -1,220 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -'''Data transformation functions. - -From bytes to a number, number to bytes, etc. -''' - -from __future__ import absolute_import - -try: - # We'll use psyco if available on 32-bit architectures to speed up code. - # Using psyco (if available) cuts down the execution time on Python 2.5 - # at least by half. - import psyco - psyco.full() -except ImportError: - pass - -import binascii -from struct import pack -from rsa import common -from rsa._compat import is_integer, b, byte, get_word_alignment, ZERO_BYTE, EMPTY_BYTE - - -def bytes2int(raw_bytes): - 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. - - >>> (((128 * 256) + 64) * 256) + 15 - 8405007 - >>> bytes2int('\x80@\x0f') - 8405007 - - ''' - - return int(binascii.hexlify(raw_bytes), 16) - - -def _int2bytes(number, block_size=None): - r'''Converts a number to a string of bytes. - - Usage:: - - >>> _int2bytes(123456789) - '\x07[\xcd\x15' - >>> bytes2int(_int2bytes(123456789)) - 123456789 - - >>> _int2bytes(123456789, 6) - '\x00\x00\x07[\xcd\x15' - >>> bytes2int(_int2bytes(123456789, 128)) - 123456789 - - >>> _int2bytes(123456789, 3) - Traceback (most recent call last): - ... - OverflowError: Needed 4 bytes for number, but block size is 3 - - @param number: the number to convert - @param block_size: the number of bytes to output. If the number encoded to - bytes is less than this, the block will be zero-padded. When not given, - the returned block is not padded. - - @throws OverflowError when block_size is given and the number takes up more - bytes than fit into the block. - ''' - # Type checking - if not is_integer(number): - raise TypeError("You must pass an integer for 'number', not %s" % - number.__class__) - - if number < 0: - raise ValueError('Negative numbers cannot be used: %i' % number) - - # Do some bounds checking - if number == 0: - needed_bytes = 1 - raw_bytes = [ZERO_BYTE] - else: - needed_bytes = common.byte_size(number) - raw_bytes = [] - - # You cannot compare None > 0 in Python 3x. It will fail with a TypeError. - if block_size and block_size > 0: - if needed_bytes > block_size: - raise OverflowError('Needed %i bytes for number, but block size ' - 'is %i' % (needed_bytes, block_size)) - - # Convert the number to bytes. - while number > 0: - raw_bytes.insert(0, byte(number & 0xFF)) - number >>= 8 - - # Pad with zeroes to fill the block - if block_size and block_size > 0: - padding = (block_size - needed_bytes) * ZERO_BYTE - else: - padding = EMPTY_BYTE - - return padding + EMPTY_BYTE.join(raw_bytes) - - -def bytes_leading(raw_bytes, needle=ZERO_BYTE): - ''' - Finds the number of prefixed byte occurrences in the haystack. - - Useful when you want to deal with padding. - - :param raw_bytes: - Raw bytes. - :param needle: - The byte to count. Default \000. - :returns: - The number of leading needle bytes. - ''' - leading = 0 - # Indexing keeps compatibility between Python 2.x and Python 3.x - _byte = needle[0] - for x in raw_bytes: - if x == _byte: - leading += 1 - else: - break - return leading - - -def int2bytes(number, fill_size=None, chunk_size=None, overflow=False): - ''' - Convert an unsigned integer to bytes (base-256 representation):: - - Does not preserve leading zeros if you don't specify a chunk size or - fill size. - - .. NOTE: - You must not specify both fill_size and chunk_size. Only one - of them is allowed. - - :param number: - Integer value - :param fill_size: - If the optional fill size is given the length of the resulting - byte string is expected to be the fill size and will be padded - with prefix zero bytes to satisfy that length. - :param chunk_size: - If optional chunk size is given and greater than zero, pad the front of - the byte string with binary zeros so that the length is a multiple of - ``chunk_size``. - :param overflow: - ``False`` (default). If this is ``True``, no ``OverflowError`` - will be raised when the fill_size is shorter than the length - of the generated byte sequence. Instead the byte sequence will - be returned as is. - :returns: - Raw bytes (base-256 representation). - :raises: - ``OverflowError`` when fill_size is given and the number takes up more - bytes than fit into the block. This requires the ``overflow`` - argument to this function to be set to ``False`` otherwise, no - error will be raised. - ''' - if number < 0: - raise ValueError("Number must be an unsigned integer: %d" % number) - - if fill_size and chunk_size: - raise ValueError("You can either fill or pad chunks, but not both") - - # Ensure these are integers. - number & 1 - - raw_bytes = b('') - - # Pack the integer one machine word at a time into bytes. - num = number - word_bits, _, max_uint, pack_type = get_word_alignment(num) - pack_format = ">%s" % pack_type - while num > 0: - raw_bytes = pack(pack_format, num & max_uint) + raw_bytes - num >>= word_bits - # Obtain the index of the first non-zero byte. - zero_leading = bytes_leading(raw_bytes) - if number == 0: - raw_bytes = ZERO_BYTE - # De-padding. - raw_bytes = raw_bytes[zero_leading:] - - length = len(raw_bytes) - if fill_size and fill_size > 0: - if not overflow and length > fill_size: - raise OverflowError( - "Need %d bytes for number, but fill size is %d" % - (length, fill_size) - ) - raw_bytes = raw_bytes.rjust(fill_size, ZERO_BYTE) - elif chunk_size and chunk_size > 0: - remainder = length % chunk_size - if remainder: - padding_size = chunk_size - remainder - raw_bytes = raw_bytes.rjust(length + padding_size, ZERO_BYTE) - return raw_bytes - - -if __name__ == '__main__': - import doctest - doctest.testmod() - diff --git a/rsa/util.py b/rsa/util.py deleted file mode 100644 index 307bda5d..00000000 --- a/rsa/util.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -'''Utility functions.''' - -from __future__ import with_statement - -import sys -from optparse import OptionParser - -import rsa.key - -def private_to_public(): - '''Reads a private key and outputs the corresponding public key.''' - - # Parse the CLI options - parser = OptionParser(usage='usage: %prog [options]', - description='Reads a private key and outputs the ' - 'corresponding public key. Both private and public keys use ' - 'the format described in PKCS#1 v1.5') - - parser.add_option('-i', '--input', dest='infilename', type='string', - help='Input filename. Reads from stdin if not specified') - parser.add_option('-o', '--output', dest='outfilename', type='string', - help='Output filename. Writes to stdout of not specified') - - parser.add_option('--inform', dest='inform', - help='key format of input - default PEM', - choices=('PEM', 'DER'), default='PEM') - - parser.add_option('--outform', dest='outform', - help='key format of output - default PEM', - choices=('PEM', 'DER'), default='PEM') - - (cli, cli_args) = parser.parse_args(sys.argv) - - # Read the input data - if cli.infilename: - print >>sys.stderr, 'Reading private key from %s in %s format' % \ - (cli.infilename, cli.inform) - with open(cli.infilename) as infile: - in_data = infile.read() - else: - print >>sys.stderr, 'Reading private key from stdin in %s format' % \ - cli.inform - in_data = sys.stdin.read() - - - # Take the public fields and create a public key - priv_key = rsa.key.PrivateKey.load_pkcs1(in_data, cli.inform) - pub_key = rsa.key.PublicKey(priv_key.n, priv_key.e) - - # Save to the output file - out_data = pub_key.save_pkcs1(cli.outform) - - if cli.outfilename: - print >>sys.stderr, 'Writing public key to %s in %s format' % \ - (cli.outfilename, cli.outform) - with open(cli.outfilename, 'w') as outfile: - outfile.write(out_data) - else: - print >>sys.stderr, 'Writing public key to stdout in %s format' % \ - cli.outform - sys.stdout.write(out_data) - - diff --git a/rsa/varblock.py b/rsa/varblock.py deleted file mode 100644 index c7d96ae6..00000000 --- a/rsa/varblock.py +++ /dev/null @@ -1,155 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2011 Sybren A. Stüvel -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -'''VARBLOCK file support - -The VARBLOCK file format is as follows, where || denotes byte concatenation: - - FILE := VERSION || BLOCK || BLOCK ... - - BLOCK := LENGTH || DATA - - LENGTH := varint-encoded length of the subsequent data. Varint comes from - Google Protobuf, and encodes an integer into a variable number of bytes. - Each byte uses the 7 lowest bits to encode the value. The highest bit set - to 1 indicates the next byte is also part of the varint. The last byte will - have this bit set to 0. - -This file format is called the VARBLOCK format, in line with the varint format -used to denote the block sizes. - -''' - -from rsa._compat import byte, b - - -ZERO_BYTE = b('\x00') -VARBLOCK_VERSION = 1 - -def read_varint(infile): - '''Reads a varint from the file. - - 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 - raised. - - @param infile: the file-like object to read from. It should have a read() - method. - @returns (varint, length), the read varint and the number of read bytes. - ''' - - varint = 0 - read_bytes = 0 - - while True: - char = infile.read(1) - if len(char) == 0: - if read_bytes == 0: - return (0, 0) - raise EOFError('EOF while reading varint, value is %i so far' % - varint) - - byte = ord(char) - varint += (byte & 0x7F) << (7 * read_bytes) - - read_bytes += 1 - - if not byte & 0x80: - return (varint, read_bytes) - - -def write_varint(outfile, value): - '''Writes a varint to a file. - - @param outfile: the file-like object to write to. It should have a write() - method. - @returns the number of written bytes. - ''' - - # 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) - - if value == 0: - outfile.write(ZERO_BYTE) - return 1 - - written_bytes = 0 - while value > 0: - to_write = value & 0x7f - value = value >> 7 - - if value > 0: - to_write |= 0x80 - - outfile.write(byte(to_write)) - written_bytes += 1 - - return written_bytes - - -def yield_varblocks(infile): - '''Generator, yields each block in the input file. - - @param infile: file to read, is expected to have the VARBLOCK format as - described in the module's docstring. - @yields the contents of each block. - ''' - - # Check the version number - first_char = infile.read(1) - if len(first_char) == 0: - raise EOFError('Unable to read VARBLOCK version number') - - version = ord(first_char) - if version != VARBLOCK_VERSION: - raise ValueError('VARBLOCK version %i not supported' % version) - - while True: - (block_size, read_bytes) = read_varint(infile) - - # EOF at block boundary, that's fine. - if read_bytes == 0 and block_size == 0: - break - - block = infile.read(block_size) - - read_size = len(block) - if read_size != block_size: - raise EOFError('Block size is %i, but could read only %i bytes' % - (block_size, read_size)) - - yield block - - -def yield_fixedblocks(infile, blocksize): - '''Generator, yields each block of ``blocksize`` bytes in the input file. - - :param infile: file to read and separate in blocks. - :returns: a generator that yields the contents of each block - ''' - - while True: - block = infile.read(blocksize) - - read_bytes = len(block) - if read_bytes == 0: - break - - yield block - - if read_bytes < blocksize: - break -