mirror of
https://github.com/theupdateframework/python-tuf
synced 2026-05-24 10:08:28 +00:00
200 lines
5.4 KiB
Python
Executable file
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
|