def crypt_CFB(instream, outstream, algorithm, key, register, direction): """'Crypt a string in cipher-feedback mode. :Parameters: - `instream`: StringIO/file incoming - `outstream`: StringIO/file outgoing - `algorithm`: integer symmetric cipher constant - `key`: string encryption/decryption key - `register`: string initialization vector (IV) to feed register - `direction`: string 'encrypt' or 'decrypt' setting CFB mode :Returns: string ciphertext or cleartext OpenPGP performs CFB shifts on blocks of characters the same size as the block used by the symmetric cipher - for example, CAST5 works on 64-bit blocks, therefore CAST5 CFB shifts use 8 bytes at a time (the remaining cleartext bytes that do not completely fill an 8-byte block at the end of a message are XOR'ed with the "left-most" bytes of the encrypted mask). """ ciphermod = _import_cipher(algorithm) cipher = ciphermod.new(key, ciphermod.MODE_ECB) encrypt = cipher.encrypt shift = ciphermod.block_size # number of bytes to process (normally 8) # after tweaking, there's still not much difference in speed (~2% max) int2str = STN.int2str str2int = STN.str2int apply_mask = lambda c,m: int2str(str2int(c) ^ str2int(m)) if register is None: register = STN.prepad(shift) # use an IV full of 0x00 if shift > len(register): raise PGPCryptoError, "CFB shift amount->(%s) can't be larger than the feedback register->(%s)." % (shift, len(register)) while True: inblock = instream.read(shift) # block size = shift size chunk = StringIO() if inblock: mask = encrypt(register) chunk.seek(0) for i, c in enumerate(inblock): chunk.write(apply_mask(c, mask[i])) chunk.truncate() chunk.seek(0) outblock = chunk.read() if 'encrypt' == direction: register = outblock elif 'decrypt' == direction: register = inblock outstream.write(outblock) else: break
def __cat_subpkt_block(subpkts): subpkt_d = ''.join([x._d for x in subpkts]) subpkt_d_len = STN.int2str(len(subpkt_d)) if 2 >= len(subpkt_d_len): return STN.prepad(2, subpkt_d_len) + subpkt_d else: raise PGPValueError, "Subpacket block length (%s) is unacceptable." % len(subpkt_d_len)
def __cat_subpkt_block(subpkts): subpkt_d = ''.join([x._d for x in subpkts]) subpkt_d_len = STN.int2str(len(subpkt_d)) if 2 >= len(subpkt_d_len): return STN.prepad(2, subpkt_d_len) + subpkt_d else: raise PGPValueError, "Subpacket block length (%s) is unacceptable." % len( subpkt_d_len)
def create_LiteralDataBody(*args, **kwords): """Create a LiteralDataBody instance. :Parameters: - `args`: parameter list, will accept keyword dictionary as args[0] - `kwords`: keyword parameters :Keywords: - `data`: string of literal data - `modified`: integer timestamp of file modification - `format`: character 'b' or 't' indicating binary or text - `filename`: optional filename associated with data :Returns: `LiteralDataBody` instance Use the filename '_CONSOLE' to signal extra-careful handling of output. """ try: if isinstance(args[0], dict): kwords = args[0] except IndexError: pass data = kwords.get('data') if not data: data = '' modified = kwords.setdefault('modified', 0) format = kwords.setdefault('format', 'b') filename = kwords.setdefault('filename', 'outfile') if format not in ['b', 't']: raise PGPValueError, "Literal data format must be 'b' or 't'. Received->(%s)" % str(format) fnlen_d = STN.int2str(len(filename)) if 1 < len(fnlen_d): raise PGPValueError, "Filename length (%s) exceeded 1 octet capacity." % len(fnlen_d) modified_d = STN.prepad(4, STN.int2str(modified)) d = ''.join([format, fnlen_d, filename, modified_d, data]) return LiteralDataBody(d)
def decrypt(encpkt, passphrase='', sespkt=None, keypkt=None): """Decrypt messages in symmetrically encrypted packets (types 9 & 18). :Parameters: - `encpkt`: packet containing encrypted data (symmetrically encrypted or integrity protected packet, types 9 & 18) - `passphrase`: string decryption passphrase (see below) - `sespkt`: optional session key packet - `keypkt`: optional public key packet :Returns: string cleartext :Exceptions: - `PGPError`: implementation error - `PGPDecryptionFailure`: decryption failed - `PGPSessionDecryptionFailure`: session decryption failed This is the all-in-one handler for "normal" decryption - that is, decrypting symmetrically encrypted (type 9) and symmetrically encrypted integrity protected (type 18) data. By consolidating everything to one function it will be easier (hopefully) to manage secure data handling in the future. Because this function focuses on decrypting information in packet types 9 and 18, decrypted data is by definition (or "will be", due to the mechanics of this function) the data used to build an OpenPGP message (not the message instance). It is up to the API layer to automate things like "if compressed, decompress" or "if signed, verify." """ result = key = algorithm = None # key & algo set to force integrity failure if sespkt: ses = sespkt.body if PKT_PUBKEYSESKEY == sespkt.tag.type: if ses.keyid == keypkt.body.id: try: seckeys = decrypt_secret_key(keypkt, passphrase) except PGPError: # catch MPI value error due to .. raise PGPCryptoError("Public key encrypted session key checksum failed.") if keypkt.body.alg in [ASYM_RSA_E, ASYM_RSA_EOS]: cipher_tuple = (ses.RSA_me_modn.value,) key_tuple = (keypkt.body.RSA_n.value, seckeys[0]) elif keypkt.body.alg in [ASYM_ELGAMAL_E, ASYM_ELGAMAL_EOS]: cipher_tuple = (ses.ELGAMAL_gk_modp.value, ses.ELGAMAL_myk_modp.value) key_tuple = (keypkt.body.ELGAMAL_p.value, seckeys[0]) else: raise NotImplementedError("Unsupported public encryption algorithm->(%s)." % keypkt.body.alg) else: # shouldn't happen, programmer error raise PGPCryptoError("The public encryption key did not match the session key target.") # should be ready to decrypt session key with key tuple padded_key = decrypt_public(ses.alg_pubkey, key_tuple, cipher_tuple) if '\x02' == padded_key[0]: # handle EME-PKCS1-v1_5 encoding idx = padded_key.find('\x00') # 0x00 used as padding separator if -1 != idx and 8 <= idx: message = padded_key[idx+1:] algorithm = STN.str2int(message[0]) # required for both.. chksum = STN.str2int(message[-2:]) key = message[1:len(message)-2] # ..symencdata and symencintdata if chksum != STN.checksum(key): raise PGPCryptoError("Public Key encrypted session key checksum failed.") else: raise PGPCryptoError("Misplaced \\x00 in session key padding, located at index->(%s)." % idx) else: raise PGPCryptoError("Session key didn't start with \\x02, received->()." % hex(ord(padded_key[0]))) elif PKT_SYMKEYSESKEY == sespkt.tag.type: # using symmetric key session key algorithm = ses.alg key = string2key(ses.s2k, algorithm, passphrase) if ses.has_key: iv = STN.prepad(_import_cipher(algorithm).block_size) padded_key = decrypt_symmetric(algorithm, key, ciphertext, iv) algorithm = padded_key[0] key = padded_key[1:] else: raise NotImplementedError("Unrecognized session key type-(%s)." % sespkt.tag.type) # 'algorithm' & 'key' should be set, it's time to decrypt the message if PKT_SYMENCINTDATA == encpkt.tag.type: bs = _import_cipher(algorithm).block_size iv = STN.prepad(bs) cleartext = decrypt_symmetric(algorithm, key, encpkt.body.data, iv) prefix = cleartext[:bs+2] clearmsg_d = cleartext[bs+2:-22] mdc_d = cleartext[-22:] mdc = mdc_d[-20:] if mdc != sha.new(''.join([prefix, clearmsg_d, '\xd3\x14'])).digest(): raise PGPCryptoError("Integrity hash check failed.") elif PKT_SYMENCDATA == encpkt.tag.type: if None == key == algorithm: # non-integrity allows default key & alg key = md5.new(passphrase).digest algorithm = SYM_IDEA clearmsg_d = decrypt_symmetric_resync(algorithm, key, encpkt.body.data) return clearmsg_d
int2str = STN.int2str ctx = StringIO() # signature context to hash ctx_write = ctx.write if 3 == version: ################### v3 ctx_write(int2str(sigtype)[0]) # signature type ctx_write(int2str(kwords['created'])[:4]) # creation timestamp elif 4 == version: ################### v4 ctx_write('\x04') # version ctx_write(int2str(sigtype)[0]) # signature type ctx_write(int2str(keyalg)[0]) # public key alg ctx_write(int2str(hashalg)[0]) # hash algorithm subhash = ''.join([x._d for x in hashed_subpkts]) ctx_write(STN.prepad(2, int2str(len(subhash)))) # hashed len ctx_write(subhash) # hashed subpkts ctx_len = ctx.tell() ctx_write('\x04\xff') # start trailer ctx_write(STN.int2quadoct(ctx_len)[-4:]) # hashed data length else: raise NotImplementedError("Signature version->(%s) is not supported." % version) ctx.seek(0) ctx_hash = hash_context(version, hashalg, sigtype, ctx, target, primary) ctx.close() if keyalg in [ASYM_RSA_S, ASYM_RSA_EOS]: ctx_hash = pad_rsa(hashalg, ctx_hash, signer.body.RSA_n.bit_length) keytup = signer.body.RSA_n.value, seckey