]> gitweb.pimeys.fr Git - NK2015_Client_Python_Alpha.git/commitdiff
on ajoute le module rsa car le client aussi en a besoin
authorVincent Le Gallic <legallic@crans.org>
Sat, 17 Mar 2012 17:50:24 +0000 (18:50 +0100)
committerVincent Le Gallic <legallic@crans.org>
Sat, 17 Mar 2012 17:50:24 +0000 (18:50 +0100)
17 files changed:
client_config.py
rsa_source/monRSA.py [new file with mode: 0644]
rsa_source/rsa/__init__.py [new file with mode: 0644]
rsa_source/rsa/_version133.py [new file with mode: 0644]
rsa_source/rsa/_version200.py [new file with mode: 0644]
rsa_source/rsa/bigfile.py [new file with mode: 0644]
rsa_source/rsa/cli.py [new file with mode: 0644]
rsa_source/rsa/common.py [new file with mode: 0644]
rsa_source/rsa/core.py [new file with mode: 0644]
rsa_source/rsa/key.py [new file with mode: 0644]
rsa_source/rsa/pem.py [new file with mode: 0644]
rsa_source/rsa/pkcs1.py [new file with mode: 0644]
rsa_source/rsa/prime.py [new file with mode: 0644]
rsa_source/rsa/randnum.py [new file with mode: 0644]
rsa_source/rsa/transform.py [new file with mode: 0644]
rsa_source/rsa/util.py [new file with mode: 0644]
rsa_source/rsa/varblock.py [new file with mode: 0644]

index a57b59a2d889d840ff977e50077e0d8179d22b61..5fa9c2500003f7690d0cba441198c11167ca72f8 100644 (file)
@@ -1,11 +1,10 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-basedir = "/usr/scripts/Note_Kfet_2015/"
-clientdir = basedir + "client/"
+clientdir = "./"  # pour le client, on a les chemins en relatif
 ca_certfile = clientdir + "keys/ca.crt"
 server_rsa_pub_key = clientdir + "keys/server_rsa_key.pub"
 
 # le module qui fait du rsa
-rsa_path = "/usr/scripts/Note_Kfet_2015/rsa_source/"
+rsa_path = clientdir+ "rsa_source/"
 port = 4242
