faqts : Computers : Programming : Languages : Python : Snippets

+ Search
Add Entry AlertManage Folder Edit Entry Add page to http://del.icio.us/
Did You Find This Entry Useful?

24 of 27 people (89%) answered Yes
Recently 8 of 10 people (80%) answered Yes

Entry

RSA Cryptosystem

Jul 5th, 2000 10:02
Nathan Wallace, Hans Nowak, Snippet 246, dj trombley


"""
Packages: crypto
"""
"""
Here is an example of how to implement an RSA cryptosystem.  The
following is a usage example taken from my console, and along with 
the code comments should serve as adequate documentation:
Python 1.5.2 (#0, Apr 13 1999, 10:51:12) [MSC 32 bit (Intel)] on win32
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> from RSA import *
>>> msg = "The quick brown fox jumped over the lazy dog."
>>> k = RSAKey(128)
>>> k.keyType()
'Private'
>>> kpriv = dumps(k)
>>> k.makePublic()
>>> k.keyType()
'Public'
>>> xmsg = k.encryptString(msg)
>>> kp = loads(kpriv)
>>> kp.decryptString(xmsg)
'The quick brown fox jumped over the lazy dog.\000'
>>>
Please be sure to read the legal notices in the code.
Also note that everything in the first section (up until the comment 
"Cryptographic functions/constructs" consists of basic mathematic
routines
and is definitely safe to copy/export/whatever.
The block cipher implementation and algorithm are mine. 
(code outside of the RSAKey class)
-dj
"""
#-----------------------RSA.py-------------------------
# RSA-like public key cryptosystem for python.
# 1999 dj trombley <dtrom@building.clark.net>
# You are free to use and modify this code for
# any purpose with the restriction that any modifications
# including the author's name or contact information
# must include all of the original characters of the file.
# (you are free to comment out as you see fit, or to delete
# my contact information)
####################################################################
# NOTE: This code contains software capable of producing a "strong"#
#       (>128-bit) cryptosystem.  It may be illegal to export it   #
# out of your country, or even to merely possess it.  Use of       #
# this code is for informational purposes only, and is permitted   #
# only with the indemnification of the author by the user of any   #
# resulting liabilities.                                           #
####################################################################
# NOTE: This code may be protected under U.S. patent law by RSA.   #
# There may be significant restrictions if you want to include     #
# it in any sort of distributed end product.  The user is solely   #
# responsible for determining said restrictions, and indemnifies   #
# author from any liabilities resulting in the code's use.         #
####################################################################
# DISCLAIMER:  This code was entirely developed in, and exported   #
# from Australia.  It is merely an example of "How-To" implement   #
#        an RSA-like cryptosystem and is not intended for use in   #
#        a distributed or 'end-user' product.                      #
####################################################################
from whrandom import randint, choice
from math import log
from cPickle import dumps, loads
# Basic mathematic functions.
# (defined elsewhere, but this minimizes dependency)
digits = "0123456789"
# Compute a^b (mod n)
def powMod(a, b, n):
    x,m,r = b,a,1L
    while (x > 0L):
        if (x&1 == 1):
            r = r * m
        m = (m * m)%n
        x = x >> 1
    return(r%n)
# The Euclidean algorithm
def invMod(a, n):
    m,b,t,s = n,a,0L,1L
    q = m/b
    r = m - q * b
    while (r > 0L):
        z = t - q * s
        if (z >= 0):
            z = z%n
        else:
            z = n - ((0-z)%n)
        t,s,m,b = s,z,b,r
        q = m/b
        r = m - q * b
    if (b != 1):
        # Non-invertable element of Z_n
        return(0)
    else:
        return(s%n)
# Return a random long.
def randLong(dig):
    l = 0L
    for i in range(dig):
        l = l * 10
        l = l + int(choice(digits))
    return(l)
# Find the bit length of a positivge long
def bitLength(n, b = 2):
    c = 0
    while (n > 0):
        n = n/b
        c = c + 1
    return c
# Miller-Rabin pseudoprimality test
def MRpP(n, blen = None):
    if (n&1 == 0):
        return(0) # Even numbers are not prime for our purposes.
    # Divide all the 2s we can out of n-1
    k,m = 0,n-1
    while (m&1 == 0):
        m = m >> 1
        k = k + 1
    # Compute the digit length if it is not known.
    if (blen == None):
        blen = bitLength(n, 10)
    # Pick a seed and start shelling
    a = randLong(blen)
    b = powMod(a,m,n)
    if (b%n == 1):
        return(1)
    for i in range(k):
        if (b%n == n-1):
            return(1)
        b = (b*b)%n
    # Throw it out
    return(0)
