2010-07-01 3 views
7

Я пишу фрагмент кода для шифрования текста с использованием симметричного шифрования. Но это не возвращается с правильным результатом ...Проблема PyCrypto с использованием AES + CTR

from Crypto.Cipher import AES 
import os 

crypto = AES.new(os.urandom(32), AES.MODE_CTR, counter = lambda : os.urandom(16)) 
encrypted = crypto.encrypt("aaaaaaaaaaaaaaaa") 
print crypto.decrypt(encrypted) 

Здесь расшифрованный текст отличается от оригинала.

Я не очень разбираюсь в криптографии, поэтому, пожалуйста, несите меня. Я понимаю, что режим CTR требует, чтобы функция «счетчик» предоставляла случайный счетчик каждый раз, но зачем ему 16 байтов, когда мой ключ равен 32 байтам, и он настаивает на том, что мое сообщение тоже кратно 16 байт? Это нормально?

Я предполагаю, что он не возвращается к исходному сообщению, потому что счетчик изменился между шифрованием и расшифровкой. Но тогда, как это должно работать теоретически? Что я делаю не так? Во всяком случае, я вынужден вернуться к ЕЦБ, пока не выясню это :(

ответ

10

counter должен вернуть то же самое на дешифрования, как это было на шифрование, как вы интуитивно, так, один (НЕ ОБЕСПЕЧИТЬ НА ВСЕ) способ сделать это:

>>> secret = os.urandom(16) 
>>> crypto = AES.new(os.urandom(32), AES.MODE_CTR, counter=lambda: secret) 
>>> encrypted = crypto.encrypt("aaaaaaaaaaaaaaaa") 
>>> print crypto.decrypt(encrypted) 
aaaaaaaaaaaaaaaa 

CTR является block cipher, поэтому ограничение «16-в-время», которое, кажется, удивляет вас, довольно естественное.

Конечно, так называемый «счетчик» возвращает то же значение при каждом звонке is grossly insecure. Не занимает много, чтобы сделать лучше, например ....:

import array 

class Secret(object): 
    def __init__(self, secret=None): 
    if secret is None: secret = os.urandom(16) 
    self.secret = secret 
    self.reset() 
    def counter(self): 
    for i, c in enumerate(self.current): 
     self.current[i] = c + 1 
     if self.current: break 
    return self.current.tostring() 
    def reset(self): 
    self.current = array.array('B', self.secret) 

secret = Secret() 
crypto = AES.new(os.urandom(32), AES.MODE_CTR, counter=secret.counter) 
encrypted = crypto.encrypt(16*'a' + 16*'b' + 16*'c') 
secret.reset() 
print crypto.decrypt(encrypted) 
+0

awesome. Хорошо, я понимаю. , так что CTR не имеет преимуществ перед ECB, если я просто хочу зашифровать одну или несколько вещей? Я просто хочу хранить несколько паролей через сеансы. Мне даже нужен AES, или я должен использовать что-то более простое? – xster

+3

На самом деле CTR может шифровать любое произвольное количество текста; он преобразует блочный шифр в генератор ключевого потока. Нет фактической причины для ограничения того, что в этом случае количество входов будет кратным размеру блока. –

+0

PyCrypto, кажется, дает ошибку, когда вход не кратен 16 байтам, но – xster

1

Инициализационный вектор («счетчик») должен оставаться таким же, как это делает ключ, между шифрованием и расшифровкой. так что вы можете кодировать один и тот же текст в миллион раз и каждый раз получать различный зашифрованный текст (предотвращая некоторые известные атаки открытого текста и сопоставление/атаки). поток, инициализации IV в то же значение, что вы начали, когда вы начали шифровать этот поток.

См http://en.wikipedia.org/wiki/Initialization_vector для получения информации о векторах инициализации.

Обратите внимание, что os.urandom (16) не является «детерминированным», что является требованием для функций счетчика. Я предлагаю вам использовать функцию increment, так как это режим CTR. Начальное значение счетчика должно быть случайным, но последующие значения должны быть полностью предсказуемы из начального значения (детерминированного). Первоначальное значение может быть позаботиться о вас (я не знаю подробности)

О ключах, IV и размерах ввода, это похоже на то, что вы выбрали шифр размером 16 байт. Все, что вы описали, соответствует этому и кажется мне нормальным.

1

why does it need it to be 16 bytes when my key is 32 bytes

Она должна быть такой же длины, как размер блока шифра. Режим CTR просто шифрует счетчик, а XOR - открытый текст с зашифрованным блоком счетчика.

Примечания:

  1. значение счетчика должно быть уникальным - если вы когда-либо использовать один и тот же значение счетчика для шифрования двух различных открытых текстов под тем же ключом, вы просто отдал свой ключ.
  2. как IV, счетчик НЕ секретный - просто отправьте его вместе с зашифрованным текстом. Если вы сделаете код более сложным, пытаясь сохранить его в секрете, вы, вероятно, стреляете в ногу.
  3. Значение счетчика необязательно должно быть непредсказуемым - начиная с нуля и добавляя по одному для каждого блока. Но учтите, что если вы шифруете несколько сообщений, вам нужно отслеживать уже принятые значения счетчиков, т. Е. Вам нужно отслеживать, сколько блоков уже было зашифровано с помощью этого ключа (и вы не можете использовать тот же ключ в разных случаях вашей программы или на разных машинах).
  4. простой текст может быть любой длины - режим CTR превращает блок-шифр в потоковый шифр.

Стандартная оговорка: Crypto is hard. Если вы не понимаете, что делаете, вы получите :.

I just want to store some passwords across sessions.

Использование scrypt. scrypt включает в себя encrypt и decrypt, которые используют AES-CTR с ключом, созданным паролем.

$ pip install scrypt 

$ python 
>>> import scrypt 
>>> import getpass 
>>> pw = getpass.getpass("enter password:") 
enter password: 
>>> encrypted = scrypt.encrypt("Guido is a space alien.",pw) 
>>> out = scrypt.decrypt(encrypted,pw) 
>>> out 
'Guido is a space alien.' 
0

Может быть, я, безусловно, поздно, и я, возможно, пропустили предыдущие ответы, но я не нашел четкого заявления о том, как это должно (по крайней мере, ИМХО) производится в соответствии с пакетами PyCrypto.

Пакет Crypto.Util.Counter предоставляет вызываемые счетчики состояния, которые очень полезны, но было легко, по крайней мере, для меня использовать их ненадлежащим образом.

Вам необходимо создать счетчик, например. ctr = Counter.new('parameters here'). Каждый раз, когда ваш счетчик вызывается с помощью объекта шифрования в режиме счетчика, чтобы зашифровать сообщение, он увеличивается. Это необходимо для хорошей практики криптографии, иначе информация об равных блоках может протекать из зашифрованного текста.

Теперь вы не можете вызвать функцию дешифрования на одном и том же объекте шифрования, потому что он будет вызывать снова тот же счетчик, который в то время был увеличен, возможно, несколько раз. Что вам нужно сделать, так это создать новый объект шифрования с другим счетчиком, инициализированным теми же параметрами. Таким образом, дешифрование работает правильно, начиная счетчик с той же точки, что и шифрование.

Рабочий пример ниже:

# Import modules 
from Crypto.Cipher import AES 
from Crypto import Random 
from Crypto.Util import Counter 


# Pad for short keys 
pad = '# constant pad for short keys ##' 

# Generate a random initialization vector, to be used by both encryptor and decryptor 
# This may be sent in clear in a real communication 

random_generator = Random.new() 
IV = random_generator.read(8) 


# Encryption steps 

# Ask user for input and pad or truncate to a 32 bytes (256 bits) key 
prompt = 'Input your key. It will padded or truncated at 32 bytes (256 bits).\n-: ' 
user_keye = raw_input(prompt) 
keye = (user_keye + pad)[:32] 

# Create counter for encryptor 
ctr_e = Counter.new(64, prefix=IV) 

# Create encryptor, ask for plaintext to encrypt, then encrypt and print ciphertext 
encryptor = AES.new(keye, AES.MODE_CTR, counter=ctr_e) 
plaintext = raw_input('Enter message to cipher: ') 
ciphertext = encryptor.encrypt(plaintext) 
print ciphertext 
print 


# Decryption steps 

# Ask user for key: it must be equal to that used for encryption 
prompt = 'Input your key. It will padded or truncated at 32 bytes (256 bits).\n-: ' 
user_keyd = raw_input(prompt) 
keyd = (user_keyd + pad)[:32] 

# Create counter for decryptor: it is equal to the encryptor, but restarts from the beginning 

ctr_d = Counter.new(64, prefix=IV) 

# Create decryptor, then decrypt and print decoded text 
decryptor = AES.new(keyd, AES.MODE_CTR, counter=ctr_d) 
decoded_text = decryptor.decrypt(ciphertext) 
print decoded_text 
3

AES является block cipher: это алгоритм (точнее, пара алгоритмов), который принимает ключ и блок сообщений и либо шифрует или дешифрует блок. Размер блока всегда равен 16 байтам, независимо от размера ключа.

CTR - mode of operation. Это пара алгоритмов, которые строятся на блочном шифре для создания потокового шифрования, который может шифровать и расшифровывать сообщения произвольной длины.

CTR работает путем объединения последовательных блоков сообщений с шифрованием последовательных значений счетчика. Размер счетчика должен быть одним блоком, поэтому он является допустимым для блочного шифрования.

  • Функционально, не имеет значения, каковы последующие значения счетчика, если на стороне шифрования и дешифрации используется одна и та же последовательность.Обычно счетчик обрабатывается как 256-битное число и увеличивается для каждого последующего блока, причем исходное значение выбирается случайным образом. Таким образом, как правило, метод инкремента искривляется в коде, но стороне дешифрования необходимо знать, что такое начальное значение, поэтому сторона шифрования отправляет или сохраняет начальное значение счетчика в начале зашифрованного сообщения.
  • Для обеспечения безопасности важно, чтобы никогда не повторял одно и то же значение счетчика с заданной клавишей. Так что для одноразового ключа нормально начать с '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'. Но если ключ используется несколько раз, то второму сообщению не разрешается повторно использовать любые значения счетчиков, используемые первым сообщением, и самый простой способ гарантировать, что это будет генерировать начальное значение счетчика в случайном порядке (с 2^128 пространство, вероятность столкновения приемлемо пренебрежимо мала).

Позволяя абоненту выбрать функцию счетчика, библиотека PyCrypto дает вам много веревки, чтобы повесить вас. Вы должны использовать Crypto.Util.Counter, а не только «для лучшей производительности», как указано в документации, а потому, что проще создать что-то безопасное, чем то, что вы, вероятно, придумаете сами. И даже так, позаботьтесь о том, чтобы использовать случайное начальное значение, которое не является значением по умолчанию.

import binascii 
import os 
from Crypto.Cipher import AES 
from Crypto.Util import Counter 
def int_of_string(s): 
    return int(binascii.hexlify(iv), 16) 
def encrypt_message(key, plaintext): 
    iv = os.urandom(16) 
    ctr = Counter.new(128, initial_value=int_of_string(iv)) 
    aes = AES.new(key, AES.MODE_CTR, counter=ctr) 
    return iv + aes.encrypt(plaintext) 
def decrypt_message(key, ciphertext): 
    iv = ciphertext[:16] 
    ctr = Counter.new(128, initial_value=int_of_string(iv)) 
    aes = AES.new(key, AES.MODE_CTR, counter=ctr) 
    return aes.decrypt(ciphertext[16:]) 
Смежные вопросы