3

I'm working on a project using Pyramid 1.3 (Python 2.7) and storing data in MySQL. I have a table of email addresses, and I would like to encrypt them for storage. I am trying to encrypt them in the application, and then will decrypt them for viewing. I'm not going for complete security but am mainly aiming to obfuscate the data enough were the database itself compromised.

I'm using PyCrypto with AES, and have been trying to follow some posts on here and some web tutorials I found. The closest I found so far is this post, and it seems to work, at least encrypting it. I follow that and get something like "7hBAQrWhJRnL9YdBGJfRErGFwGi3aC6noGzYTrGwAoQ=" stored in the database. But the decrypt function keeps erroring with this:

UnicodeDecodeError: 'ascii' codec can't decode byte 0xa1 in position 1: ordinal not in range(128)

I came across some unicode presentation about Python which sort of helped me make more sense of it but I still keep getting the same error.

Is there a straightforward tutorial on how to encode, store in a database, pull out of database, and decode a source data string?

Do I need a specific collation on the database column? Does the field need to be a certain type? So far I've been using a default collation and setting it to VARCHAR, assuming that I was storing a string. It sounds like I've got some encoding problem somewhere with incompatible types or something but my head is spinning on where I need to change something.

Any better pointers or anything else I can provide? I can show my code but its basically a copy of the link above... I was just trying to get a proof of concept working before modifying it too much.

edit: some sample source... In MySQL, the table is id (int) client_id (int) emailaddress varchar(100) utf8mb4_general_ci (I've been playing around with the collations, I have no idea what it should be!)

Python:

from base64 import b64encode, b64decode, urlsafe_b64decode, urlsafe_b64encode

BLOCK_SIZE = 32
INTERRUPT = u'\u0001'
PAD = u'\u0000'
def AddPadding(data, interrupt, pad, block_size):
    new_data = ''.join([data, interrupt])
    new_data_len = len(new_data)
    remaining_len = block_size - new_data_len
    to_pad_len = remaining_len % block_size
    pad_string = pad * to_pad_len
    return ''.join([new_data, pad_string])
def StripPadding(data, interrupt, pad):
    return data.rstrip(pad).rstrip(interrupt)#data.rsplit(interrupt,1)[0]#rstrip(pad).rstrip(interrupt)

SECRET_KEY = u'a1b2c3d4e5f6g7h8a1b2c3d4e5f6g7h8'
IV = u'12345678abcdefgh'

cipher_for_encryption = AES.new(SECRET_KEY, AES.MODE_CBC, IV)
cipher_for_decryption = AES.new(SECRET_KEY, AES.MODE_CBC, IV)

def EncryptWithAES(encrypt_cipher, plaintext_data):
    plaintext_padded = AddPadding(plaintext_data, INTERRUPT, PAD, BLOCK_SIZE)
    encrypted = encrypt_cipher.encrypt(plaintext_padded)
    return urlsafe_b64encode(encrypted)
def DecryptWithAES(decrypt_cipher, encrypted_data):
    decoded_encrypted_data = urlsafe_b64decode(encrypted_data)
    decrypted_data = decrypt_cipher.decrypt(decoded_encrypted_data)
    return StripPadding(decrypted_data, INTERRUPT, PAD)

#encrypts it
posted_singleaddress = EncryptWithAES(cipher_for_encryption, posted_singleaddress)

#"me@mail.com" inserts "Ktpr49Uzn99HZXbmqEzGKlWo9wk-XBMXGZl_iyna-8c=" into the database

clientemails is the list of emails from the table above. I get the error when uncommenting out:

#if clientemails:
#    decrypted = DecryptWithAES(cipher_for_decryption, clientemails[0].emailaddress)

I was just trying to decode the first item just to try and get it to work but that's the part that seems to be giving it fits now....

4

1 回答 1

2

PyCrypto 的一般规则是加密密钥、IV、明文、填充和密文应始终定义为二进制字符串,而不是文本。您为它们使用 Unicode 的事实本身就是问题的根源。

另一个问题是您以AES.new十六进制编码形式传递给 key 和 IV,因此前者是 256 位,后者是 128 位。这似乎仍然有效,但我猜你的意图是使用 AES128 - 它有一个 128 位密钥。因此,您需要将其转换为二进制,例如通过unhexlify:两个字符串 b'34' 将映射到单字节 '\x34'。IV 需要两倍长。

因此,在您的代码中最好有:

from binascii import unhexlify

INTERRUPT = b'\x01'
PAD = b'\x00'
SECRET_KEY = unhexlify('a1b2c3d4e5f6g7h8a1b2c3d4e5f6g7h8')
IV = unhexlify('12345678abcdefgh'*2)

如果您需要加密文本,您将首先对其进行编码(例如编码为 UTF-8),然后将其传递给您的函数EncryptWithAES()。另请参阅取自PyCrypto API的示例:

from Crypto.Cipher import AES
from Crypto import Random

key = b'Sixteen byte key'
iv = Random.new().read(AES.block_size)
cipher = AES.new(key, AES.MODE_CFB, iv)
msg = iv + cipher.encrypt(b'Attack at dawn')

加密步骤的结果(即密文)又是一个二进制字符串。为了将它直接存储在 MySQL DB 中,您必须使用 aBINARYVARBINARYtype 列。

于 2013-02-06T22:02:39.450 回答