#@+leo
#@+node:0::@file easy/ezPyCrypto.py
#@+body


#@@language python
#@<< ezPyCrypto declarations >>
#@+node:1::<< ezPyCrypto declarations >>
#@+body
"""
ezPyCrypto - very simple API for military-grade cryptography
in Python.

Designed to be approachable for even total crypto newbies,
this may be the only crypto API for Python you ever need.

Features:
 - Create, Import and Export public keys and public/private keypairs - easy
 - Encrypt and Decrypt arbitrary-sized pieces of data, such as
   strings or files
 - Open up 'streams', so this object can be used as an encrypting/decrypting
   filter - good for socket-based comms and crypto of large files
 - Sign and Verify documents without fuss
 - Create private keys with or without a passphrase
 - Export private keys with a different (or no) passphrase
 - Sensible defaults - no need to specify a zillion options (or any options
   at all) unless you  want to
 - Algorithms include RSA, ElGamal, DSA, ARC2, Blowfish, CAST, DES3, IDEA and RC5
   (default RSA and Blowfish)
 - Choose your own public and session key sizes (or accept defaults)

Contains an easily-used yet versatile cryptography class, called
L{key}, that performs stream and block encryption.

Packaged with a suite of very simple example programs, which demonstrate
ezPyCrypto and speed learning.

ezPyCrypto requires the PyCrypto library (which I have hand-picked from
several different Python crypto APIs, since it's the only
API that doesn't lead its programmers on a wild goose chase
of 3rd party libs, or require dozens/hundreds of lines of
code just to do basic stuff, or lack in documentation.
PyCrypto is available from http://pycrypto.sf.net)

PyCrypto is a very usable and well implemented lower-level
crypto API for Python. C backends give it speed, while
well designed OO interface makes it relatively fast to learn.
Also, it compiles cleanly and smoothly on Linux and Windows
with no dramas.

But I've written this module because PyCrypto is relatively
low-level, and does present a harder learning curve for newbies.

ezPyCrypto is written by David McNab <david@freenet.org.nz>
Released under the GNU General Public License.
No warranty, yada yada

Refer to the documentation for class 'key' for more info.
"""

from pdb import set_trace as trace
import pickle
import types
import base64
import zlib

import Crypto

from Crypto.PublicKey import ElGamal, DSA, RSA
from Crypto.Util.randpool import RandomPool
from Crypto.Util.number import getPrime
from Crypto.Cipher import ARC2, Blowfish, CAST, DES3, IDEA, RC5
from Crypto.Hash import MD5

#@-body
#@-node:1::<< ezPyCrypto declarations >>


#@+others
#@+node:2::exceptions
#@+body
# Define some exceptions for the various problems that can happen

class CryptoKeyError(Exception):
    "Attempt to import invalid key"


