/OldUniversityStuff/ZIKS/ezPyCrypto-0.1.1/easy/ezPyCrypto.py
Python | 1334 lines | 1197 code | 20 blank | 117 comment | 21 complexity | c5937c935c720122d22e015c5a7b382c MD5 | raw file
Possible License(s): GPL-2.0
- #@+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")
- 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