def changepassword(self): """ Creates a new key. The key itself is actually stored in the database in crypted form. This key is encrypted using the password that the user provides. This makes it easy to change the password for the database. If oldKeyCrypted is none, then a new password is generated.""" if self._callback == None: raise CryptoNoCallbackException("No call back class has been specified") if self._keycrypted == None: # Generate a new key, 32 bits in length, if that's # too long for the Cipher, _getCipherReal will sort it out random = RandomPool() key = random.get_bytes(32).encode("base64") else: password = self._callback.execute("Please enter your current password") cipher = self._getcipher_real(password, self._algo) plainkey = cipher.decrypt(self._keycrypted.decode("base64")) key = self._retrievedata(plainkey) newpassword1 = self._callback.execute("Please enter your new password") newpassword2 = self._callback.execute("Please enter your new password again") if newpassword1 != newpassword2: raise CryptoPasswordMismatchException("Passwords do not match") newcipher = self._getcipher_real(newpassword1, self._algo) self._keycrypted = newcipher.encrypt(self._preparedata(key, newcipher.block_size)).encode("base64") # we also want to create the cipher if there isn't one already # so this CryptoEngine can be used from now on if self._cipher == None: self._cipher = self._getcipher_real(key.decode("base64"), self._algo) CryptoEngine._timeoutcount = time.time() return self._keycrypted
def get_random_bytes( nbytes ): nbits = nbytes * 8 random_pool = RandomPool( 1064 ) while random_pool.entropy < nbits: random_pool.add_event() random_pool.stir() return str( number.getRandomNumber( nbits, random_pool.get_bytes ) )
def generate_key(self): pool = RandomPool(384) pool.stir() randfunc = pool.get_bytes key_size = 1024 self.private_key = RSA.generate(key_size, randfunc) self.public_key = self.private_key.publickey()
def _generate_seed(self, size): rp = RandomPool() for i in range(7): m = SHA.new() tempseed = rp.get_bytes(size) m.update(tempseed) rp.add_event(m.hexdigest()) return rp.get_bytes(size)
def AFSplit(data, stripes, digesttype='sha1'): """AF-Split data using digesttype. Returned data size will be len(data) * stripes""" blockSize = len(data) rand = RandomPool() bufblock = "\x00" * blockSize ret = "" for i in range(0, stripes - 1): # Get some random data rand.randomize() rand.stir() r = rand.get_bytes(blockSize) if rand.entropy < 0: print "Warning: RandomPool entropy dropped below 0" ret += r bufblock = _xor(r, bufblock) bufblock = _diffuse(bufblock, blockSize, digesttype) rand.add_event(bufblock) ret += _xor(bufblock, data) return ret
def generate_key(self): ''' Generate public and private session_key ''' pool = RandomPool(384) pool.stir() randfunc = pool.get_bytes key_size = 1024 key = RSA.generate(key_size, randfunc) public_key = key.publickey() return key, public_key
def create_ssh_key(): """ Crate SSH key locally """ key_size = 1024 pool = RandomPool(key_size) pool.stir() rsakey = RSA.generate(key_size, pool.get_bytes) with open('private/gmailarchive_rsa', 'w') as k: k.write(rsakey.exportKey('PEM')) with open('private/gmailarchive_rsa.pub', 'w') as k: k.write(rsakey.publickey().exportKey('OpenSSH'))
def AFSplit(data, stripes, digesttype='sha1'): """AF-Split data using digesttype. Returned data size will be len(data) * stripes""" blockSize = len(data) rand = RandomPool() bufblock = "\x00" * blockSize ret = "" for i in range(0, stripes-1): # Get some random data rand.randomize() rand.stir() r = rand.get_bytes(blockSize) if rand.entropy < 0: print "Warning: RandomPool entropy dropped below 0" ret += r bufblock = _xor(r, bufblock) bufblock = _diffuse(bufblock, blockSize, digesttype) rand.add_event(bufblock) ret += _xor(bufblock, data) return ret
class FallbackRandomPool (BaseOSRandomPool): def __init__(self): self._wr = RandomPool() self.randomize() def get_bytes(self, n): return self._wr.get_bytes(n)
class FallbackRandomPool(BaseOSRandomPool): def __init__(self): self._wr = RandomPool() self.randomize() def get_bytes(self, n): return self._wr.get_bytes(n)
def privkeytostr(key, passphrase=None): keyData = '-----BEGIN RSA PRIVATE KEY-----\n' p, q = key.p, key.q if p > q: (p, q) = (q, p) # p is less than q objData = [ 0, key.n, key.e, key.d, q, p, key.d % (q - 1), key.d % (p - 1), util.number.inverse(p, q) ] if passphrase: iv = RandomPool().get_bytes(8) hexiv = ''.join(['%02X' % ord(x) for x in iv]) keyData += 'Proc-Type: 4,ENCRYPTED\n' keyData += 'DEK-Info: DES-EDE3-CBC,%s\n\n' % hexiv ba = hashlib.md5(passphrase + iv).digest() bb = hashlib.md5(ba + passphrase + iv).digest() encKey = (ba + bb)[:24] asn1Data = asn1pack([objData]) if passphrase: padLen = 8 - (len(asn1Data) % 8) asn1Data += (chr(padLen) * padLen) asn1Data = DES3.new(encKey, DES3.MODE_CBC, iv).encrypt(asn1Data) b64Data = base64.encodestring(asn1Data).replace('\n', '') b64Data = '\n'.join( [b64Data[i:i + 64] for i in range(0, len(b64Data), 64)]) keyData += b64Data + '\n' keyData += '-----END RSA PRIVATE KEY-----' return keyData
def generate_public_key_pair(self, p1, p2, data): """ The GENERATE PUBLIC-KEY PAIR command either initiates the generation and storing of a key pair, i.e., a public key and a private key, in the card, or accesses a key pair previously generated in the card. :param p1: should be 0x00 (generate new key) :param p2: '00' (no information provided) or reference of the key to be generated :param data: One or more CRTs associated to the key generation if P1-P2 different from '0000' """ from Crypto.PublicKey import RSA, DSA from Crypto.Util.randpool import RandomPool rnd = RandomPool() cipher = self.ct.algorithm c_class = locals().get(cipher, None) if c_class is None: raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) if p1 & 0x01 == 0x00: #Generate key PublicKey = c_class.generate(self.dst.keylength, rnd.get_bytes) self.dst.key = PublicKey else: pass #Read key #Encode keys if cipher == "RSA": #Public key n = str(PublicKey.__getstate__()['n']) e = str(PublicKey.__getstate__()['e']) pk = ((0x81, len(n), n), (0x82, len(e), e)) result = bertlv_pack(pk) #Private key d = PublicKey.__getstate__()['d'] elif cipher == "DSA": #DSAParams p = str(PublicKey.__getstate__()['p']) q = str(PublicKey.__getstate__()['q']) g = str(PublicKey.__getstate__()['g']) #Public key y = str(PublicKey.__getstate__()['y']) pk = ((0x81, len(p), p), (0x82, len(q), q), (0x83, len(g), g), (0x84, len(y), y)) #Private key x = str(PublicKey.__getstate__()['x']) #Add more algorithms here #elif cipher = "ECDSA": else: raise SwError(SW["ERR_CONDITIONNOTSATISFIED"]) result = bertlv_pack([[0x7F49, len(pk), pk]]) #TODO: Internally store key pair if p1 & 0x02 == 0x02: #We do not support extended header lists yet raise SwError["ERR_NOTSUPPORTED"] else: return SW["NORMAL"], result
def secure_chan(self, server_nick): """Request a secure channel.""" if (self.config["crypto"] == "a"): # TODO: This key generation should be done elsewhere and better from Crypto.PublicKey import RSA from Crypto.Util.randpool import RandomPool from Crypto.Util import number self.pool = RandomPool(384) self.pool.stir() # Larger key needed to encrypt larger data, 768 too small print "Generating key..." import cPickle # Only send public key, not private key # TODO: this needs to be fixed, its not seamless self.config["passwd"] = \ cPickle.dumps(RSA.generate(1024, self.pool.get_bytes).publickey()) msg = "sumi sec " + \ self.config["crypto"] + \ base64.encodestring(self.config["passwd"]).replace("\n", "") # Bootstrap sendmsg #self.sendmsg(server_nick, msg) self.senders[server_nick]["sendmsg"](server_nick, msg) # Now, communicate in crypto with server_nick self.senders[server_nick]["crypto"] = self.config["crypto"] self.senders[server_nick]["passwd"] = self.config["passwd"]
def gerar_chave_publica(): pool = RandomPool(384) pool.stir() # randfunc(n) deve retornar uma string de dados aleatórios de # comprimento n, no caso de RandomPool, o método get_bytes randfunc = pool.get_bytes # Tamanho da chave, em bits N = 1024 # Geramos a chave (contendo a chave pública e privada): key = RSA.generate(N, randfunc) # Separando apenas a chave pública: pub_key = key.publickey() arquivo = open(ARQ_CHAVE_PUBLICA, 'w') arquivo.write(pub_key.exportKey()) arquivo.close() return pub_key
def generate_public_key_pair(self, p1, p2, data): """ In the Cryptoflex card this command only supports RSA keys. :param data: Contains the public exponent used for key generation :param p1: The key number. Can be used later to refer to the generated key :param p2: Used to specify the key length. The mapping is: 0x40 => 256 Bit, 0x60 => 512 Bit, 0x80 => 1024 """ from Crypto.PublicKey import RSA from Crypto.Util.randpool import RandomPool keynumber = p1 # TODO: Check if key exists keylength_dict = {0x40: 256, 0x60: 512, 0x80: 1024} if p2 not in keylength_dict: raise SwError(SW["ERR_INCORRECTP1P2"]) else: keylength = keylength_dict[p2] rnd = RandomPool() PublicKey = RSA.generate(keylength, rnd.get_bytes) self.dst.key = PublicKey e_in = struct.unpack("<i", data) if e_in[0] != 65537: logging.warning("Warning: Exponents different from 65537 are " + "ignored! The Exponent given is %i" % e_in[0]) # Encode Public key n = PublicKey.__getstate__()['n'] n_str = inttostring(n) n_str = n_str[::-1] e = PublicKey.__getstate__()['e'] e_str = inttostring(e, 4) e_str = e_str[::-1] pad = 187 * '\x30' # We don't have CRT components, so we need to pad pk_n = TLVutils.bertlv_pack( ((0x81, len(n_str), n_str), (0x01, len(pad), pad), (0x82, len(e_str), e_str))) # Private key d = PublicKey.__getstate__()['d'] # Write result to FID 10 12 EF-PUB-KEY df = self.mf.currentDF() ef_pub_key = df.select("fid", 0x1012) ef_pub_key.writebinary([0], [pk_n]) data = ef_pub_key.getenc('data') # Write private key to FID 00 12 EF-PRI-KEY (not necessary?) # How to encode the private key? ef_priv_key = df.select("fid", 0x0012) ef_priv_key.writebinary([0], [inttostring(d)]) data = ef_priv_key.getenc('data') return PublicKey
def runTest(self): """Crypto.Util.randpool.RandomPool""" # Import the winrandom module and try to use it from Crypto.Util.randpool import RandomPool sys.stderr.write("SelfTest: You can ignore the RandomPool_DeprecationWarning that follows.\n") rpool = RandomPool() x = rpool.get_bytes(16) y = rpool.get_bytes(16) self.assertNotEqual(x, y) self.assertNotEqual(rpool.entropy, 0) rpool.randomize() rpool.stir('foo') rpool.add_event('foo')
def encrypt(self, data): """encrypt data with AES-CBC and sign it with HMAC-SHA256""" aes_key, hmac_key = self.keys pad = AES_BLOCK_SIZE - len(data) % AES_BLOCK_SIZE data = data + pad * chr(pad) iv_bytes = RandomPool(512).get_bytes(AES_BLOCK_SIZE) cypher = AES.new(aes_key, AES.MODE_CBC, iv_bytes) data = iv_bytes + cypher.encrypt(data) sig = hmac.new(hmac_key, data, hashlib.sha256).digest() return data + sig
def encrypt(plain_text, passwd): '''Encrypts C{plain_text} using a key derived from C{passwd}. A key is derived from C{passwd} using L{_derive_key} with a random 8 byte salt. This key is then used to encrypt the following string: <len(plain)> + plain + "keysafe" + <random bytes> Then the salt and the ciphertext is concatenated and the result is base64 encoded before being returned. @type plain_text: string @param plain_text: password @type passwd: string @param passwd: key to use for encryption @rtype: string @return: base64 encoded encryption of C{plain_text} using C{passwd} ''' # TODO: Change the string to be encrypted to # <len(plain)> + <plain> + <sha256(plain)> + <random bytes> # This should be better than using "keysafe" in the string # create the salt, and derive the key rp = RandomPool() salt = rp.get_bytes(8) key = _derive_key(passwd, salt) # create the full plain text string, make sure it matches the block size # for AES pt = plain_text + _KNOWN_STR rnd_pad_len = struct.unpack('B', rp.get_bytes(1))[0] rnd_pad_len += AES.block_size - \ (1 + len(pt) + rnd_pad_len) % AES.block_size rnd_pad = rp.get_bytes(rnd_pad_len) full_plain_text = struct.pack('B', len(plain_text)) + pt + rnd_pad # encrypt, it seems I need to do it twice to get a predictable result cipher_block = AES.new(key, AES.MODE_PGP) cipher_text = cipher_block.encrypt(full_plain_text) cipher_text = cipher_block.encrypt(full_plain_text) return b64encode(salt + cipher_text)
def salaa(): #Allerkirjoitetaan sisalto #RSA avaimen luonti RSAkey = RSA.generate(1024, RandomPool().get_bytes) #Allekirjoitettavan tiedoston avaus ja luku SignFile = open(sys.argv[2], 'rb') contents = SignFile.read() SignFile.close() #Hashin laskeminen datalle hash1 = SHA.new(contents).digest() #Datan allekirjoitus signature = RSAkey.sign(hash1, "") #Julkisen avaimen tallennus pubkey = RSAkey.publickey() PubkeyFILE = open("pubkey.pem", 'w') PubkeyFILE.write(pubkey.exportKey('PEM')) PubkeyFILE.close() #Allekirjoituksen tallennus SigFILE = open("signature.txt", 'wb') test = signature[0] SigFILE.write(str(test)) SigFILE.close() password = raw_input("Anna salasana:") key = hashlib.sha256(password).digest() #Luetaan julkinen avain tiedostoon publickeyFILE = open("julkinenavain", 'wb') publickeyFILE.write(key) publickeyFILE.close() #Moden valinta mode = AES.MODE_CBC iv = ''.join(chr(random.randint(0, 0xFF)) for i in range(16)) #Salaimen luonti cipher = AES.new(key, mode, iv) #Salattavan tiedoston koko filesize = os.path.getsize(sys.argv[2]) #Lohkon koko chunksize = AES.block_size * 16 with open(sys.argv[2], 'rb') as infile: with open("salattusopimus.txt", 'wb') as outfile: #Tiedoston koon kirjoitus tiedoston alkuun outfile.write(struct.pack('<Q', filesize)) outfile.write(iv) while True: chunk = infile.read(chunksize) if len(chunk) == 0: break elif len(chunk) % 16 != 0: chunk += ' ' * (AES.block_size - len(chunk) % AES.block_size) outfile.write(cipher.encrypt(chunk)) outfile.close() infile.close()
def get_random_bytes(nbytes): nbits = nbytes * 8 random_pool = RandomPool(1064) while random_pool.entropy < nbits: random_pool.add_event() random_pool.stir() return str(number.getRandomNumber(nbits, random_pool.get_bytes))
def changepassword(self): """ Creates a new key. The key itself is actually stored in the database in crypted form. This key is encrypted using the password that the user provides. This makes it easy to change the password for the database. If oldKeyCrypted is none, then a new password is generated.""" if (self._callback == None): raise CryptoNoCallbackException( "No call back class has been specified") if (self._keycrypted == None): # Generate a new key, 32 bits in length, if that's # too long for the Cipher, _getCipherReal will sort it out random = RandomPool() key = str(random.get_bytes(32)).encode('base64') else: password = self._callback.getsecret( "Please enter your current password") cipher = self._getcipher_real(password, self._algo) plainkey = cipher.decrypt(str(self._keycrypted).decode('base64')) key = self._retrievedata(plainkey) newpassword1 = self._callback.getsecret( "Please enter your new password") newpassword2 = self._callback.getsecret( "Please enter your new password again") if (newpassword1 != newpassword2): raise CryptoPasswordMismatchException("Passwords do not match") newcipher = self._getcipher_real(newpassword1, self._algo) self._keycrypted = str( newcipher.encrypt(self._preparedata( key, newcipher.block_size))).encode('base64') # we also want to create the cipher if there isn't one already # so this CryptoEngine can be used from now on if (self._cipher == None): self._cipher = self._getcipher_real( str(key).decode('base64'), self._algo) CryptoEngine._timeoutcount = time.time() return self._keycrypted
def pycryptest(): from Crypto.PublicKey import RSA from Crypto.Hash import SHA from Crypto.Util.randpool import RandomPool r = RandomPool() k = RSA.generate(1024, r.get_bytes) progress("pycryptest key:", `(k.n, k.e)`) data = "abcdefg" dh = SHA.new(data).digest() sig = k.sign(dh, None) progress("pycryptest signature:", `sig`) result = k.verify(dh, sig) progress("pycryptest result:", result)
def generate(bits, filename): rp = RandomPool() key = RSA.generate(bits, rp.get_bytes) public_key = key.publickey() public_dump = pickle.dumps(public_key) private_dump = pickle.dumps(key) try: fo = open(filename + ".priv", "w") fo.write(private_dump) fo.close fo = open(filename + ".pub", "w") fo.write(public_dump) fo.close except IOError, msg: print msg sys.exit(2)
def _encrypt(data, mode='RGB'): ''' Encrypt the pixels (as a concatinated string). ''' # unit: how many bytes does the smallest component have # size: how many components does each pixel have if mode == '1' or mode == 'I' or mode == 'F': print "Mode {mode} is not supported at the moment!".format(mode=mode) return [] else: unit = 1 format = 'B' cleartext = '' if type(data[0]) == int: pixel_size = 1 for pixel in data: cleartext += chr(pixel) else: pixel_size = len( data[0]) # get the vector size based on the last pixel for pixel in data: for val in pixel: cleartext += chr(val) num_pixels = len(data) # encrypt the data string, pay attention to the length key = RandomPool().get_bytes(KEY_LEN) blowfish = Blowfish.new(key, mode=2) padding = '\x00' * (BLK_LEN - (len(cleartext) - 1) % BLK_LEN - 1) cleartext += padding ciphered = blowfish.encrypt(cleartext) ciphered = ciphered[len( padding):] # don't really care if data is messed... ret = [] chunk_size = unit * pixel_size for i in range(num_pixels): pixel = struct.unpack(format * pixel_size, ciphered[chunk_size * i:chunk_size * (1 + i)]) ret.append(pixel) return ret
def gen_random_key(size=32): """ Generate a cryptographically-secure random key. This is done by using Python 2.4's os.urandom, or PyCrypto. """ import os if hasattr(os, "urandom"): # Python 2.4+ return os.urandom(size) # Try using PyCrypto if available try: from Crypto.Util.randpool import RandomPool from Crypto.Hash import SHA256 return RandomPool(hash=SHA256).get_bytes(size) except ImportError: print >> sys.stderr, "WARNING: The generated key will not be cryptographically-secure key. Consider using Python 2.4+ to generate the key, or install PyCrypto." # Stupid random generation import random L = [] for i in range(size): L.append(chr(random.randint(0, 255))) return "".join(L)
def setup (self): # Set up a random pool; we won't bother to actually fill it with # entropy from the keyboard self.pool = RandomPool(384) self.pool.stir()
def create(self, file, cipherName, cipherMode, hashSpec, masterSize, stripes): """Initializes the file class passed in with the LUKS header Parameters cipherName: aes, cast5, blowfish cipherMode: cbc-plain, cbc-essiv:<hash> hashSpec: sha1, sha256, md5, ripemd160 masterSize: length of the master key in bytes (must match cipher) stripes: number of stripes when Af Splitting keys For compatibility with the Linux kernel dm-crypt, hashSpec must equal "sha1" cbc-plain uses the sector number as the IV. This has a weakness: an attacker may be able to detect the existance of watermarked files. cbc-essiv:<hash> protects against the weakness in cbc-plain, but is slightly slower. The digest size of the hash function passed to cbc-essiv must match the key size of the cipher. aes-cbc-essiv:sha256 works, while aes-cbc-essiv:sha1 does not For more information about the details of the attacks and risk assesment, see http://clemens.endorphin.org/LinuxHDEncSettings """ if self.file != None: raise LuksError("This LuksFile has already been initialized") self._check_cipher(cipherName, cipherMode) self.magic = self.LUKS_MAGIC self.version = 1 self.mkDigestIterations = 10 self.keyBytes = masterSize self.hashSpec = hashSpec rand = RandomPool(self.SALT_SIZE + 16 + masterSize) # Generate the salt self.mkDigestSalt = rand.get_bytes(self.SALT_SIZE) # Generate a random master key self.masterKey = rand.get_bytes(self.keyBytes) self.ivGen.set_key(self.masterKey) # generate the master key digest pbkdf = PBKDFv2.PBKDFv2() self.mkDigest = pbkdf.makeKey(self.masterKey, self.mkDigestSalt, self.mkDigestIterations, hashlib.new(self.hashSpec).digest_size, self.hashSpec) # init the key information currentSector = math.ceil(592.0 / self.SECTOR_SIZE) alignSectors = 4096 / self.SECTOR_SIZE blocksPerStripe = math.ceil( float(self.keyBytes * stripes) / self.SECTOR_SIZE) self.keys = [None] * 8 for i in range(0, 8): if currentSector % alignSectors > 0: currentSector += alignSectors - currentSector % alignSectors self.keys[i] = self._key_block() self.keys[i].create(currentSector, stripes, self.LUKS_KEY_DISABLED) currentSector += blocksPerStripe # Set the data offset if currentSector % alignSectors > 0: currentSector += alignSectors - currentSector % alignSectors self.payloadOffset = currentSector # Generate a UUID for this file self._uuidgen(rand) # Create a new file, and save the header into it self.file = file self._save_header() # Write FF into all the key slots FFData = "\xFF" * int(self.SECTOR_SIZE) for i in range(0, 8): self.file.seek(int(self.keys[i].keyMaterialOffset)) for sector in range(0, int(blocksPerStripe)): self.file.write(FFData) # Flush the file to disk try: self.file.flush() os.fsync(self.file.fileno()) except: # We might get an error because self.file.fileno() does not exist on StringIO pass
def _get_random_pool(self): pool = RandomPool() pool.randomize() return pool
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)
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")
class Random: def __init__(self): from Crypto.Util.randpool import RandomPool self.RandomPool = RandomPool() def getRandomString(self, N): """Returns a N-bit length random string.""" r = self.getRandomNumber(N) return number.long_to_bytes(r) def getRandomNumber(self, N): """Returns an N-bit length random number.""" if self.RandomPool.entropy < 2 * N: self.RandomPool.randomize(4 * N) self.RandomPool.add_event('') self.RandomPool.stir() random = number.getRandomNumber(N, self.RandomPool.get_bytes) self.RandomPool.stir() return random def getPrime(self, N): """Returns a N-bit length prime.""" if self.RandomPool.entropy < 2 * N: self.RandomPool.randomize(4 * N) self.RandomPool.add_event('') self.RandomPool.stir() prime = number.getPrime(N, self.RandomPool.get_bytes) self.RandomPool.stir() return prime def addEvent(self, text): """Adds a bit of random text to the pool as additional entropy. Use caution. The curreny implementation of this function just XORs the text over the entropy, probably giving it bias if we just roll through our messages. I'm not sure. """ self.RandomPool.add_event(text) self.RandomPool.stir() def verifyEntropy(self, N): """Verifies enough entropy is in the RandomPool. If we are close to no entropy, attempt to add some.""" if self.RandomPool.entropy < 2 * N: self.RandomPool.randomize(4 * N) self.RandomPool.add_event('') self.RandomPool.stir() if self.RandomPool.entropy < N: # if the stirring got rid of entropy, seed with more entropy self.verifyEntropy(2 * N) def get_bytes(self, num): """Get num bytes of randomness from the RandomPool.""" return self.RandomPool.get_bytes(num)
def __init__(self): self._wr = RandomPool() self.randomize()
#!/usr/bin/env python # Script to time fast and slow RSA operations # Contributed by Joris Bontje. import time, pprint from Crypto.PublicKey import * from Crypto.Util.randpool import RandomPool from Crypto.Util import number pool = RandomPool() pool.stir() KEYSIZE = 2048 COUNT = 5 fasttime = 0 slowtime = 0 for x in range(COUNT): begintime = time.time() rsa = RSA.generate(KEYSIZE, pool.get_bytes) endtime = time.time() print "Server: Generating %d bit RSA key: %f s" % (KEYSIZE, endtime - begintime) rsa_slow = RSA.construct((rsa.n, rsa.e, rsa.d)) code = number.getRandomNumber(256, pool.get_bytes) begintime = time.time() signature = rsa.sign(code, None)[0] endtime = time.time() fast = (endtime - begintime) fasttime = fasttime + fast
try: _urandom = file('/dev/urandom', 'rb') except IOError: raise ImportError('No adequate source of randomness found!') else: def getBytes(n): bytes = [] while n: chunk = _urandom.read(n) n -= len(chunk) bytes.append(chunk) assert n >= 0 return ''.join(bytes) else: _pool = RandomPool() def getBytes(n, pool=_pool): if pool.entropy < n: pool.randomize() return pool.get_bytes(n) # A randrange function that works for longs try: randrange = random.SystemRandom().randrange except AttributeError: # In Python 2.2's random.Random, randrange does not support # numbers larger than sys.maxint for randrange. For simplicity, # use this implementation for any Python that does not have # random.SystemRandom
def set_key(self, keyIndex, password, iterations, checkMinStripes=0): """Sets the key block at keyIndex using password Sets the key block at keyIndex using password, hashed iterations times using PBKDFv2 (RFC2104). This LuksFile must be unlocked by calling open_any_key() before calling this function. checkMinStripes is used to detect basic header manipulation, since the number of stripes for the key is set by create(), before we write a password to disk we make sure the key is not weak because of a small number of stripes. This function will raise an error if the key is already enabled. """ # Some checks if self.masterKey == None: raise LuksError( "A key has not been unlocked. Call open_any_key() first.") if keyIndex < 0 or keyIndex > 7: raise LuksError("keyIndex out of range") key = self.keys[keyIndex] if key.active != self.LUKS_KEY_DISABLED: raise LuksError("Key is active. Delete the key and try again") if checkMinStripes == 0: checkMinStripes = self.KEY_STRIPES if key.stripes < checkMinStripes: raise LuksError( "Key section %i contains too few stripes. Header manipulation?" % keyIndex) key.passwordIterations = iterations # Generate a random salt for this key rand = RandomPool(self.SALT_SIZE) key.passwordSalt = rand.get_bytes(self.SALT_SIZE) # Hash the key using PBKDFv2 pbkdf = PBKDFv2.PBKDFv2() derived_key = pbkdf.makeKey(password, key.passwordSalt, key.passwordIterations, self.keyBytes, self.hashSpec) # Split the key into key.stripes AfKey = AfSplitter.AFSplit(self.masterKey, key.stripes, self.hashSpec) AfKeySize = len(AfKey) if AfKeySize != key.stripes * self.keyBytes: raise LuksError( "ERROR: AFSplit did not return the correct length of key") # Set the key for IV generation self.ivGen.set_key(derived_key) # Encrypt the splitted key using the hashed password AfSectors = int(math.ceil(float(AfKeySize) / self.SECTOR_SIZE)) for sector in range(0, AfSectors): self._encrypt_sector(derived_key, key.keyMaterialOffset + sector, sector, \ AfKey[int(sector * self.SECTOR_SIZE):int((sector + 1) * self.SECTOR_SIZE)]) key.active = self.LUKS_KEY_ENABLED # Reset the key used for to IV generation in data mode self.ivGen.set_key(self.masterKey) self._save_header()
class PublicKeyTest (TestScenario): def setup (self): # Set up a random pool; we won't bother to actually fill it with # entropy from the keyboard self.pool = RandomPool(384) self.pool.stir() def shutdown (self): del self.pool def testkey (self, key, randfunc, verbose=0): plaintext="Hello" # Generate maximum-size plaintext maxplain = (key.size() // 8) * '\377' if key.can_encrypt(): if verbose: print ' Encryption/decryption test' K=number.getPrime(10, randfunc) ciphertext=key.encrypt(plaintext, K) self.test_val('key.decrypt(ciphertext)', plaintext) ciphertext=key.encrypt(maxplain, K) self.test_val('key.decrypt(ciphertext)', maxplain) if key.can_sign(): if verbose: print ' Signature test' K=number.getPrime(30, randfunc) signature=key.sign(plaintext, K) self.test_bool('key.verify(plaintext, signature)') self.test_bool('key.verify(plaintext[:-1], signature)', want_true=0) # Change a single bit in the plaintext badtext=plaintext[:-3]+chr( 1 ^ ord(plaintext[-3]) )+plaintext[-3:] self.test_bool('key.verify(badtext, signature)', want_true=0) if verbose: print ' Removing private key data' pubonly=key.publickey() self.test_bool('pubonly.verify(plaintext, signature)') # Test blinding if key.can_blind(): if verbose: print ' Blinding test' K=number.getPrime(30, randfunc) B="garbage" blindedtext=key.blind(plaintext, B) signature=key.sign(blindedtext, K) unblindedsignature=(key.unblind(signature[0], B),) self.test_bool('key.verify(plaintext, unblindedsignature)') self.test_val('key.sign(plaintext, K)', unblindedsignature) # Change a single bit in the blinding factor badB=B[:-3]+chr( 1 ^ ord(B[-3]) )+B[-3:] badunblindedsignature=(key.unblind(signature[0], badB),) self.test_false('key.verify(badtext, badunblindedsignature)') badblindedtext=key.blind(plaintext, badB) badsignature=key.sign(blindedtext, K) badunblindedsignature2=(key.unblind(signature[0], B),) self.test_false('key.verify(badtext, badunblindedsignature2)') def exercise (self, randfunc, pk_mod, verbose=0): N=256 # Key size, measured in bits key=pk_mod.generate(N, randfunc) if verbose: print ' Key data:' for field in key.keydata: print " ", field, ':', hex(getattr(key,field)) if verbose: print " Testing newly generated key" self.testkey(key, randfunc, verbose) if verbose: print " Testing pickled/unpickled key" import pickle s = pickle.dumps(key) ; key2 = pickle.loads(s) self.testkey(key2, randfunc, verbose) if verbose: print " Testing cPickled key" s = cPickle.dumps(key) ; key2 = cPickle.loads(s) self.testkey(key2, randfunc, verbose) if verbose: print def check_rsa(self): "Check RSA algorithm" self.exercise(self.pool.get_bytes, RSA) def check_dsa(self): "Check DSA algorithm" self.exercise(self.pool.get_bytes, DSA) def check_elgamal(self): "Check ElGamal algorithm" self.exercise(self.pool.get_bytes, ElGamal) def check_qnew(self): "Check qNEW algorithm" self.exercise(self.pool.get_bytes, qNEW)
from Crypto.PublicKey import RSA from Crypto import Random from Crypto.Util.randpool import RandomPool """ rng = Random.new().read RSAkey = RSA.generate(1024, rng) """ pool = RandomPool(1024) pool.stir() randfunc = pool.get_bytes RSAkey = RSA.generate(1024, randfunc) f = open("meter-private.pem", "w+") f.write(RSAkey.exportKey("PEM")) f.close() print "Gerou chave Privada" f = open("meter-public", "w+") f.write(RSAkey.publickey().exportKey("PEM")) f.close() print "Gerou chave Publica"
def __init__(self): from Crypto.Util.randpool import RandomPool self.RandomPool = RandomPool()
from Crypto.PublicKey import RSA from Crypto.Util.randpool import RandomPool texto = "texto a encriptar" # Você deve usar a melhor fonte de dados aleatórios que tiver à # disposição. Pra manter o exemplo mais portável, usaremos o # RandomPool do próprio PyCrypto: pool = RandomPool(384) pool.stir() # randfunc(n) deve retornar uma string de dados aleatórios de # comprimento n, no caso de RandomPool, o método get_bytes randfunc = pool.get_bytes # Se tiver uma fonte segura (como /dev/urandom em sistemas unix), ela # deve ser usada ao invés de RandomPool # pool = open("/dev/urandom") # randfunc = pool.read # Tamanho da chave, em bits N = 2048 # O algoritmo RSA usado aqui não utiliza K, que pode ser uma string # nula. K = None # Geramos a chave (contendo a chave pública e privada): key = RSA.generate(N, randfunc)
#!/usr/bin/env python # Script to time fast and slow RSA operations # Contributed by Joris Bontje. import time, pprint from Crypto.PublicKey import * from Crypto.Util.randpool import RandomPool from Crypto.Util import number pool = RandomPool() pool.stir() KEYSIZE=2048 COUNT=5 fasttime=0 slowtime=0 for x in range(COUNT): begintime=time.time() rsa=RSA.generate(KEYSIZE, pool.get_bytes) endtime=time.time() print "Server: Generating %d bit RSA key: %f s" % (KEYSIZE, endtime-begintime) rsa_slow=RSA.construct((rsa.n,rsa.e,rsa.d)) code=number.getRandomNumber(256, pool.get_bytes) begintime=time.time() signature=rsa.sign(code,None)[0] endtime=time.time() fast=(endtime-begintime) fasttime=fasttime+fast print "Fast signing took %f s" % fast
from Crypto.PublicKey import RSA from Crypto import Random from Crypto.Util.randpool import RandomPool """ rng = Random.new().read RSAkey = RSA.generate(1024, rng) """ bits = 1024 pool = RandomPool(bits) pool.stir() randfunc = pool.get_bytes RSAkey = RSA.generate(bits, randfunc) f = open("inmetro-private.pem", "w+") f.write(RSAkey.exportKey("PEM")) f.close() print "Gerou chave Privada" f = open("inmetro-public", "w+") f.write(RSAkey.publickey().exportKey("PEM")) f.close() print "Gerou chave Publica"
def geraChave(): pool = RandomPool(1024) pool.stir() randfunc = pool.get_bytes key = RSA.generate(1024, randfunc) return key
def generateRandom(n): atfork() rand = RandomPool() # using seperate instance of RandomPool purposely return rand.get_bytes(n)
def generate_key_string(cls, key_size=256): key = RandomPool(512).get_bytes(key_size / 8 + SIG_SIZE) return key.encode("base64").replace("\n", "")
def create(self, file, cipherName, cipherMode, hashSpec, masterSize, stripes): """Initializes the file class passed in with the LUKS header Parameters cipherName: aes, cast5, blowfish cipherMode: cbc-plain, cbc-essiv:<hash> hashSpec: sha1, sha256, md5, ripemd160 masterSize: length of the master key in bytes (must match cipher) stripes: number of stripes when Af Splitting keys For compatibility with the Linux kernel dm-crypt, hashSpec must equal "sha1" cbc-plain uses the sector number as the IV. This has a weakness: an attacker may be able to detect the existance of watermarked files. cbc-essiv:<hash> protects against the weakness in cbc-plain, but is slightly slower. The digest size of the hash function passed to cbc-essiv must match the key size of the cipher. aes-cbc-essiv:sha256 works, while aes-cbc-essiv:sha1 does not For more information about the details of the attacks and risk assesment, see http://clemens.endorphin.org/LinuxHDEncSettings """ if self.file != None: raise "This LuksFile has already been initialized" self._check_cipher(cipherName, cipherMode) self.magic = self.LUKS_MAGIC self.version = 1 self.mkDigestIterations = 10 self.keyBytes = masterSize self.hashSpec = hashSpec rand = RandomPool(self.SALT_SIZE + 16 + masterSize) # Generate the salt self.mkDigestSalt = rand.get_bytes(self.SALT_SIZE) # Generate a random master key self.masterKey = rand.get_bytes(self.keyBytes) self.ivGen.set_key(self.masterKey) # generate the master key digest pbkdf = PBKDFv2.PBKDFv2() self.mkDigest = pbkdf.makeKey(self.masterKey, self.mkDigestSalt, self.mkDigestIterations, hashlib.new(self.hashSpec).digest_size, self.hashSpec) # init the key information currentSector = math.ceil(592.0 / self.SECTOR_SIZE) alignSectors = 4096 / self.SECTOR_SIZE blocksPerStripe = math.ceil(float(self.keyBytes * stripes) / self.SECTOR_SIZE) self.keys = [None] * 8 for i in range(0, 8): if currentSector % alignSectors > 0: currentSector += alignSectors - currentSector % alignSectors self.keys[i] = self._key_block() self.keys[i].create(currentSector, stripes, self.LUKS_KEY_DISABLED) currentSector += blocksPerStripe # Set the data offset if currentSector % alignSectors > 0: currentSector += alignSectors - currentSector % alignSectors self.payloadOffset = currentSector # Generate a UUID for this file self._uuidgen(rand) # Create a new file, and save the header into it self.file = file self._save_header() # Write FF into all the key slots FFData = "\xFF" * int(self.SECTOR_SIZE) for i in range(0, 8): self.file.seek(int(self.keys[i].keyMaterialOffset)) for sector in range(0, int(blocksPerStripe)): self.file.write(FFData) # Flush the file to disk try: self.file.flush() os.fsync(self.file.fileno()) except: # We might get an error because self.file.fileno() does not exist on StringIO pass
def set_key(self, keyIndex, password, iterations, checkMinStripes=0): """Sets the key block at keyIndex using password Sets the key block at keyIndex using password, hashed iterations times using PBKDFv2 (RFC2104). This LuksFile must be unlocked by calling open_any_key() before calling this function. checkMinStripes is used to detect basic header manipulation, since the number of stripes for the key is set by create(), before we write a password to disk we make sure the key is not weak because of a small number of stripes. This function will raise an error if the key is already enabled. """ # Some checks if self.masterKey == None: raise "A key has not been unlocked. Call open_any_key() first." if keyIndex < 0 or keyIndex > 7: raise "keyIndex out of range" key = self.keys[keyIndex] if key.active != self.LUKS_KEY_DISABLED: raise "Key is active. Delete the key and try again" if checkMinStripes == 0: checkMinStripes = self.KEY_STRIPES if key.stripes < checkMinStripes: raise "Key section %i contains too few stripes. Header manipulation?" % keyIndex key.passwordIterations = iterations # Generate a random salt for this key rand = RandomPool(self.SALT_SIZE) key.passwordSalt = rand.get_bytes(self.SALT_SIZE) # Hash the key using PBKDFv2 pbkdf = PBKDFv2.PBKDFv2() derived_key = pbkdf.makeKey(password, key.passwordSalt, key.passwordIterations, self.keyBytes, self.hashSpec) # Split the key into key.stripes AfKey = AfSplitter.AFSplit(self.masterKey, key.stripes, self.hashSpec) AfKeySize = len(AfKey) if AfKeySize != key.stripes * self.keyBytes: raise "ERROR: AFSplit did not return the correct length of key" # Set the key for IV generation self.ivGen.set_key(derived_key) # Encrypt the splitted key using the hashed password AfSectors = int(math.ceil(float(AfKeySize) / self.SECTOR_SIZE)) for sector in range(0, AfSectors): self._encrypt_sector(derived_key, key.keyMaterialOffset + sector, sector, \ AfKey[int(sector*self.SECTOR_SIZE):int((sector+1)*self.SECTOR_SIZE)]) key.active = self.LUKS_KEY_ENABLED # Reset the key used for to IV generation in data mode self.ivGen.set_key(self.masterKey) self._save_header()
class Client: def __init__(self): print "Loading config...", # Mode "U" for universal newlines, so \r\n is okay self.config = eval("".join(open(config_file, "rU").read())) print "OK" if self.config.has_key("myip"): if self.config["myip"] != "": self.myip = self.config["myip"] else: self.myip = get_default_ip() print "Using IP: ", self.myip else: print "IP not specified, getting network interface list..." # Look for an up interface. Use get_ifaces instead of just the # IP so we can also get the netmask, too ifaces = get_ifaces() for name in ifaces: if not ifaces[name].has_key("status") or \ ifaces[name]["status"] != True and \ ifaces[name]["status"] != "active": continue if not ifaces[name]["inet"]: continue print name,ifaces[name]["inet"],ifaces[name]["netmask"] # XXX: This is a problem for wxfer.py! It needs input! This is # how it should be, though. Chuck popen into the trash. print "Which interface? ", i = sys.stdin.readline()[:-1] print "Using IP ", ifaces[i]["inet"] self.myip = ifaces[i]["inet"] self.netmask = ifaces[i]["netmask"] # save for later self.netmask = int(ifaces[i]["mtu"]) - 28 if self.config.has_key("myport"): self.myport = self.config["myport"] else: print "defaulting to port 41170" self.myport = 41170 # Your local IP to bind to. This can be your private IP if you're behind # a NAT it can be the same as "myip", or it can be "" meaning bind to # all ifaces self.localaddr = "" self.senders = {} if self.config.has_key("irc_nick"): self.irc_nick = self.config["irc_nick"] else: print "No IRC nick specified. What to use? " self.irc_nick = sys.stdin.readline()[:-1] if self.config.has_key("irc_name"): self.irc_name = self.config["irc_name"] else: self.irc_name = self.irc_nick if self.config.has_key("mss"): self.mss = self.config["mss"] else: try: self.mss except: print "MSS not found! Please set it." raise SystemExit if self.config.has_key("rwinsz"): self.rwinsz = self.config["rwinsz"] self.rwinsz_old = 0 #RWINSZ never changes here! TODO:if it does, # then rwinsz_old MUST be updated to reflect. else: print "Please set rwinsz" raise SystemExit if self.config.has_key("bandwidth"): self.bandwidth = self.config["bandwidth"] else: print "Please set bandwidth" sys.exit(5) self.set_callback(self.default_cb) # override me please def on_msg(self, nick, msg): """Handle incoming messages.""" print "<%s> %s" % (nick, msg) # This isn't used anymore - its in the auth packet instead if (msg.find("sumi start ") == 0): args = unpack_args(msg[len("sumi start "):]) (filename, offset, size) = (args["f"], args["o"], args["l"]) offset = int(offset) size = int(size) if (self.senders.has_key(nick)): self.sendmsg(nick, "n%d" % self.rwinsz) self.rwinsz_old = self.rwinsz print "Starting n%d %s for %s,%d,%d" % (self.rwinsz, nick, filename, offset, size) else: print "ERROR: user not known, stranger trying to sumi start!" print "Senders: ", self.senders elif (msg.find("error: ") == 0): errmsg = msg[len("error: "):] print "*** Error: ", errmsg # Write the resume file for transfer from nick def save_lost(self, x): lost = ",".join(map(str, self.senders[x]["lost"].keys())) lost += "," + str(self.senders[x]["at"]) # last is at, cur/last if (lost != ""): self.senders[x]["fs"].seek(0) self.senders[x]["fs"].truncate() self.senders[x]["fs"].flush() self.senders[x]["fs"].write(lost) self.senders[x]["fs"].flush() #print "WROTE LOST: ",lost def handle_packet(self, data, addr): """Handle received packets.""" prefix = data[:3] (seqno, ) = struct.unpack("!L", "\0" + data[3:6]) # Find nick that is associated with the random prefix; which is the # only way to identify the source. # The data structures aren't setup very efficiently nick = None for x in self.senders: if self.senders[x].has_key("prefix") and \ self.senders[x]["prefix"] == prefix: nick = x break if nick == None: print "DATA:UNKNOWN PREFIX!",len(data),"bytes from",addr return #print "Incoming:",len(data),"bytes from",addr,"=",nick," #",seqno self.senders[nick]["retries"] = 0 # acks worked self.senders[nick]["last_msg"] = time.time() # XXX: Resuming sometimes asks for packets more than once, fix it. # Maybe the dupe could be compared to see if any errors? # Last most recently received packet, for resuming self.senders[nick]["at"] = seqno # Sequence number is 3 bytes in the SUMI header in network order # (so a null can easily be prepended for conversion to a long), # this used to be partially stored in the source port, but PAT-- # Port Address Translation--closely related to NAT, can mangle # the srcport if (seqno == 0): # all 0's = auth packet context = md5.md5() context.update(data) # auth "key" is all of data, hash it #context.update("hi")#bad hash #hash = context.hexdigest() hash = base64.encodestring(context.digest())[:-1] print "PKT:Got auth packet from",addr," for ",nick, print "PKT:Verifying prefix (authenticity of server)..." # XXX: Does this ever fail? if (data[0:3] == prefix and len(data) > len(prefix)): print "OK\n" else: print "failed!" # file metadata should be here (file length) (self.senders[nick]["size"], ) = struct.unpack("!L", data[SUMIHDRSZ:SUMIHDRSZ + SUMIAUTHHDRSZ]) print "SIZE:%d" % (self.senders[nick]["size"],) filename=data[SUMIHDRSZ+4:data[SUMIHDRSZ+4:].find("\0")+SUMIHDRSZ+4] self.senders[nick]["fn"] = filename print "Filename: <%s>" % filename self.callback(nick, "info", self.senders[nick]["size"], \ base64.encodestring(prefix)[:-1], filename, \ self.senders[nick]["transport"], \ self.config["data_chan_type"]) if (self.mss != len(data)): print "WARNING: Downgrading MSS %d->%d, maybe set it lower?" % (self.mss, len(data)) self.mss = len(data) if (self.mss < 256): print "MSS is extremely low (%d), quitting" % self.mss sys.exit(-1) # Open the file and set it up if not self.senders[nick].has_key("fh"): # file not open yet print "Opening tempout for %s..." % nick, self.senders[nick]["start"] = time.time() # These try/except blocks try to open the file rb+, but if # it fails with 'no such file', create them with wb+ and # open with rb+. Good candidate for a function! try: self.senders[nick]["fh"] = open(self.config["dl_dir"] + \ self.senders[nick]["fn"], "rb+") except IOError: open(self.config["dl_dir"] + \ self.senders[nick]["fn"], "wb+").close() self.senders[nick]["fh"] = open(self.config["dl_dir"] + \ self.senders[nick]["fn"], "rb+") print "open" # Open a new resuming file (create if needed) try: self.senders[nick]["fs"] = open(self.config["dl_dir"] + \ self.senders[nick]["fn"] + ".lost", "rb+") is_resuming = 1 # unless proven otherwise except IOError: open(self.config["dl_dir"] + \ self.senders[nick]["fn"] + ".lost", "wb+").close() self.senders[nick]["fs"] = open(self.config["dl_dir"] + \ self.senders[nick]["fn"] + ".lost", "rb+") is_resuming = 0 # empty resume file # Check if the data file exists, and if so, resume off it lostdata = None if (os.access(self.config["dl_dir"] + \ self.senders[nick]["fn"], os.R_OK)): # The data file is readable, read lost data lostdata = self.senders[nick]["fs"].read().split(",") # If the below is true, the xfer is complete--",X". # TODO: Let user overwrite or cancel. Needs to have # a better interface for this, GUI-able. if (len(lostdata) == 2 and lostdata[0] == "" \ and int(lostdata[1]) == lostdata[1]): is_resuming = 0 print self.senders[nick]["fn"] + " is finished!" + \ " It will not be resumed." sys.exit(1) else: is_resuming = 0 if (len(lostdata) <= 1): is_resuming = 0 print "LEN LOSTDATA=",len(lostdata) is_resuming=0#FORCE # Setup lost if (is_resuming): # this works self.senders[nick]["at"] = int(lostdata.pop()) if (len(lostdata) == 0 or len(lostdata[0]) == 0): # Several ways to do this. a) Go through the normal # process, and finish there. con: auth. pro: simple? # b) save user the time, finished. c) overwrite choice print "File complete, not resumed" # Right now, it finishes the transfer. self.callback(nick, "fin", 0, \ self.senders[nick]["size"], \ 0, "") # TODO: give choice #self.cb("overwrite?", self.senders[nick]["fn"] #sys.exit(1) return #XYZ print "RESUMING" print "IS_RESUMING: LOST: ",lostdata self.senders[nick]["lost"] = {} for x in lostdata: try: self.senders[nick]["lost"][int(x)] = 1 except ValueError: pass # don't add non-ints print "LOADED LOSTS:",self.senders[nick]["lost"] # Initialize the rwin with empty hashes, mark off missings self.senders[nick]["rwin"] = {} for x in range(1, self.senders[nick]["at"]): self.senders[nick]["rwin"][int(x)] = 1 # received for L in self.senders[nick]["rwin"]: # mark losses self.senders[nick]["rwin"][int(L)] = 0 # Bytes received = (MSS * at) - (MSS * numlost) # TODO: Fix error in reporting number of bytes received, off by about 16, # even though it is saved correctly...this leads to >100% progress rates # XXX: MSS's may be inconsistant across users! Corruption self.senders[nick]["bytes"] = \ (self.mss * self.senders[nick]["at"]) - \ (self.mss * len(self.senders[nick]["lost"].keys())) # Files don't store statistics like these self.senders[nick]["all_lost"] = [] # blah self.senders[nick]["rexmits"] = 0 else: # Initialize self.senders[nick]["at"] = 0 self.senders[nick]["rexmits"] = 0 self.senders[nick]["all_lost"] = [] self.senders[nick]["bytes"] = 0 # bytes received self.senders[nick]["lost"] = {} # use: keys(), pop().. # RWIN is a list of all the packets, and if they occured (0=no), # incremented each time a packet of that seqno is received. Since # Python arrays don't automatically grow with assignment, a hash # is used instead. If "rwin" was an array, [], missed packets would # cause an IndexError. See #http://mail.python.org/pipermail/python-list/2003-May/165484.html # for rationale and some other class implementations self.senders[nick]["rwin"] = {} # Tell the sender to start sending, we're ok print "Sending sumi auth" self.sendmsg(nick, "sumi auth " + pack_args({"m":self.mss, "s":addr[0], "h":hash, "o":self.senders[nick]["at"]})) # The rest of this function handles data transfer return else: # Prefix has been checked, seqno calculated, so just get to the data data = data[SUMIHDRSZ:] # All file data is received here self.senders[nick]["last_msg"] = time.time() offset = (seqno - 1) * (self.mss - SUMIHDRSZ) self.senders[nick]["fh"].seek(offset) self.senders[nick]["fh"].write(data) #sys.stdout.write("."); #print "WRITE:%d:%d:%d:%s"%(offset, offset + len(data), len(data)) #self.callback(nick, "write", offset, offset + len(data), len(data), \ #self.senders[nick]["size"], addr) self.senders[nick]["bytes"] += len(data) # correct self.callback(nick, "write", offset, self.senders[nick]["bytes"], self.senders[nick]["size"], addr) # Mark down each packet in our receive window try: self.senders[nick]["rwin"][seqno] += 1 except KeyError: self.senders[nick]["rwin"][seqno] = 1 # create if (self.senders[nick]["rwin"][seqno] >= 2): print "(DUPLICATE PACKET %d, IGNORED)" % seqno return #Check previous packets, see if they were lost (unless first packet) if (seqno > 1): i = 1 while 1: if (not self.senders[nick]["rwin"].has_key(seqno - i)): self.senders[nick]["lost"][seqno - i] = 1 self.senders[nick]["all_lost"].append(str(seqno - i)) i += 1 else: break # this one wasn't lost, so already checked if self.senders[nick]["lost"].has_key(seqno): self.senders[nick]["lost"].pop(seqno) print "Recovered packet ", seqno, self.senders[nick]["lost"] self.senders[nick]["rexmits"] += 1 print "(rexmits = ", self.senders[nick]["rexmits"] self.callback(nick, "rexmits", self.senders[nick]["rexmits"]) #on_timer() # Maybe its all we need if (self.senders.has_key(nick) and len(self.senders[nick]["lost"])): self.callback(nick, "lost", self.senders[nick]["lost"].keys()) #print "These packets are currently lost: ", self.senders[nick]["lost"].keys() self.save_lost(nick) else: self.callback(nick, "lost", ()) # Less than full sized packet = last if (len(data) != self.mss - SUMIHDRSZ): print "NON-FULLSIZED: %d != %d" % (len(data), self.mss - SUMIHDRSZ) self.senders[nick]["gotlast"] = 1 # File size is now sent in auth packet so no need to calc it here #self.senders[nick]["size"] = self.senders[nick]["fh"].tell() self.on_timer() # have it check if finished def thread_timer(self): """Every RWINSZ seconds, send a nak of missing pkts up to that point.""" while 1: time.sleep(self.rwinsz) self.on_timer() def on_timer(self): """Acknowledge to all senders and update bytes/second.""" tmp_senders = self.senders.copy() for x in tmp_senders: if (not self.senders[x].has_key("lost")): # not xfering yet self.senders[x]["retries"] = 0 # initialize continue # Worth trying to update bps here if self.senders[x].has_key("bytes"): if self.senders[x].has_key("last_bytes"): bytes_per_rwinsz = \ self.senders[x]["bytes"] - self.senders[x]["last_bytes"] rate = float(bytes_per_rwinsz) / float(self.rwinsz) # rate = delta_bytes / delta_time (bytes arrived) # eta = delta_bytes * rate (bytes not arrived) if (rate != 0): eta = (self.senders[x]["size"] - self.senders[x]["bytes"]) / rate else: eta = 0 # Callback gets raw bits/sec and seconds remaining self.callback(x, "rate", rate, eta) self.senders[x]["last_bytes"] = self.senders[x]["bytes"] else: self.senders[x]["last_bytes"] = self.senders[x]["bytes"] if (len(self.senders[x]["lost"]) == 0 and self.senders[x].has_key("gotlast")): return self.finish_xfer(x) # there's nothing left, we're done! try: # Some missing packets, finish it up lost = ",".join(map(str, self.senders[x]["lost"].keys())) # Compress by omitting redundant elements to ease bandwidth if (self.rwinsz_old == self.rwinsz and lost == ""): self.sendmsg(x, "n") elif (lost == ""): self.sendmsg(x, "n%d" % self.rwinsz) else: self.sendmsg(x, ("n%d," % self.rwinsz) + lost) self.senders[x]["retries"] += 1 if (self.senders[x]["retries"] > 5): print x,"exceeded maximum retries (5), cancelling" self.senders.pop(x) self.callback(x, "timeout") self.rwinsz_old = self.rwinsz# TODO: update if changes..but need # to give the win on first(right) except KeyError: # sender ceased existance pass def finish_xfer(self, nick): """Finish the file transfer.""" # Nothing lost anymore, update. Saved as ",X" where X = last packet. print "DONE - UPDATING" self.save_lost(nick) # If was encrypted, decrypt # TODO: Separate transport and dchan encryption types if (self.config["crypto"] == "s"): #from aes.aes import aes from Crypto.Cipher import AES self.callback(nick, "dec", "AES") #aes_crypto = aes() #aes_crypto.setKey(self.config["passwd"]) # Use CFB mode because it doesn't require padding aes_crypto = AES.new(self.config["passwd"], AES.MODE_CFB) # Error: I/O operation on closed file? self.senders[nick]["fh"].flush() self.senders[nick]["fh"].seek(0) data = self.senders[nick]["fh"].read() print "About to decrypt"+str(len(data))+"bytes" data = aes_crypto.decrypt(data) out = open(self.config["dl_dir"] + self.senders[nick]["fn"], "wb") out.write(data) self.senders[nick]["fh"] = out self.sendmsg(nick, "sumi done") duration = time.time() - self.senders[nick]["start"] self.senders[nick]["fh"].close() self.callback(nick, "fin", duration, self.senders[nick]["size"], \ self.senders[nick]["size"] / duration / 1024, \ self.senders[nick]["all_lost"]) #print "Transfer complete in %.6f seconds" % (duration) #print "All lost packets: ", self.senders[nick]["all_lost"] #print str(self.senders[nick]["size"]) + " at " + str( # self.senders[nick]["size"] / duration / 1024) + " KB/s" self.senders.pop(nick) # delete the server key sys.exit(0) # here now for one file xfer per program def thread_recv_packets(self): """Receive anonymous packets.""" print "THREAD 1 - PACKET RECV" if (self.config["data_chan_type"] == "u"): self.server_udp() elif (self.config["data_chan_type"] == "e"): self.server_icmp() elif (self.config["data_chan_type"] == "i"): self.server_icmp() def server_icmp(self): """Receive ICMP packets. Requires raw sockets.""" print "ICMP started." # At the moment, needs to be ran as root #print "UID=", os.getuid() sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, \ socket.IPPROTO_ICMP) # XXX: TODO: Fix ICMP. The server doesn't generate ICMP checksums, # so we won't be able to receive here! sock.bind((self.localaddr, 0)) while 1: (data, addr) = sock.recvfrom(65535) data = data[20 + 8:] # IPHDRSZ + ICMPHDRSZ, get to payload self.handle_packet(data, addr) def server_udp(self): """Receive UDP packets.""" print "UDP started." sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) while 1: try: sock.bind((self.localaddr, self.myport)) except socket.error: failed = 0 if (sys.exc_info()[1].args[0] == 48): print "Port",self.myport,"in use, trying next" self.myport += 1 failed = 1 if not failed: break print "Bound to ", self.myport while 1: # This is interrupted...? try: (data, addr) = sock.recvfrom(65535) except socket.error: if sys.exc_info()[1].args[0] != 4: # Interrupted system call raise sys.exc_info()[1] # not for us to catch # Reinvoke it continue self.handle_packet(data, addr) def cli_user_input(self): """Client user input (not used anymore), belongs in separate program.""" input_lock.acquire() # wait for messaging program to be connected print "Started user input thread... you may now type" while 1: line = sys.stdin.readline() if line == "": # EOF, exit return 0 line = line[:-1] # Strip \n if line == "": # blank line (\n), ignore continue args = line.split() if (args[0] == "/get"): try: self.request(args[1], args[2]) except IndexError: print "Usage: get <server_nick> <file>" else: self.sendmsg(irc_chan, line) # DO SUMI SEC THEN ENCRYPT MSGS & PACKETS # Pre-auth. aes'd sumi send > irc_maxlen # MAX IRC PRIVMSG IN XCHAT: 452 in #sumi, 454 in #a (??) 462 in #a*31 # ON NGIRCD: COMMAND_LEN - 1, is 513-1 is 512. Room for PRIVMSG+nick. # ":jeff PRIVMSG " = 14 # Continuations are now implemented in some transports (segment()), # TODO: Also, think about combining some elements of sumi send with # sumi sec. Consider making sumi sec handle the auth packet, and # sumi send deal strictly with file transfers. This way, sumi sec+ # sumi login can be implemented later; allowing remote admin. # TODO: Actually, why not simply extend sumi send to allow remote admn. def secure_chan(self, server_nick): """Request a secure channel.""" if (self.config["crypto"] == "a"): # TODO: This key generation should be done elsewhere and better from Crypto.PublicKey import RSA from Crypto.Util.randpool import RandomPool from Crypto.Util import number self.pool = RandomPool(384) self.pool.stir() # Larger key needed to encrypt larger data, 768 too small print "Generating key..." import cPickle # Only send public key, not private key # TODO: this needs to be fixed, its not seamless self.config["passwd"] = \ cPickle.dumps(RSA.generate(1024, self.pool.get_bytes).publickey()) msg = "sumi sec " + \ self.config["crypto"] + \ base64.encodestring(self.config["passwd"]).replace("\n", "") # Bootstrap sendmsg #self.sendmsg(server_nick, msg) self.senders[server_nick]["sendmsg"](server_nick, msg) # Now, communicate in crypto with server_nick self.senders[server_nick]["crypto"] = self.config["crypto"] self.senders[server_nick]["passwd"] = self.config["passwd"] # def sendmsg(self, nick, msg): # """Send a message over the covert channel using loaded module.""" # #print "SENDING |%s| to |%s|" % (msg, nick) # #return sendmsg(nick, msg) # return self.senders[nick]["sendmsg"](nick, msg) # Send a secure message to server_nick def sendmsg(self, server_nick, msg): is_enc = 0 #print "===", self.senders[server_nick] print ">>%s>%s" % (server_nick, msg) if (self.senders.has_key(server_nick) and \ self.senders[server_nick]["crypto"] == "s"): from Crypto.Cipher import AES #from aes.aes import aes #aes_crypto = aes() #aes_crypto.setKey(self.senders[server_nick]["passwd"]) #msg = aes_crypto.encrypt(msg) aes_crypto = AES.new(self.senders[server_nick]["passwd"], AES.MODE_CFB) msg = aes_crypto.encrypt(msg) is_enc = 1 elif (self.senders.has_key(server_nick) and \ self.senders[server_nick]["crypto"] == "a"): import cPickle from Crypto.PublicKey import RSA key = cPickle.loads(self.config["passwd"]) msg = key.encrypt(msg, number.getPrime(10, self.pool.get_bytes))[0] is_enc = 1 # If encrypted, base64 it for transport if is_enc: msg = base64.encodestring(msg) # base64 likes to split the lines, remove newlines we'll do it # ourself thanks msg = msg.replace("\n", "") #print "<<<<<<%s>>>>>>" % msg # Note, this message will usually be long; the transport takes # care of splitting it up for us if necessary ##self.sendmsg(server_nick, msg) return self.senders[server_nick]["sendmsg"](server_nick, msg) def request(self, transport, server_nick, file): """Request a file from a server.""" global transports # command line args are now the sole form of user input; self.callback(server_nick, "t_wait") # transport waiting, see below # Input lock is mostly obsolete -- it is supposed to wait for # transport_init() to return, but we already wait for it #input_lock.acquire() # wait for transport connection if (self.senders.has_key(server_nick)): print "Already have a transfer from ",server_nick return self.senders[server_nick] = {} # Setup transport system self.senders[server_nick]["transport"] = transport self.load_transport(transport, server_nick) if (transports.has_key(transport) and transports[transport]): pass # already initialized print "Not initing ",transport else: self.senders[server_nick]["transport_init"]() transports[transport] = 1 # Initialize only once print "Just inited",transport # Setup cryptology self.secure_chan(server_nick) print "You want %s from %s" % (server_nick, file) offset = 0 prefix = string.join(map(chr, (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))), "") self.senders[server_nick]["prefix"] = prefix msg = "sumi send " + pack_args({"f":file, "o":offset, "i":self.myip, "n":self.myport, "m":self.mss, "p":base64.encodestring(prefix)[:-1], "b":self.bandwidth, "w":self.rwinsz, "d":self.config["data_chan_type"], "x":self.config["crypto"]}) # XYZ: In sumi sec #"K":base64.encodestring(self.config["passwd"])[:-1]}) self.sendmsg(server_nick, msg) #self.sendmsg(server_nick, msg) print "Sent" self.callback(server_nick, "req_sent") # request sent (handshaking) # Countdown. This provides a timeout for handshaking with nonexistant # senders, so the user isn't left hanging. maxwait = self.config["maxwait"] for x in range(maxwait, 0, -1): # If received fn in this time, then exists, so stop countdown if (self.senders[server_nick].has_key("fn")): return # don't break - otherwise will timeout self.callback(server_nick, "req_count", x) time.sleep(1) self.callback(server_nick, "timeout") # The sole request. In a separate thread so it can wait for IRC. # NOTE, sumigetw doesn't use this, it makes its own thread & calls request def thread_request(self, nick, file): self.request(nick, file) def set_callback(self, f): """Set callback to be used for handling notifications.""" self.callback = f def default_cb(self, cmd, *args): print "(CB)" + cmd + ":" + ",".join(list(map(str, args))) def load_transport(self, transport, nick): global input_lock, sendmsg, transport_init # Import the transport. This may fail, if, for example, there is # no such transport module. print sys.path try: t = __import__("transport.mod" + transport, None, None, ["transport_init", "sendmsg"]) except ImportError: self.callback(nick, "t_fail", sys.exc_info()) return t.segment = segment self.senders[nick]["sendmsg"] = t.sendmsg #sendmsg = t.sendmsg #transport_init = t.transport_init #self.transport_init = transport_init self.senders[nick]["transport_init"] = t.transport_init # Wait for transport #input_lock.acquire() # moved to caller def main(self, transport, nick, file): #load_transport(transport) thread.start_new_thread(self.thread_timer, ()) senders[nick]["transport_init"] = t.transport_init thread.start_new_thread(self.thread_request, (nick, file)) # This thread will release() input_lock, letting thread_request to go #transport_init() print "RELEASED" input_lock.release() # Main thread is UDP server. There is no transport thread, its sendmsg self.thread_recv_packets() # start waiting before requesting def on_exit(self): # GUI uses this on_exit print "Cleaning up..." import pprint savefile = open(config_file, "w") savefile.write("# Client configuration file\n") savefile.write("# Please note - ALL COMMENTS IN THIS FILE WILL BE DESTROYED\n") # ^ I place all comments in config.py.default instead, or the docs pprint.pprint(self.config, savefile) savefile.close() sys.exit()