diff --git a/rsa_source/monRSA.py b/rsa_source/monRSA.py
new file mode 100644 (file)
index 0000000..b209517
--- /dev/null
@@ -0,0 +1,70 @@
+# -*- coding:utf8 -*-
+
+import rsa
+
+import base64
+
+
+
+def lit(fi):
+  f=open(fi,"r")
+  a=f.read()
+  f.close()
+  return a
+
+
+def decoupe(s,taille):
+  l=[]
+  tranche=s[0:taille]
+  ind=0
+  while tranche!='':
+    l.append(tranche)
+    ind+=1
+    tranche=s[ind*taille:(ind+1)*taille]
+  return l
+
+def crypte(message,pub):
+  """Crypte le message, même si il est est trop long.
+     sort une chaîne b64-encodée avec les sauts de lignes à chaque découpage"""
+  # NB : ici, trop long veut dire >245,
+  #  en vrai je sais pas comment on fait pour savoir
+  vraimessage=decoupe(message,245)
+  out=[]
+  for i in vraimessage:
+    lignecrypt=rsa.encrypt(i,pub)
+    out.append(base64.b64encode(lignecrypt))
+  return "\n".join(out)
+
+def decrypte(message,priv):
+  """Décrypte un long message, en prenant en entrée des lignes de b64"""
+  out=[]
+  vraimessage=message.split('\n')
+  for i in vraimessage:
+    lignecrypt=base64.b64decode(i)
+    out.append(rsa.decrypt(lignecrypt,priv))
+  return "".join(out)
+
+
+
+
+
+def litcles(privfile,pubfile):
+  priv,pub=None,None
+  if privfile!=None:
+    futurpriv=lit(privfile)
+    priv=rsa.PrivateKey.load_pkcs1(futurpriv)
+  if pubfile!=None:
+    futurpub=lit(pubfile)
+    pub=rsa.PublicKey.load_pkcs1(futurpub)
+  return priv,pub
+
+
+if __name__=="__main__":
+  priv,pub=litcles(privfile,pubfile)
+  message = raw_input("Entrez un message :")
+  print "message avant :\n%s\n"%(message)
+  message = crypte(message,pub)
+  print "message crypté (avec la clé publique)  :\n%s\n"%(message)
+  message = decrypte(message,priv)
+  print "message crypté décrypté :\n%s\n"%(message)
+
diff --git a/rsa_source/rsa/__init__.py b/rsa_source/rsa/__init__.py
new file mode 100644 (file)
index 0000000..f5e3a52
--- /dev/null
@@ -0,0 +1,45 @@
+# -*- coding: utf-8 -*-
+#
+#  Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      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, Marloes de Boer, Ivo Tamboer, and Barry Mead"
+__date__ = "2011-08-07"
+__version__ = '3.0.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_source/rsa/_version133.py b/rsa_source/rsa/_version133.py
new file mode 100644 (file)
index 0000000..1adae42
--- /dev/null
@@ -0,0 +1,440 @@
+"""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
+
+# 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<q: return gcd(q, p)
+    if q == 0: return p
+    return gcd(q, abs(p%q))
+
+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)
+    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" % (chr(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_source/rsa/_version200.py b/rsa_source/rsa/_version200.py
new file mode 100644 (file)
index 0000000..c297aee
--- /dev/null
@@ -0,0 +1,532 @@
+"""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
+
+# 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
+    
+    >>>int2bytes(123456789)
+    '\x07[\xcd\x15'
+    >>> 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" % (chr(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 chr(number + 48)
+
+    if 10 <= number <= 35:
+        return chr(number + 55)     #10-35 translates to 'A' - 'Z'
+
+    if 36 <= number <= 61:
+        return chr(number + 61)     #36-61 translates to 'a' - 'z'
+
+    if number == 62:                # 62   translates to '-' (minus)
+        return chr(45)
+
+    if number == 63:                # 63   translates to '_' (underscore)
+        return chr(95)
+
+    raise ValueError(u'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(u'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_source/rsa/bigfile.py b/rsa_source/rsa/bigfile.py
new file mode 100644 (file)
index 0000000..f930944
--- /dev/null
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+#
+#  Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      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
+
+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(chr(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_source/rsa/cli.py b/rsa_source/rsa/cli.py
new file mode 100644 (file)
index 0000000..012c77d
--- /dev/null
@@ -0,0 +1,377 @@
+# -*- coding: utf-8 -*-
+#
+#  Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      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.
+'''
+
+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 >>sys.stderr, 'Not a valid number: %s' % cli_args[0]
+        raise SystemExit(1)
+
+    print >>sys.stderr, 'Generating %i-bit key' % keysize
+    (pub_key, priv_key) = rsa.newkeys(keysize)
+
+
+    # Save public key
+    if cli.pubout:
+        print >>sys.stderr, 'Writing public key to %s' % cli.pubout
+        data = pub_key.save_pkcs1(format=cli.form)
+        with open(cli.pubout, 'w') as outfile:
+            outfile.write(data)
+
+    # Save private key
+    data = priv_key.save_pkcs1(format=cli.form)
+    
+    if cli.out:
+        print >>sys.stderr, 'Writing private key to %s' % cli.out
+        with open(cli.out, 'w') as outfile:
+            outfile.write(data)
+    else:
+        print >>sys.stderr, 'Writing private key to stdout'
+        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 >>sys.stderr, self.operation_progressive.title()
+        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 >>sys.stderr, 'Reading %s key from %s' % (self.keyname, filename)
+        with open(filename) 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 >>sys.stderr, 'Reading input from %s' % inname
+            with open(inname, 'rb') as infile:
+                return infile.read()
+
+        print >>sys.stderr, 'Reading input from stdin'
+        return sys.stdin.read()
+
+    def write_outfile(self, outdata, outname):
+        '''Write the output file'''
+
+        if outname:
+            print >>sys.stderr, 'Writing output to %s' % outname
+            with open(outname, 'wb') as outfile:
+                outfile.write(outdata)
+        else:
+            print >>sys.stderr, 'Writing output to stdout'
+            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 >>sys.stderr, 'Verification OK'
+
+
+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 >>sys.stderr, self.operation_progressive.title()
+        self.perform_operation(infile, outfile, key, cli_args)
+
+    def get_infile(self, inname):
+        '''Returns the input file object'''
+
+        if inname:
+            print >>sys.stderr, 'Reading input from %s' % inname
+            fobj = open(inname, 'rb')
+            self.file_objects.append(fobj)
+        else:
+            print >>sys.stderr, 'Reading input from stdin'
+            fobj = sys.stdin
+
+        return fobj
+
+    def get_outfile(self, outname):
+        '''Returns the output file object'''
+
+        if outname:
+            print >>sys.stderr, 'Will write output to %s' % outname
+            fobj = open(outname, 'wb')
+            self.file_objects.append(fobj)
+        else:
+            print >>sys.stderr, 'Will write output to stdout'
+            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_source/rsa/common.py b/rsa_source/rsa/common.py
new file mode 100644 (file)
index 0000000..5ae92f0
--- /dev/null
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+#
+#  Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      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.'''
+
+
+import math
+
+def bit_size(number):
+    '''Returns the number of bits required to hold a specific long number.
+
+    >>> bit_size(1023)
+    10
+    >>> bit_size(1024)
+    11
+    >>> bit_size(1025)
+    11
+
+    >>> bit_size(1 << 1024)
+    1025
+    >>> bit_size((1 << 1024) + 1)
+    1025
+    >>> bit_size((1 << 1024) - 1)
+    1024
+
+    '''
+
+    if number < 0:
+        raise ValueError('Only nonnegative numbers possible: %s' % number)
+
+    if number == 0:
+        return 1
+    
+    # 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.
+
+    >>> byte_size(1 << 1023)
+    128
+    >>> byte_size((1 << 1024) - 1)
+    128
+    >>> byte_size(1 << 1024)
+    129
+    """
+
+    return int(math.ceil(bit_size(number) / 8.0))
+
+if __name__ == '__main__':
+    import doctest
+    doctest.testmod()
+
diff --git a/rsa_source/rsa/core.py b/rsa_source/rsa/core.py
new file mode 100644 (file)
index 0000000..cc95f59
--- /dev/null
@@ -0,0 +1,60 @@
+# -*- coding: utf-8 -*-
+#
+#  Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      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.
+'''
+
+import types
+
+def assert_int(var, name):
+
+    if type(var) in (types.IntType, types.LongType):
+        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"""
+
+    if type(cyphertext) not in (types.IntType, types.LongType):
+        raise TypeError('cyphertext should be an integer, not %s' %
+                cyphertext.__type__)
+
+    assert_int(cyphertext, 'cyphertext')
+    assert_int(dkey, 'dkey')
+    assert_int(n, 'n')
+
+    message = pow(cyphertext, dkey, n)
+    return message
+
diff --git a/rsa_source/rsa/key.py b/rsa_source/rsa/key.py
new file mode 100644 (file)
index 0000000..031c7e9
--- /dev/null
@@ -0,0 +1,583 @@
+# -*- coding: utf-8 -*-
+#
+#  Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      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
+
+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 u'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, _) = extended_gcd(q, p)
+        else:
+            self.coef = coef
+
+    def __getitem__(self, key):
+        return getattr(self, key)
+
+    def __repr__(self):
+        return u'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, '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, 'RSA PRIVATE KEY')
+
+
+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 find_p_q(nbits, 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 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 = rsa.prime.getprime(pbits)
+    log.debug('find_p_q(%i): Finding q', nbits)
+    q = rsa.prime.getprime(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
+    tries = 0
+    while not is_acceptable(p, q):
+        tries += 1
+        # Change p on one iteration and q on the other
+        if change_p:
+            log.debug('   find another p')
+            p = rsa.prime.getprime(pbits)
+        else:
+            log.debug('   find another q')
+            q = rsa.prime.getprime(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
+
+    (divider, d, _) = extended_gcd(e, phi_n)
+
+    if divider != 1:
+        raise ValueError("e (%d) and phi_n (%d) are not relatively prime" %
+                (e, phi_n))
+    if (d < 0):
+        raise ValueError("extended_gcd shouldn't return negative values, "
+                "please file a bug")
+    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, 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.
+    """
+
+    (p, q) = find_p_q(nbits // 2, accurate)
+    (e, d) = calculate_keys(p, q, nbits // 2)
+
+    return (p, q, e, d)
+
+def newkeys(nbits, accurate=True):
+    """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:`PublicKey` object. The private key is also known as the
+    'decryption key' and is a :py:class:`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.
+
+    :returns: a tuple (:py:class:`rsa.PublicKey`, :py:class:`rsa.PrivateKey`)
+    
+    """
+
+    if nbits < 16:
+        raise ValueError('Key too small')
+
+    (p, q, e, d) = gen_keys(nbits)
+    
+    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_source/rsa/pem.py b/rsa_source/rsa/pem.py
new file mode 100644 (file)
index 0000000..9ea9f03
--- /dev/null
@@ -0,0 +1,118 @@
+# -*- coding: utf-8 -*-
+#
+#  Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      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
+
+def _markers(pem_marker):
+    '''Returns the start and end PEM markers
+
+    >>> _markers('RSA PRIVATE KEY')
+    ('-----BEGIN RSA PRIVATE KEY-----', '-----END RSA PRIVATE KEY-----')
+
+    '''
+
+    return ('-----BEGIN %s-----' % pem_marker,
+            '-----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.split('\n'):
+        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 ':' 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 = ''.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('\n', '')
+    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('')
+
+    return '\n'.join(pem_lines)
+    
diff --git a/rsa_source/rsa/pkcs1.py b/rsa_source/rsa/pkcs1.py
new file mode 100644 (file)
index 0000000..fbe9fe7
--- /dev/null
@@ -0,0 +1,388 @@
+# -*- coding: utf-8 -*-
+#
+#  Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      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 import common, transform, core, varblock
+
+# ASN.1 codes that describe the hash algorithm used.
+HASH_ASN1 = {
+    'MD5': '\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10',
+    'SHA-1': '\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14',
+    'SHA-256': '\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20',
+    'SHA-384': '\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30',
+    'SHA-512': '\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 = ''
+    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('\x00', '')
+        padding = padding + new_padding[:needed_bytes]
+    
+    assert len(padding) == padding_length
+    
+    return ''.join(['\x00\x02',
+                    padding,
+                    '\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 ''.join(['\x00\x01',
+                    padding_length * '\xff',
+                    '\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 = 'X' + crypto[1:] # change the first 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] != '\x00\x02':
+        raise DecryptionError('Decryption failed')
+    
+    # Find the 00 separator between the padding and the message
+    try:
+        sep_idx = cleartext.index('\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] != '\x00\x01':
+        raise VerificationError('Verification failed')
+    
+    # Find the 00 separator between the padding and the payload
+    try:
+        sep_idx = clearsig.index('\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.iteritems():
+        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_source/rsa/prime.py b/rsa_source/rsa/prime.py
new file mode 100644 (file)
index 0000000..4b2cb2e
--- /dev/null
@@ -0,0 +1,148 @@
+# -*- coding: utf-8 -*-
+#
+#  Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      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.'''
+
+__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
+    """
+
+    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 _ 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_source/rsa/randnum.py b/rsa_source/rsa/randnum.py
new file mode 100644 (file)
index 0000000..a6a635b
--- /dev/null
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+#
+#  Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      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 <yesudeep@gmail.com>
+
+import os
+
+from rsa import common, transform
+
+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 = chr(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_source/rsa/transform.py b/rsa_source/rsa/transform.py
new file mode 100644 (file)
index 0000000..2778729
--- /dev/null
@@ -0,0 +1,103 @@
+# -*- coding: utf-8 -*-
+#
+#  Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      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.
+'''
+
+import types
+import binascii
+
+from rsa import common
+
+def bytes2int(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(bytes), 16)
+
+def int2bytes(number, block_size=None):
+    r'''Converts a number to a string of bytes.
+
+    @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.
+
+
+    >>> 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
+
+    '''
+
+    # Type checking
+    if type(number) not in (types.LongType, types.IntType):
+        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 block_size is not None:
+        needed_bytes = common.byte_size(number)
+        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.
+    bytes = []
+    while number > 0:
+        bytes.insert(0, chr(number & 0xFF))
+        number >>= 8
+
+    # Pad with zeroes to fill the block
+    if block_size is not None:
+        padding = (block_size - needed_bytes) * '\x00'
+    else:
+        padding = ''
+
+    return padding + ''.join(bytes)
+
+
+if __name__ == '__main__':
+    import doctest
+    doctest.testmod()
+
diff --git a/rsa_source/rsa/util.py b/rsa_source/rsa/util.py
new file mode 100644 (file)
index 0000000..db6944e
--- /dev/null
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+#
+#  Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      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.'''
+
+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_source/rsa/varblock.py b/rsa_source/rsa/varblock.py
new file mode 100644 (file)
index 0000000..b8bd899
--- /dev/null
@@ -0,0 +1,149 @@
+# -*- coding: utf-8 -*-
+#
+#  Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
+#
+#  Licensed under the Apache License, Version 2.0 (the "License");
+#  you may not use this file except in compliance with the License.
+#  You may obtain a copy of the License at
+#
+#      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.
+
+'''
+
+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('\x00')
+        return 1
+
+    written_bytes = 0
+    while value > 0:
+        to_write = value & 0x7f
+        value = value >> 7
+
+        if value > 0:
+            to_write |= 0x80
+
+        outfile.write(chr(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
+