#@-body
#@-node:2::exceptions
#@+node:3::class key
#@+body
class key:
    """
    This may well be the only crypto class for Python that you'll ever need.
    Think of this class, and the ezPyCrypto module, as 'cryptography for
    the rest of us'.

    Designed to strike the optimal balance between ease of use, features
    and performance.

    Basic High-level methods:

     - L{encString} - encrypt a string
     - L{decString} - decrypt a string

     - L{encStringToAscii} - encrypt a string to a printable, mailable format
     - L{decStringFromAscii} - decrypt an ascii-format encrypted string

     - L{signString} - produce ascii-format signature of a string
     - L{verifyString} - verify a string against a signature

     - L{importKey} - import public key (and possibly private key too)
     - L{exportKey} - export public key only, as printable mailable string
     - L{exportKeyPrivate} - same, but export private key as well
     - L{makeNewKeys} - generate a new, random private/public key pair

    Middle-level (stream-oriented) methods:

     - L{encStart} - start a stream encryption session
     - L{encNext} - encrypt another piece of data
     - L{encEnd} - finalise stream encryption session

     - L{decStart} - start a stream decryption session
     - L{decNext} - decrypt the next piece of available data
     - L{decEnd} - finalise stream decryption session

    Low-level methods:

     - refer to the source code
    
    Principle of operation:

     - Data is encrypted with choice of symmetric block-mode session cipher
       (or default Blowfish if user doesn't care)
     - CFB block chaining is used for added security - each next block's
       key is affected by the previous block
     - The session key and initial value (IV) are encrypted against an RSA
       or ElGamal public key (user's choice, default RSA)
     - Each block in the stream is prepended with a 'length' byte, indicating
       how many bytes in the decrypted block are significant - needed when
       total data len mod block size is non-zero
     - Format of encrypted data is:
         - public key len - 2 bytes, little-endian - size of public key in bytes
        - public key - public key of recipient
        - block cipher len - unencrypted length byte - size of block cipher in bytes
        - block cipher - encrypted against public key, index into array
          of session algorithms
        - block key len - unencrypted length byte - size of block key in bytes
        - block key - encrypted against public key
        - block IV len - unencrypted length of block cipher IV - IV length in bytes
        - block cipher IV - encrypted against public key, prefixed 1-byte length
        - block1 len - 1 byte - number of significant chars in block1 *
        - block1 data - always 8 bytes, encrypted against session key
        - ...
        - blockn len
        - blockn data
        - If last data block is of the same size as the session cipher blocksize,
          a final byte 0x00 is sent.
    """
    
    #@<< class key declarations >>
    #@+node:1::<< class key declarations >>
    #@+body
    # Various lookup tables for encryption algorithms
    
    _algosPub = {'ElGamal':ElGamal, 'RSA':RSA}
    
    _algosPub1 = {ElGamal:'ElGamal', RSA:'RSA'}
    
    _algosSes = { "ARC2":ARC2, "Blowfish":Blowfish, "CAST":CAST,
                  "DES3":DES3, "IDEA":IDEA, "RC5":RC5}
    _algosSes1 = {'ARC2':0, 'Blowfish':1, 'CAST':2, 'DES3':3, 'IDEA':4, 'RC5':5}
    
    _algosSes2 = [ARC2, Blowfish, CAST, DES3, IDEA, RC5]
    
    _algosSes3 = {ARC2:'ARC2', Blowfish:'Blowfish', CAST:'CAST',
                  DES3:'DES3', IDEA:'IDEA', RC5:'RC5'}
    
    # Generate IV for passphrase encryption
    _passIV = "w8Z4(51fKH#p{!29Q05HWcb@K 6(1qdyv{9|4=+gvji$chw!9$38^2cyGK#;}'@DHx%3)q_skvh4#0*="
    
    # Buffer for yet-to-be-encrypted stream data
    _encBuf = ''
    
    #@-body
    #@-node:1::<< class key declarations >>


    #@+others
    #@+node:2::__init__
    #@+body
    def __init__(self, something = 512, algoPub=None, algoSess=None, **kwds):
        """Constructor. Creates a key object
    
        This constructor, when creating the key object, does one of
        two things:
         1. Creates a completely new keypair, OR
         2. Imports an existing keypair
    
        Arguments:
         1. If new keys are desired:
             - key size in bits (int), default 512 - advise at least 1536
             - algoPub - either 'RSA' or 'ElGamal' (default 'RSA')
             - algoSess - one of 'ARC2', 'Blowfish', 'CAST', 'DES3', 'IDEA', 'RC5',
               (default 'Blowfish')
         2. If importing an existing key or keypair:
             - keyobj (string) - result of a prior exportKey() call
        Keywords:
         - passphrase - default '':
            - If creating new keypair, this passphrase is used to encrypt privkey when
              exporting.
            - If importing a new keypair, the passphrase is used to authenticate and
              grant/deny access to private key
        """
        passphrase = kwds.get('passphrase', '')
    
        if type(something) is types.IntType:
            # which public key algorithm did they choose?
            if algoPub == None:
                algoPub = 'RSA'
            algoP = self._algosPub.get(algoPub, None)
            if algoP == None:
                # Whoops - don't know that one
                raise Exception("AlgoPub must be one of 'ElGamel', 'RSA' or 'DSA'")
            self.algoPub = algoP
            self.algoPname = algoPub
    
            # which session key algorithm?
            if algoSess == None:
                algoSess = 'Blowfish'
            algoS = self._algosSes.get(algoSess, None)
            if algoS == None:
                # Whoops - don't know that session algorithm
                raise Exception("AlgoSess must be one of AES/ARC2/Blowfish/CAST/DES/DES3/IDEA/RC5")
            self.algoSes = algoS
            self.algoSname = algoSess
    
            # organise random data pool
            self.randpool = RandomPool()
            self.randfunc = self.randpool.get_bytes
    
            # now create the keypair
            self.makeNewKeys(something, passphrase=passphrase)
    
        elif type(something) is types.StringType:
            if algoPub != None:
                raise Exception("Don't specify algoPub if importing a key")
            if self.importKey(something, passphrase=passphrase) == False:
                raise CryptoKeyError(
                    "Attempted to import invalid key, or passphrase is bad")

            if algoSess == None:
                algoSess = 'Blowfish'
            algoS = self._algosSes.get(algoSess, None)
            if algoS == None:
                # Whoops - don't know that session algorithm
                raise Exception("AlgoSess must be one of AES/ARC2/Blowfish/CAST/DES/DES3/IDEA/RC5")
            self.algoSes = algoS
            self.algoSname = algoSess

            self.randpool = RandomPool()
            self.randfunc = self.randpool.get_bytes
        else:
            raise Exception("Must pass keysize or importable keys")
    
    #@-body
    #@-node:2::__init__
    #@+node:3::makeNewKeys()
    #@+body
    def makeNewKeys(self, keysize=512, **kwds):
        """
        Creates a new keypair in cipher object, and a new session key
    
        Arguments:
         - keysize (default 512), advise at least 1536
        Returns:
         - None
        Keywords:
         - passphrase - used to secure exported private key - default '' (no passphrase)
    
        Keypair gets stored within the key object. Refer L{exportKey},
        L{exportKeyPrivate} and L{importKey}.
        
        Generally no need to call this yourself, since the constructor
        calls this in cases where you aren't instantiating with an
        importable key.
        """
    
        passphrase = kwds.get('passphrase', '')
        if passphrase == None:
            passphrase = ''
        self.passphrase = passphrase
    
        # set up a public key object
        self.randpool.stir()
        self.k = self.algoPub.generate(keysize, self.randfunc)
        self.randpool.stir()
        self._calcPubBlkSize()
    
        # Generate random session key
        self._genNewSessKey()
    
        # Create session cipher object
        self.randpool.stir()
    
        #trace()
    
        # Create a new block cipher object
        self._initBlkCipher()
    
    #@-body
    #@-node:3::makeNewKeys()
    #@+node:4::importKey()
    #@+body
    def importKey(self, keystring, **kwds):
        """
        Imports a public key or private/public key pair.
    
        (as previously exported from this object
        with the L{exportKey} or L{exportKeyPrivate} methods.)
    
        Arguments:
         - keystring - a string previously imported with
           L{exportKey} or L{exportKeyPrivate}
        Keywords:
         - passphrase - string (default '', meaning 'try to import without passphrase')
        Returns:
         - True if import successful, False if failed
    
        You don't have to call this if you instantiate your key object
        in 'import' mode - ie, by calling it with a previously exported key.
    
        Note - you shouldn't give a 'passphrase' when importing a public key.
        """
    
        passphrase = kwds.get('passphrase', '')
        if passphrase == None:
            passphrase = ''
    
        try:
            #k1 = keystring.split("<StartPycryptoKey>", 1)
            #k2 = k1[1].split("<EndPycryptoKey>")
            ##print "decoding:\n", k2[0]
            #k = base64.decodestring(k2[0])
    
            #trace()
    
            keypickle = self._unwrap("Key", keystring)
            keytuple = pickle.loads(keypickle)
            haspass, size, keyobj = keytuple
    
            if haspass:
                # decrypt against passphrase
                blksiz = 8 # lazy of me
    
                # create temporary symmetric cipher object for passphrase - hardwire to Blowfish
                ppCipher = Blowfish.new(passphrase,
                                        Blowfish.MODE_CFB,
                                        self._passIV[0:blksiz])
                enclen = len(keyobj)
                decpriv = ''
                i = 0
                while i < enclen:
                    decbit = ppCipher.decrypt(keyobj[i:i+blksiz])
                    decpriv += decbit
                    i += blksiz
                keyobj = decpriv[0:size]
        
            self.algoPname, self.k = pickle.loads(keyobj)
            self.algoPub = self._algosPub[self.algoPname]
    
            #raise Exception("Tried to import Invalid Key")
            self._calcPubBlkSize()
            self.passphrase = passphrase
            return True
        except:
            return False
    
    #@-body
    #@-node:4::importKey()
    #@+node:5::exportKey()
    #@+body
    def exportKey(self):
        """
        Exports the public key as a printable string.
    
        Exported keys can be imported elsewhere into MyCipher instances
        with the L{importKey} method.
        
        Note that this object contains only the public key. If you want to
        export the private key as well, call L{exportKeyPrivate} instaead.
    
        Note also that the exported string is Base64-encoded, and safe for sending
        in email.
    
        Arguments:
         - None
        Returns:
         - a base64-encoded string containing an importable key
        """
        rawpub = self._rawPubKey()
        expTuple = (False, None, rawpub)
        expPickle = pickle.dumps(expTuple, True)
        return self._wrap("Key", expPickle)
    
    #@-body
    #@-node:5::exportKey()
    #@+node:6::exportKeyPrivate()
    #@+body
    def exportKeyPrivate(self, **kwds):
        """
        Exports public/private key pair as a printable string.
    
        This string is a binary string consisting of a pickled key object,
        that can be imported elsewhere into MyCipher instances
        with the L{importKey} method.
        
        Note that this object contains the public AND PRIVATE keys.
        Don't EVER email any keys you export with this function (unless you
        know what you're doing, and you encrypt the exported keys against
        another key). When in doubt, use L{exportKey} instead.
    
        Keep your private keys safe at all times. You have been warned.
    
        Note also that the exported string is Base64-encoded, and safe for sending
        in email.
    
        Arguments:
         - None
        Keywords:
         - passphrase - default (None) to using existing passphrase. Set to '' to export
           without passphrase (if this is really what you want to do!)
        Returns:
         - a base64-encoded string containing an importable key
        """
    
        passphrase = kwds.get('passphrase', None)
        if passphrase == None:
            passphrase = self.passphrase
    
        # exported key is a pickle of the tuple:
        # (haspassphrase, keylen, keypickle)
        # if using passphrase, 'keypickle' is encrypted against blowfish, and 'keylen'
        # indicates the number of significant bytes.
    
        rawpriv = pickle.dumps((self.algoPname, self.k), True)
    
        # prepare the key tuple, depending on whether we're using passphrases
        if passphrase != '':
            blksiz = 8 # i'm getting lazy, assuming 8 for blowfish
    
            # encrypt this against passphrase
            ppCipher = Blowfish.new(passphrase,
                                    Blowfish.MODE_CFB,
                                    self._passIV[0:blksiz])
            keylen = len(rawpriv)
            extras = (blksiz - (keylen % blksiz)) % blksiz
            rawpriv += self.randfunc(extras) # padd with random bytes
            newlen = len(rawpriv)
            encpriv = ''
            #print "newlen = %d" % newlen
            #trace()
            i = 0
            while i < newlen:
                rawbit = rawpriv[i:i+blksiz]
                encbit = ppCipher.encrypt(rawpriv[i:i+blksiz])
                #print "i=%d rawbit len=%d, encbit len=%d" % (i, len(rawbit), len(encbit))
                encpriv += encbit
                i += blksiz
            #print "keylen=%d, newlen=%d, len(encpriv)=%d" % (keylen, newlen, len(encpriv))
            #trace()
            keytuple = (True, keylen, encpriv)
        else:
            keytuple = (False, None, rawpriv)
    
        # prepare final pickle, base64 encode, wrap
        keypickle = pickle.dumps(keytuple, True)
        return self._wrap("Key", keypickle)
    
    
    
    #@-body
    #@-node:6::exportKeyPrivate()
    #@+node:7::encString()
    #@+body
    def encString(self, raw):
        """
        Encrypt a string of data
    
        High-level func. encrypts an entire string of data, returning the encrypted
        string as binary.
    
        Arguments:
         - raw string to encrypt
        Returns:
         - encrypted string as binary
    
        Note - the encrypted string can be stored in files, but I'd suggest
        not emailing them - use L{encStringToAscii} instead. The sole advantage
        of this method is that it produces more compact data, and works a bit faster.
        """
    
        # All the work gets done by the stream level
        self.encStart()
    
        # carve up into segments, because Python gets really slow
        # at manipulating large strings
    
        size = len(raw)
        bits = []
        pos = 0
        chunklen = 1024
        while pos < size:
            bits.append(self.encNext(raw[pos:pos+chunklen]))
            pos += chunklen
        bits.append(self.encEnd())
    
        return "".join(bits)
    
    #@-body
    #@-node:7::encString()
    #@+node:8::encStringToAscii()
    #@+body
    def encStringToAscii(self, raw):
        """
        Encrypts a string of data to printable ASCII format
    
        Use this method instead of L{encString}, unless size and speed are
        major issues.
    
        This method returns encrypted data in bracketed  base64 format,
        safe for sending in email.
    
        Arguments:
         - raw - string to encrypt
        Returns:
         - enc - encrypted string, text-wrapped and Base-64 encoded, safe for
           mailing.
    
        There's an overhead with base64-encoding. It costs size, bandwidth and
        speed. Unless you need ascii-safety, use encString() instead.
        """
        enc = self.encString(raw)
        return self._wrap("Message", enc)
    
    #@-body
    #@-node:8::encStringToAscii()
    #@+node:9::decString()
    #@+body
    def decString(self, enc):
        """
        Decrypts a previously encrypted string.
    
        Arguments:
         - enc - string, previously encrypted in binary mode with encString
        Returns:
         - dec - raw decrypted string
        """
    
        chunklen = 1024
    
        size = len(enc)
        bits = []
        pos = 0
    
        self.decStart()
    
        # carve up into small chunks so we don't get any order n^2 on large strings
        while pos < size:
            bits.append(self.decNext(enc[pos:pos+chunklen]))
            pos += chunklen
    
        self.decEnd()
    
        dec = "".join(bits)
        return dec
    
    #@-body
    #@-node:9::decString()
    #@+node:10::decStringFromAscii()
    #@+body
    def decStringFromAscii(self, enc):
        """
        Decrypts a previously encrypted string in ASCII (base64)
        format, as created by encryptAscii()
    
        Arguments:
         - enc - ascii-encrypted string, as previously encrypted with
           encStringToAscii()
        Returns:
         - dec - decrypted string
    
        May generate an exception if the public key of the encrypted string
        doesn't match the public/private keypair in this key object.
    
        To work around this problem, either instantiate a key object with
        the saved keypair, or use the importKey() function.
    
        Exception will also occur if this object is not holding a private key
        (which can happen if you import a key which was previously exported
        via exportKey(). If you get this problem, use exportKeyPrivate() instead
        to export your keypair.
        """
        #trace()
        wrapped = self._unwrap("Message", enc)
        return self.decString(wrapped)
    
    #@-body
    #@-node:10::decStringFromAscii()
    #@+node:11::signString()
    #@+body
    def signString(self, raw):
        """
        Sign a string using private key
    
        Arguments:
         - raw - string to be signed
        Returns:
         - wrapped, base-64 encoded string of signature
    
        Note - private key must already be present in the key object.
        Call L{importKey} for the right private key first if needed.
        """
    
        # hash the key with MD5
        m = MD5.new()
        m.update(raw)
        d = m.digest()
        #print "sign: digest"
        #print repr(d)
    
        # sign the hash with our current public key cipher
        self.randpool.stir()
        k = getPrime(128, self.randfunc)
        self.randpool.stir()
        s = self.k.sign(d, k)
    
        # now wrap into a tuple with the public key cipher
        tup = (self.algoPname, s)
    
        # and pickle it
        p = pickle.dumps(tup, True)
    
        # lastly, wrap it into our base64
        w = self._wrap("Signature", p)
    
        return w
    
    #@-body
    #@-node:11::signString()
    #@+node:12::verifyString()
    #@+body
    def verifyString(self, raw, signature):
        """
        Verifies a string against a signature.
    
        Object must first have the correct public key loaded. (see
        L{importKey}). An exception will occur if this is not the case.
    
        Arguments:
         - raw - string to be verified
         - signature - as produced when key is signed with L{signString}
        Returns:
         - True if signature is authentic, or False if not
        """
    
        # unrwap the signature to a pickled tuple
        p = self._unwrap("Signature", signature)
    
        # unpickle
        algoname, rawsig = pickle.loads(p)
    
        # ensure we've got the right algorithm
        if algoname != self.algoPname:
            return False # wrong algorithm - automatic fail
    
        # hash the string
        m = MD5.new()
        m.update(raw)
        d = m.digest()
        #print "verify: digest"
        #print repr(d)
    
        # now verify the hash against sig
        if self.k.verify(d, rawsig):
            return True # signature valid, or very clever forgery
        else:
            return False # sorry
    
    #@-body
    #@-node:12::verifyString()
    #@+node:13::test()
    #@+body
    def test(self, raw):
        """
        Encrypts, then decrypts a string. What you get back should
        be the same as what you put in.
    
        This is totally useless - it just gives a way to test if this API
        is doing what it should.
        """
        enc = self.encString(raw)
        dec = self.decString(enc)
        return dec
    
    #@-body
    #@-node:13::test()
    #@+node:14::testAscii()
    #@+body
    def testAscii(self, raw):
        """
        Encrypts, then decrypts a string. What you get back should
        be the same as what you put in.
    
        This is totally useless - it just gives a way to test if this API
        is doing what it should.
        """
        enc = self.encStringToAscii(raw)
        dec = self.decStringFromAscii(enc)
        return dec
    
    #@-body
    #@-node:14::testAscii()
    #@+node:15::Stream Methods
    #@+body
    # ---------------------------------------------
    #
    # These methods provide stream-level encryption
    #
    # ---------------------------------------------
    
    
    #@-body
    #@+node:1::encStart()
    #@+body
    def encStart(self):
        """
        Starts a stream encryption session
        Sets up internal buffers for accepting ad-hoc data.
    
        No arguments needed, nothing returned.
        """
    
        # Create a header block of segments, each segment is
        # encrypted against recipient's public key, to enable
        # recipient to decrypt the rest of the stream.
    
        # format of header block is:
        #  - recipient public key
        #  - stream algorithm id
        #  - stream session key
        #  - stream cipher initial value
    
        # Take algorithm index and pad it to the max length
    
        # stick in pubkey
        pubkey = self._rawPubKey()
        pubkeyLen = len(pubkey)
    
        self._tstSessKey0 = ''
        self._tstSessKey1 = ''
        self._tstIV0 = ''
        self._tstIV1 = ''
        self._tstBlk0 = ''
        self._tstBlk1 = ''
    
        #print "pub key len=%d" % pubkeyLen
        
        len0 = pubkeyLen % 256
        len1 = pubkeyLen / 256
    
        # Create algorithms info blk. Structure is:
        #  1byte  - index into session ciphers table
        #  2bytes - session key len, LSB first
        #  1byte  - session IV len, LSB first
    
        while 1:
            self._encHdrs = chr(len0) + chr(len1) + pubkey
    
            # add algorithms index
            algInfo = chr(self._algosSes2.index(self.algoSes))
    
            # Create new session key
            self._genNewSessKey()
    
            # add session key length
            sessKeyLen = len(self.sessKey)
            sessKeyLenL = sessKeyLen % 256
            sessKeyLenH = sessKeyLen / 256
            algInfo += chr(sessKeyLenL) + chr(sessKeyLenH)
    
            # add session IV length
            sessIVLen = len(self.sessIV)
            algInfo += chr(sessIVLen)
            #alg += self.randfunc(self.pubBlkSize - 1) # add random chaff
            #encAlgNum = self._encRawPub(alg)
            encAlgEnc = self._encRawPub(self._padToPubBlkSize(algInfo))
            if encAlgEnc == None:
                continue
            #encAlgLen = len(encAlgNum)
            #self._encHdrs += chr(encAlgLen) + encAlgNum
            self._encHdrs += encAlgEnc
    
            # ensure we can encrypt session key in one hit
            if len(self.sessKey) > self.pubBlkSize:
                raise Exception(
                    "encStart: you need a bigger public key length")
    
            # encrypt and add session key
            sKeyEnc = self._encRawPub(self._padToPubBlkSize(self.sessKey))
            if sKeyEnc == None:
                continue
            # sKeyLen = len(sKeyEnc)
            # self._encHdrs += chr(sKeyLen) + sKeyEnc
            self._encHdrs += sKeyEnc
    
            # encrypt and add session cipher initial value
            sCipherInit = self._encRawPub(self._padToPubBlkSize(self.sessIV))
            if sCipherInit == None:
                continue
            # sCipherIVLen = len(sCipherInit)
            # self._encHdrs += chr(sCipherIVLen) + sCipherInit
            self._encHdrs += sCipherInit
    
            self._tstSessKey0 = self.sessKey
            self._tstIV0 = self.sessIV
    
            # Create a new block cipher object
            self._initBlkCipher()
    
            # ready to go!
            self._encBuf = ''
    
            # success
            break
    
    #@-body
    #@-node:1::encStart()
    #@+node:2::encNext()
    #@+body
    def encNext(self, raw=''):
        """
        Encrypt the next piece of data in a stream.
    
        Arguments:
         - raw - raw piece of data to encrypt
        Returns - one of:
         - '' - not enough data to encrypt yet - stored for later
         - encdata - string of encrypted data
        """
    
        if raw == '':
            return ''
    
        # grab any headers
        enc = self._encHdrs
        self._encHdrs = ''
    
        # add given string to our yet-to-be-encrypted buffer
        self._encBuf += raw
    
        # Loop on data, breaking it up and encrypting it in blocks. Don't
        # touch the last (n mod b) bytes in buffer, where n is total size and
        # b is blocksize
        size = len(self._encBuf)
        next = 0
        while next <= size - self.sesBlkSize: # skip trailing bytes for now
            # extract next block
            blk = self._encBuf[next:next+self.sesBlkSize]
    
            if self._tstBlk0 == '':
                self._tstBlk0 = blk
    
            # encrypt block against session key
            encpart = self.blkCipher.encrypt(blk)
    
            # add length byte and crypted block to internal buffer
            enc += chr(self.sesBlkSize) + encpart
    
            next += self.sesBlkSize
    
        # ditch what we've consumed from buffer
        self._encBuf = self._encBuf[next:]
        
        # return whatever we've encrypted so far
        return enc
    
    #@-body
    #@-node:2::encNext()
    #@+node:3::encEnd()
    #@+body
    def encEnd(self):
        """
        Called to terminate a stream session.
        Encrypts any remaining data in buffer.
    
        Arguments:
         - None
        Returns - one of:
         - last block of data, as a string
        """
    
        buf = ''
        if self._encBuf == '':
            # no trailing data - pass back empty packet
            return chr(0)
    
        # break up remaining data into packets, and encrypt
        while len(self._encBuf) > 0:
    
            # extract session blocksize worth of data from buf
            blk = self._encBuf[0:self.sesBlkSize]
            self._encBuf = self._encBuf[self.sesBlkSize:]
            blklen = len(blk)
    
            # pad if needed
            if blklen < self.sesBlkSize:
                blk += self.randfunc(self.sesBlkSize - blklen)
    
            # encrypt against session key, and add
            buf += chr(blklen)
            buf += self.blkCipher.encrypt(blk)
    
        # clean up and get out
        return buf
    
    #@-body
    #@-node:3::encEnd()
    #@+node:4::decStart()
    #@+body
    def decStart(self):
        """
        Start a stream decryption session.
    
        Call this method first, then feed in pieces of stream data into decNext until
        there's no more data to decrypt
    
        Arguments:
         - None
        Returns:
         - None
        """
    
        # Start with fresh buffer and initial state
        self._decBuf = ''
        self._decState = 'p'
        self._decEmpty = False
    
        self._tstSessKey1 = ''
        self._tstIV1 = ''
        self._tstBlk1 = ''
    
        # states - 'p'->awaiting public key
        #          'c'->awaiting cipher index
        #          'k'->awaiting session key
        #          'i'->awaiting cipher initial data
        #          'd'->awaiting data block
    
    #@-body
    #@-node:4::decStart()
    #@+node:5::decNext()
    #@+body
    def decNext(self, chunk):
        """
        Decrypt the next piece of incoming stream data.
    
        Arguments:
         - chunk - some more of the encrypted stream
        Returns (depending on state)
         - '' - no more decrypted data available just yet
         - data - the next available piece of decrypted data
         - None - session is complete - no more data available
        """
    
        if self._decEmpty:
            return None
    
        # add chunk to our buffer
        self._decBuf += chunk
    
        # bail out if nothing to do
        chunklen = len(self._decBuf)
        if chunklen < 2:
            return ''
    
        # start with empty decryption buffer
        decData = ''
    
        # loop around processing as much data as we can
        #print "decNext: started"
        while 1:
            if self._decState == 'p':
                size = ord(self._decBuf[0]) + 256 * ord(self._decBuf[1])
                if chunklen < size + 2:
                    # don't have full pubkey yet
                    return ''
                else:
                    pubkey = self._decBuf[2:size+2]
                    if not self._testPubKey(pubkey):
                        raise Exception("Can't decrypt - public key mismatch")
    
                    self._decBuf = self._decBuf[size+2:]
                    self._decState = 'c'
                    continue
    
            if self._decState == 'd':
    
                #trace()
    
                # awaiting next data chunk
                sizeReqd = self.sesBlkSize + 1
                size = len(self._decBuf)
                if size < sizeReqd:
                    return decData
                nbytes = ord(self._decBuf[0])
                if nbytes == 0:
                    self._decEmpty = True
                    return None
                blk = self._decBuf[1:sizeReqd]
                self._decBuf = self._decBuf[sizeReqd:]
                decBlk = self.blkCipher.decrypt(blk)
                if self._tstBlk1 == '':
                    self._tstBlk1 = decBlk
                decBlk = decBlk[0:nbytes]
                decData += decBlk
                if nbytes < self.sesBlkSize:
                    self._decEmpty = True
                    return decData
                continue
    
            if len(self._decBuf) < 2:
                return decData
    
            sizeReqd = ord(self._decBuf[0]) + 256 * ord(self._decBuf[1]) + 2
            size = len(self._decBuf)
    
            # bail if we have insufficient data
            if size < sizeReqd:
                return decData
    
            # extract length byte plus block
            #blksize = sizeReqd - 1
            #blk = self._decBuf[1:sizeReqd]
            #self._decBuf = self._decBuf[sizeReqd:]
            blk = self._decBuf[0:sizeReqd]
            self._decBuf = self._decBuf[sizeReqd:]
    
            # state-dependent processing
            if self._decState == 'c':
                #print "decrypting cipher info"
                # awaiting cipher info
                blk = self._decRawPub(blk)
    
                # session cipher index
                c = ord(blk[0])
                self.algoSes = self._algosSes2[c]
    
                # session key len
                self._tmpSessKeyLen = ord(blk[1]) + 256 * ord(blk[2])
    
                # session IV len
                self._tmpSessIVLen = ord(blk[3])
    
                # ignore the rest - it's just chaff
                self._decState = 'k'
                continue
    
            elif self._decState == 'k':
                # awaiting session key
                #print "decrypting session key"
                blk = self._decRawPub(blk)
                self.sessKey = blk[0:self._tmpSessKeyLen]
                self._tstSessKey1 = self.sessKey
                self._decState = 'i'
                continue
    
            elif self._decState == 'i':
                # awaiting cipher start value
                #print "decrypting IV"
                blk = self._decRawPub(blk)
                self.sessIV = blk[0:self._tmpSessIVLen]
                self._tstIV1 = self.sessIV
    
                # Create cipher object, now we have what we need
                self.blkCipher = self.algoSes.new(self.sessKey,
                                                  getattr(self.algoSes, "MODE_CFB"),
                                                  self.sessIV)
                self._calcSesBlkSize()
                self._decState = 'd'
                continue
    
            else:
                raise Exception(
                    "decNext: strange state '%s'" % self._decState[0])
    
    #@-body
    #@-node:5::decNext()
    #@+node:6::decEnd()
    #@+body
    def decEnd(self):
        """
        Ends a stream decryption session.
        """
        # nothing really to do here - decNext() has taken care of it
        # just reset internal state
        self._decBuf = ''
        self._decState = 'c'
    
    #@-body
    #@-node:6::decEnd()
    #@-node:15::Stream Methods
    #@+node:16::Low Level
    #@+node:1::_wrap()
    #@+body
    def _wrap(self, type, msg):
        """
        Encodes message as base64 and wraps with <StartPyCryptoname>/<EndPycryptoname>
        Args:
         - type - string to use in header/footer - eg 'Key', 'Message'
         - msg - binary string to wrap
        """
        return "<StartPycrypto%s>\n%s<EndPycrypto%s>\n" \
                 % (type, base64.encodestring(msg), type)
    
    #@-body
    #@-node:1::_wrap()
    #@+node:2::_unwrap()
    #@+body
    def _unwrap(self, type, msg):
        """
        Unwraps a previously _wrap()'ed message.
        """
        try:
            #trace()
            k1 = msg.split("<StartPycrypto%s>" % type, 1)
            k2 = k1[1].split("<EndPycrypto%s>" % type)
            k = k2[0]
            #print "raw = "
            #print k
            bin = base64.decodestring(k)
            return bin
        except:
            raise Exception("Tried to import Invalid %s" % type)
            self._calcBlkSize()
    
    #@-body
    #@-node:2::_unwrap()
    #@+node:3::_calcPubBlkSize()
    #@+body
    def _calcPubBlkSize(self):
        """
        Determine size of public key
        """
        self.pubBlkSize = (self.k.size() - 7) / 8
    
    #@-body
    #@-node:3::_calcPubBlkSize()
    #@+node:4::_encRawPub()
    #@+body
    def _encRawPub(self, raw):
        """
        Encrypt a small raw string using the public key
        algorithm. Input must not exceed the allowable
        block size.
    
        Arguments:
         - raw - small raw bit of string to encrypt
        Returns:
         - binary representation of encrypted chunk, or None if verify failed
        """
    
        if len(raw) > self.pubBlkSize:
            raise Exception(
                "_encraw: max len %d, passed %d bytes" % (self.pubBlkSize, len(raw)))
    
        self.randpool.stir()
        k = getPrime(128, self.randfunc)
        s = self.k.encrypt(raw, k)
        #d = self.k.decrypt(s)
        #if d != raw:
        #    #print "_encRawPub: decrypt verify fail"
        #    return None
    
        #trace()
    
        # format this tuple into <len><nitems><item1len><item1bytes><item2len><item2bytes>...
        enc = chr(len(s))
        for item in s:
            itemLen = len(item)
            itemLenL = itemLen % 256
            itemLenH = itemLen / 256
            #enc += chr(len(item))
            enc += chr(itemLenL) + chr(itemLenH)
            enc += item
        encLen = len(enc)
        encLenL = encLen % 256
        encLenH = encLen / 256
        #enc = chr(len(enc)) + enc
        enc = chr(encLenL) + chr(encLenH) + enc
    
        #d = self._decRawPub(enc)
        #if d != raw:
        #    print "panic:_encRawPub: decrypt verify fail!"
            
        return enc
    
    
    #@-body
    #@-node:4::_encRawPub()
    #@+node:5::_decRawPub()
    #@+body
    def _decRawPub(self, enc):
        """
        Decrypt a public-key encrypted block, and return the decrypted string
    
        Arguments:
         - enc - the encrypted string, in the format as created by _encRawPub()
        Returns:
         - decrypted block
        """
    
        #trace()
    
        blklen = ord(enc[0]) + 256 * ord(enc[1])
        nparts = ord(enc[2])
        enc = enc[3:]
    
        if blklen != len(enc)+1:
            raise Exception(
                "_decRawPub: bad block length %d, should be %d" % (len(enc), blklen))
        parts = []
        for i in range(nparts):
            partlen = ord(enc[0]) + 256 * ord(enc[1])
            part = enc[2:partlen+2]
            enc = enc[partlen+2:]
            parts.append(part)
        partsTuple = tuple(parts)
        dec = self.k.decrypt(partsTuple)
        return dec
    
    
    
    #@-body
    #@-node:5::_decRawPub()
    #@+node:6::_initBlkCipher()
    #@+body
    def _initBlkCipher(self):
        """
        Create a new block cipher object, set up with a new session key
        and IV
        """
    
        self.blkCipher = self.algoSes.new(self.sessKey,
                                          getattr(self.algoSes, "MODE_CFB"),
                                          self.sessIV)
        self._calcSesBlkSize()
    
    #@-body
    #@-node:6::_initBlkCipher()
    #@+node:7::_calcSesBlkSize()
    #@+body
    def _calcSesBlkSize(self):
        """
        Determine size of session blocks
        """
        self.sesBlkSize = (self.blkCipher.block_size)
    
    #@-body
    #@-node:7::_calcSesBlkSize()
    #@+node:8::_testPubKey()
    #@+body
    def _testPubKey(self, k):
        """
        Checks if binary-encoded key matches this object's pubkey
        """
    
        if k == self._rawPubKey():
            return True
        else:
            return False
    
    #@-body
    #@-node:8::_testPubKey()
    #@+node:9::_rawPubKey()
    #@+body
    def _rawPubKey(self):
        """
        Returns a binary-encoded string of public key
        """
        return pickle.dumps((self.algoPname, self.k.publickey()), True)
    
    #@-body
    #@-node:9::_rawPubKey()
    #@+node:10::_padToPubBlkSize()
    #@+body

    def _padToPubBlkSize(self, raw):
        """
        padToPubBlkSize - pad a string to max size encryptable by public key
    
        Defence against factoring attacks that can uplift a session key when
        that key is encrypted by itself against public key
    
        Arguments:
         - raw - string to pad with random bytes
        returns:
         - padded string. Note - it is the responsibility of the decryption
           code to know how much of the string to extract once decrypted.
        """
    
        rawlen = len(raw)
        extras = self.randfunc(self.pubBlkSize - rawlen)
        #print "padToPubBlkSize: len=%d, added %d bytes of chaff :)" \
        #      % (rawlen, len(extras))
        return raw + extras
    
    #@-body
    #@-node:10::_padToPubBlkSize()
    #@+node:11::_genNewSessKey()
    #@+body
    def _genNewSessKey(self):
        """
        Generate a new random session key
        """
        self.randpool.stir()
        self.sessKey = self.randfunc(32)
        self.randpool.stir()
        self.sessIV = self.randfunc(8)
    
    #@-body
    #@-node:11::_genNewSessKey()
    #@-node:16::Low Level
    #@-others


#@-body
#@-node:3::class key
#@-others



#@-body
#@-node:0::@file easy/ezPyCrypto.py
#@-leo

