def encrypt_public_session(keypkt, key, symalg): """Create a public-key encrypted session key. :Parameters: - `keypkt`: either an `OpenPGP.packet.PublicKey.PublicKey` (or subclass) instance or encryption passphrase string - `key`: string encryptrion algorithm used for `symalg` - `symalg`: integer symmetric encryption algorithm constant :Returns: `OpenPGP.packet.PublicKeyEncryptedSessionKey.PublicKeyEncryptedSessionKey` instance """ from openpgp.sap.pkt.Packet import create_Packet from openpgp.sap.pkt.PublicKeyEncryptedSessionKey import create_PublicKeyEncryptedSessionKeyBody pubalg = keypkt.body.alg rnd_prefix = [] i = 0 # fixing the "intended length" business to 127 while i <= 63 - len(key): # seems proper, but probably inefficient rnd_byte = gen_random(1) if '\x00' != rnd_byte: rnd_prefix.append(rnd_byte) i += 1 chksum = STN.int2str(STN.checksum(key))[:2] if pubalg in [ASYM_RSA_EOS, ASYM_RSA_E]: key_tup = (keypkt.body.RSA_n.value, keypkt.body.RSA_e.value) elif pubalg in [ASYM_ELGAMAL_EOS, ASYM_ELGAMAL_E]: key_tup = (keypkt.body.ELGAMAL_p.value, keypkt.body.ELGAMAL_g.value, keypkt.body.ELGAMAL_y.value) else: raise NotImplementedError("Unsupported public key algorithm->(%s)." % pubalg) padded_key = ''.join(['\x02', ''.join(rnd_prefix), '\x00', STN.int2str(symalg)[0], key, chksum]) cipher_tup = encrypt_public(pubalg, padded_key, key_tup) sesbody = create_PublicKeyEncryptedSessionKeyBody( keyid=keypkt.body.id, alg=pubalg, mpis=cipher_tup) return create_Packet(PKT_PUBKEYSESKEY, sesbody._d)
def decrypt_secret_key(keypkt, passphrase=''): """Retrieve decryption key values from a secret key packet. :Parameters: - `keypkt`: `OpenPGP.packet.SecretKey.SecretKey` instance or `OpenPGP.packet.SecretSubkey.SecretSubkey` instance - `passphrase`: optional decryption string :Returns: tuple of secret key integer values :Exceptions: - `PGPKeyDecryptionFailure`: secret key values did not encrypt properly Secret key tuples: - RSA keys: (d, p, q, u) - ElGamal keys: (x, ) - DSA keys: (x, ) :note: As far as key decryption goes, the only real check performed is the internal MPI check that the header, size, etc. make sense. Chances are slight that a failed decryption will render sensible MPIs. This is why the integrity checks are so important. """ if not hasattr(keypkt.body, 's2k_usg'): raise PGPCryptoError("Key material does not contain secret key values (missing s2k usage).") if 0 == keypkt.body.s2k_usg: if keypkt.body.alg in [ASYM_RSA_E, ASYM_RSA_EOS]: key_tuple = (keypkt.body.RSA_d.value, keypkt.body.RSA_p.value, keypkt.body.RSA_q.value, keypkt.body.RSA_u.value) elif keypkt.body.alg in [ASYM_ELGAMAL_E, ASYM_ELGAMAL_EOS]: key_tuple = keypkt.body.ELGAMAL_x.value, # coerce tuple elif keypkt.body.alg in [ASYM_DSA]: key_tuple = keypkt.body.DSA_x.value, # coerce tuple else: # ..'idx' comes into play during the integrity check if 3 >= keypkt.body.version: if keypkt.body.alg in [ASYM_RSA_E, ASYM_RSA_EOS]: alg = keypkt.body.alg_sym if keypkt.body.s2k_usg in [254, 255]: k = string2key(keypkt.body.s2k, alg, passphrase) else: k = md5.new(passphrase).digest() # extra work required since MPIs integers are encrypted w/out # their lengths # create MPIs w/ encrypted integer octets secRSA_d, idx = MPI.strcalc_mpi(keypkt.body._enc_d, 0) secRSA_p, idx = MPI.strcalc_mpi(keypkt.body._enc_d, idx) secRSA_q, idx = MPI.strcalc_mpi(keypkt.body._enc_d, idx) secRSA_u, idx = MPI.strcalc_mpi(keypkt.body._enc_d, idx) # decrypt integer octets RSA_d_int_d = decrypt_symmetric(alg, k, secRSA_d._int_d, keypkt.body.iv) RSA_p_int_d = decrypt_symmetric(alg, k, secRSA_d._int_d, keypkt.body.iv) RSA_q_int_d = decrypt_symmetric(alg, k, secRSA_d._int_d, keypkt.body.iv) RSA_u_int_d = decrypt_symmetric(alg, k, secRSA_d._int_d, keypkt.body.iv) # translate integer values RSA_d_value = STN.str2int(RSA_d_int_d) RSA_p_value = STN.str2int(RSA_p_int_d) RSA_q_value = STN.str2int(RSA_q_int_d) RSA_u_value = STN.str2int(RSA_u_int_d) key_tuple = (RSA_d_value, RSA_p_value, RSA_q_value, RSA_u_value) sec_d = ''.join([secRSA_d._d[:2], RSA_d_int_d, secRSA_p._d[:2], RSA_p_int_d, secRSA_q._d[:2], RSA_q_int_d, secRSA_q._d[:2], RSA_q_int_d]) else: raise NotImplementedError("Unsupported v3 decryption key algorithm->(%s)." % keypkt.body.alg) elif 4 == keypkt.body.version: alg = keypkt.body.alg_sym k = string2key(keypkt.body.s2k, alg, passphrase) sec_d = decrypt_symmetric(alg, k, keypkt.body._enc_d, keypkt.body.iv) if keypkt.body.alg in [ASYM_RSA_E, ASYM_RSA_EOS, ASYM_RSA_S]: RSA_d, idx = MPI.strcalc_mpi(sec_d, 0) RSA_p, idx = MPI.strcalc_mpi(sec_d[idx:], idx) RSA_q, idx = MPI.strcalc_mpi(sec_d[idx:], idx) RSA_u, idx = MPI.strcalc_mpi(sec_d[idx:], idx) key_tuple = (RSA_d.value, RSA_p.value, RSA_q.value, RSA_u.value) elif keypkt.body.alg in [ASYM_ELGAMAL_E, ASYM_ELGAMAL_EOS]: ELGAMAL_x, idx = MPI.strcalc_mpi(sec_d, 0) key_tuple = ELGAMAL_x.value, # coerce tuple elif keypkt.body.alg in [ASYM_DSA]: DSA_x, idx = MPI.strcalc_mpi(sec_d, 0) key_tuple = DSA_x.value, # coerce tuple else: raise NotImplementedError("Unsupported public key algorithm->(%s)." % keypkt.body.alg) else: raise NotImplementedError, "Unsupported key version->(%s)." % keypkt.body.version # check integrity if 254 == keypkt.body.s2k_usg: if sec_d[idx:] != sha.new(sec_d[:idx]).digest(): raise PGPCryptoError("Integrity hash check failed.") elif 255 == keypkt.body.s2k_usg: if keypkt.body.chksum != STN.checksum(sec_d): raise PGPCryptoError("Integrity checksum failed.") return key_tuple
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