Строка кодирования/декодирования Python AES и сохранение в MySQL

Я работаю над проектом, используя Pyramid 1.3 (Python 2.7) и храня данные в MySQL. У меня есть таблица адресов электронной почты, и я хотел бы зашифровать их для хранения. Я пытаюсь зашифровать их в приложении, а потом расшифрую для просмотра. Я не стремлюсь к полной безопасности, но в основном стремлюсь запутать данные в достаточной степени, если сама база данных скомпрометирована.

Я использую PyCrypto с AES и пытаюсь следить за некоторыми сообщениями здесь и некоторыми веб-учебниками, которые я нашел. Самое близкое, что я нашел, это этот пост, и кажется, это работает, по крайней мере, шифрование. Я следую этому и получаю что-то вроде "7hBAQrWhJRnL9YdBGJfRErGFwGi3aC6noGzYTrGwAoQ=" в базе данных. Но функция расшифровки продолжает выдавать ошибку:

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

Я наткнулся на какую-то презентацию Unicode о Python, которая помогла мне лучше понять это, но я все еще продолжаю получать ту же ошибку.

Есть ли простой учебник о том, как кодировать, сохранять в базе данных, извлекать из базы данных и декодировать строку исходных данных?

Нужна ли мне определенная сортировка в столбце базы данных? Должно ли поле быть определенного типа? До сих пор я использовал сопоставление по умолчанию и устанавливал его на VARCHAR, предполагая, что сохраняю строку. Похоже, у меня где-то проблема с кодировкой с несовместимыми типами или что-то в этом роде, но у меня голова кружится, где мне нужно что-то изменить.

Любые лучшие указатели или что-нибудь еще, что я могу предоставить? Я могу показать свой код, но в основном это копия ссылки выше... Я просто пытался получить доказательство работоспособности концепции, прежде чем слишком сильно ее модифицировать.

редактировать: некоторый образец источника... В MySQL таблица имеет вид id (int) client_id (int) emailaddress varchar (100) utf8mb4_general_ci (я играл с сопоставлениями, я понятия не имею, что это должно быть!)

Питон:

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)

#"[email protected]" inserts "Ktpr49Uzn99HZXbmqEzGKlWo9wk-XBMXGZl_iyna-8c=" into the database

clientemails — это список адресов электронной почты из таблицы выше. Я получаю сообщение об ошибке при раскомментировании:

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

Я просто пытался декодировать первый элемент, просто чтобы попытаться заставить его работать, но это та часть, которая, кажется, сейчас подходит ....


person Peter Tirrell    schedule 26.05.2012    source источник
comment
Можете ли вы опубликовать проблемный участок вашего кода?   -  person Blender    schedule 26.05.2012
comment
Отредактировано с дополнениями...   -  person Peter Tirrell    schedule 26.05.2012
comment
Вы убедились, что ваши AddPadding и StripPadding работают так, как вы ожидаете? Второе определение подходит, выдает ли оно исключение или просто дает вам неверные данные? Если он выдает исключение, что это за исключение, можем ли мы увидеть обратную трассировку?   -  person john-charles    schedule 26.05.2012
comment
Я продолжаю получать сообщение об ошибке UnicodeDecodeError: кодек ascii не может декодировать байт 0xa1 в позиции 1: порядковый номер не в диапазоне (128). Похоже, это относительно распространено и связано с преобразованием между ascii и unicode или чем-то еще.   -  person Peter Tirrell    schedule 26.05.2012
comment
Трассировка выглядит так: Файл /home/peter/dev/env/myproj/myproj/views.py, строка 320, в view_clientemails decrypted = DecryptWithAES(cipher_for_decryption, email.emailaddress) Файл /home/peter/dev/env/myproj/ myproj/views.py, строка 114, в DecryptWithAES возвращает StripPadding(decrypted_data, INTERRUPT, PAD) Файл /home/peter/dev/env/myproj/myproj/views.py, строка 79, в StripPadding возвращает data.rstrip(pad) .rstrip(прерывание)   -  person Peter Tirrell    schedule 26.05.2012
comment
Ладно, знаешь, что теперь? Поэтому я заменил функции b64encode/decode и вместо этого сделал .encode('hex') для шифрования и .decode('hex') после его извлечения. Похоже, он хранит шестнадцатеричное значение в базе данных и успешно записывает его обратно после расшифровки. Итак, что, черт возьми, происходит с кодированием/декодированием b64, и на что я наткнулся, используя шестнадцатеричный код сейчас?   -  person Peter Tirrell    schedule 26.05.2012
comment
Аааа, и мне также пришлось снова добавить cipher_for_decryption = AES.new(SECRET_KEY, AES.MODE_CBC, IV) перед оператором расшифровки!   -  person Peter Tirrell    schedule 26.05.2012


Ответы (1)


Общее правило PyCrypto заключается в том, что криптографические ключи, IV, открытые тексты, заполнители и зашифрованные тексты всегда должны определяться как двоичные строки, а не как текст. Тот факт, что вы используете для них Unicode, сам по себе является источником проблем.

Другая проблема заключается в том, что вы переходите к ключу AES.new и 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, вы должны использовать столбец типа BINARY или VARBINARY.

person SquareRootOfTwentyThree    schedule 06.02.2013