# Return a random prime of approximately 'dig' digits.
def randPrime(dig, certainty = 50):
    # Pick a random odd integer.
    pstr = ""
    for i in range(dig):
        pstr = pstr + choice(digits)
    p = long(pstr)
    if (p&1 == 0):
        p = p + 1L
    # Run a single test, and then a bunch.
    found = 0
    blen = bitLength(p,10)
    while (found == 0):
        if (MRpP(p,blen) == 1):
            found = 1
            for i in range(certainty):
                if (MRpP(p,blen) == 0):
                    found = 0
        if (found == 0):
            p = p + 2L
            blen = bitLength(p,10)
    return(p)
# cryptographic functions/constructs.
PRIVATE = 0
PUBLIC = 1
# Block cipher routines.  These transform some text into a list of numbers
# suitable for encrypting with a public key cryptosystem.
# Split a string up into a list of blocks for use in a block cipher
# not susecptable to dictionary or trivial linear plaintext attacks.
def splitText(s, radix):
    # Pad the text to the radix.
    s = s + "\0"*((len(s)/radix)*radix-len(s)) + "\0"
    # Create the textblock list.
    blist = []
    for i in range(len(s)/radix + 1):
        blist = blist + [s[i*radix:(i+1)*radix]]
    # Add a byte of chaining information to each block.
    for i in range(len(blist)):
        blist[i] = chr(randint(5,250)) + blist[i]
    # Mask each of the bytes in each block with that block's
    # chain data.
        for x in range(len(blist[i])-1):
            blist[i] = blist[i][0:x+1] + chr(ord(blist[i][x+1]) ^ \
            ord(blist[i][0])) + blist[i][x+2:]
    # Mask each noninitial block's chain data with the previous block's
    # chain data byte.
    for i in range(len(blist)-1):
        blist[i+1] = chr(ord(blist[i+1][0]) ^ ord(blist[i][0])) + \
        blist[i+1][1:]
    # Transform each block into a number.
    nlist = []
    for i in blist:
        r = 0L
        for x in i:
            r = r << 8
            r = r + ord(x)
        nlist = nlist + [r]
    # Yay for block ciphers.
    return(nlist)
# The inverse of the above.
def joinText(nlist):
    # Transform each block into a string
    blist = []
    for i in nlist:
        s = ''
        while(i > 0):
            s = chr(int(i%256)) + s
            i = i >> 8
        blist = blist + [s]
    # Unmask the chaining info bytes.
    for i in range(len(blist)-1, 0, -1):
        blist[i]= chr(ord(blist[i-1][0]) ^ ord(blist[i][0])) + \
        blist[i][1:]
    # Unmask the actual text using the unmasked mask.
    for i in range(len(blist)):
        q =''
        for x in range(len(blist[i])):
            q = q + chr(ord(blist[i][x]) ^ ord(blist[i][0]))
        blist[i] = q
    # Remove the chaining bytes and concatenate the text.
    msg =''
    for i in range(len(blist)):
        blist[i] = blist[i][1:]
        msg = msg + blist[i]
    # Srehpic kcolb rof yay.
    return(msg)
class RSAKey:
    def __init__(self, bits = 128, cert = 50, edig = 7):
     # First find the number of base-10 digits we want.
     dig = int(log(2)/log(10)*(bits/2))
     # Create the primes.
     p = randPrime(dig, cert)
     q = randPrime(dig, cert)
     self.e = randPrime(edig, cert)
     # Store the modulus and decryption exponent
     self.ktype = PRIVATE
     self.n = p*q
     self.d = invMod(self.e,(p-1)*(q-1))
    def keyType(self):
        if (self.ktype == PRIVATE):
            return("Private")
        return("Public")
    def makePublic(self):
        del(self.d)
        self.d = 0
        self.ktype = PUBLIC
    def printKey(self):
     try:
         s = dumps(self)
     except:
         s = "Error dumping key"
     return(s)
    def encryptBlock(self, x):
        return(powMod(x,self.e,self.n))
    def decryptBlock(self, x):
        return(powMod(x,self.d,self.n))
    def encryptString(self, s):
        # First let's find an appropriate radix for this modulus
        r = bitLength(self.n, 256) - 2
        if (r < 2):
            raise Exception("Modulus too small")
        # Get the plaintext.
        pt = splitText(s, r)
        # Encrypt.
        xmsg = []
        for i in pt:
            xmsg = xmsg + [self.encryptBlock(i)]
        return(dumps(xmsg))
    def decryptString(self, s):
        if (self.ktype == PUBLIC):
            raise Exception("Cannot decrypt with a public key")
        xmsg = loads(s)
        pt = []
        # Decrypt.
        for i in xmsg:
            pt = pt + [self.decryptBlock(i)]
        # Get plaintext.
        return(joinText(pt))
#---------------------------------RSA.py------------------------------
# a little test, added by PSST
if __name__ == "__main__":
    rsakey = RSAKey()
    es = rsakey.encryptString("Hello friends")
    print es