python-tuf/evpy/cipher.py

200 lines
5.4 KiB
Python
Executable file

#! /usr/bin/env python
"""
cipher.py
Written by Geremy Condra
Released on 18 March 2010
Licensed under MIT License
This module provides a basic interface to OpenSSL's EVP
cipher functions.
All the functions in this module raise CipherError on
malfunction.
From an end-user perspective, this module should be used
in situations where you want to have a single generally
human-readable or human-generated key used for both
encryption and decryption.
This means that as a general rule, if your application
involves transmitting this key over an insecure channel
you should not be using this module, but rather
evpy.envelope.
Usage:
>>> from evpy import cipher
>>> message = b"this is data"
>>> pw = b"mypassword"
>>> salt, iv, enc = cipher.encrypt(message, pw)
>>> cipher.decrypt(salt, iv, enc, pw)
'this is data'
"""
import ctypes
import time
import evp
class CipherError(evp.SSLError):
pass
def _strengthen_password(pw, iv, salt=None):
# add the hash
evp.OpenSSL_add_all_digests()
# build the key buffer
key = ctypes.create_string_buffer(24)
# either take the existing salt or build a new one
if not salt:
salt = ctypes.create_string_buffer(8)
# get the needed entropy, bailing if it doesn't work in
# the first thousand tries
for i in range(1000):
if evp.RAND_bytes(salt, 8): break
else:
raise CipherError("Could not generate enough entropy")
# extract the salt
salt = salt.raw
# get the hash
evp_hash = evp.EVP_get_digestbyname("sha512")
if not evp_hash:
raise CipherError("Could not create hash object")
# fill the key
if not evp.EVP_BytesToKey(evp.EVP_aes_192_cbc(), evp_hash, salt, pw, len(pw), 1000, key, iv):
raise CipherError("Could not strengthen key")
# go home
return salt, key.raw
def encrypt(data, password):
"""Encrypts the given data, raising CipherError on failure.
This uses AES192 to encrypt and strengthens the given
passphrase using SHA512.
Usage:
>>> from evpy import cipher
>>> f = open("test/short.txt", "rb")
>>> data = f.read()
>>> pw = b"mypassword"
>>> salt, iv, enc = cipher.encrypt(data, pw)
>>> cipher.decrypt(salt, iv, enc, pw) == data
True
"""
# ensure data exists
if not len(data):
raise CipherError("Data must actually exist")
if not len(password):
raise CipherError("Password must actually exist")
# build and initialize the context
ctx = evp.EVP_CIPHER_CTX_new()
if not ctx:
raise CipherError("Could not create context")
evp.EVP_CIPHER_CTX_init(ctx)
# get the cipher object
cipher_object = evp.EVP_aes_192_cbc()
if not cipher_object:
raise CipherError("Could not create cipher object")
# finish the context and cipher object
if not evp.EVP_EncryptInit_ex(ctx, cipher_object, None, None, None):
raise CipherError("Could not finish context")
# build the randomized iv
iv_length = evp.EVP_CIPHER_CTX_iv_length(ctx)
iv = ctypes.create_string_buffer(iv_length)
# get the needed entropy, bailing if it doesn't work in
# the first thousand tries
for i in range(1000):
if evp.RAND_bytes(iv, iv_length): break
else:
raise CipherError("Not enough entropy for IV")
output_iv = iv.raw
# strengthen the password into an honest-to-goodness key
salt, aes_key = _strengthen_password(password, iv)
# initialize the encryption operation
if not evp.EVP_EncryptInit_ex(ctx, None, None, aes_key, iv):
raise CipherError("Could not start encryption operation")
# build the output buffer
buf = ctypes.create_string_buffer(len(data) + 16)
written = ctypes.c_int(0)
final = ctypes.c_int(0)
# update
if not evp.EVP_EncryptUpdate(ctx, buf, ctypes.byref(written), data, len(data)):
raise CipherError("Could not update ciphertext")
output = buf.raw[:written.value]
# finalize
if not evp.EVP_EncryptFinal_ex(ctx, buf, ctypes.byref(final)):
raise CipherError("Could not finalize ciphertext")
output += buf.raw[:final.value]
# ...and go home
return salt, output_iv, output
def decrypt(salt, iv, data, password):
"""Decrypts the given data, raising CipherError on failure.
Usage:
>>> from evpy import cipher
>>> f = open("test/short.txt", "rb")
>>> data = f.read()
>>> pw = b"mypassword"
>>> salt, iv, enc = cipher.encrypt(data, pw)
>>> cipher.decrypt(salt, iv, enc, pw) == data
True
"""
# ensure inputs are the correct size
if not len(data):
raise CipherError("Data must actually exist")
if not len(password):
raise CipherError("Password must actually exist")
if len(salt) != 8:
raise CipherError("Incorrect salt size")
if len(iv) != 16:
raise CipherError("Incorrect iv size")
# build and initialize the context
ctx = evp.EVP_CIPHER_CTX_new()
if not ctx:
raise CipherError("Could not create context")
evp.EVP_CIPHER_CTX_init(ctx)
# get the cipher object
cipher_object = evp.EVP_aes_192_cbc()
if not cipher_object:
raise CipherError("Could not create cipher object")
# build the key
salt, key = _strengthen_password(password, iv, salt)
# start decrypting the ciphertext
if not evp.EVP_DecryptInit_ex(ctx, cipher_object, None, key, iv):
raise CipherError("Could not open envelope")
# build the output buffers
buf = ctypes.create_string_buffer(len(data) + 16)
written = ctypes.c_int(0)
final = ctypes.c_int(0)
# update
if not evp.EVP_DecryptUpdate(ctx, buf, ctypes.byref(written), data, len(data)):
raise CipherError("Could not update plaintext")
output = buf.raw[:written.value]
# finalize
if not evp.EVP_DecryptFinal_ex(ctx, buf, ctypes.byref(final)):
raise CipherError("Could not finalize decryption")
output += buf.raw[:final.value]
return output