Вопрос по encryption, pycrypto, aes, cryptography, python – Как AES в CTR работает для Python с PyCrypto?

3

Я использую Python 2.7.1 Я хочу зашифровать с помощью AES в режиме CTR. Я установил библиотеку PyCrypto для Python. Я написал следующий код:

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

Я должен запустить crypto.decrypt столько раз, сколько байт моего открытого текста, чтобы правильно получить расшифрованные данные. то есть:

encrypted = crypto.encrypt("test")
print crypto.decrypt(encrypted)
print crypto.decrypt(encrypted)
print crypto.decrypt(encrypted)
print crypto.decrypt(encrypted)

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

Ваш Ответ

4   ответа
0

Согласно @gertvdijk, AES_CTR является потоковым шифром, который делаетnot нужна прокладка. Поэтому я удалил соответствующие коды.

Вот что я знаю.

You have to use a same key(the first parameter in AES.new(...)) in encryption and decryption, and keep the key private.

The encryption/decryption methods are stateful, that means crypto.en(de)crypt("abcd")==crypto.en(de)crypt("abcd") is not always true. In your CTR, your counter callback always returns a same thing, so it becomes stateless when encrypt (I am not 100% sure it is the reason), but we still find that it is somewhat stateful in decryption. As a conclusion, we should always use a new object to do them.

The counter callback function in both encryption and decryption should behave the same. In your case, it is to make both of them return the same secret. Yet I don't think the secret is a "secret". You can use a random generated "secret" and pass it across the communicating peers without any encryption so that the other side can directly use it, as long as the secret is not predictable.

Поэтому я напишу свой шифр вот так, надеюсь, он поможет.

import os
import hashlib
import Crypto.Cipher.AES as AES

class Cipher:

        @staticmethod
        def md5sum( raw ):
                m = hashlib.md5()
                m.update(raw)
                return m.hexdigest()

        BS = AES.block_size

        @staticmethod 
        def pad( s ):
                """note that the padding is no necessary"""
                """return s + (Cipher.BS - len(s) % Cipher.BS) * chr(Cipher.BS - len(s) % Cipher.BS)"""
                return s

        @staticmethod
        def unpad( s ):
                """return s[0:-ord(s[-1])]"""
                return s

        def __init__(self, key):
                self.key = Cipher.md5sum(key)
                #the state of the counter callback 
                self.cnter_cb_called = 0 
                self.secret = None

        def _reset_counter_callback_state( self, secret ):
                self.cnter_cb_called = 0
                self.secret = secret

        def _counter_callback( self ):
                """
                this function should be stateful
                """
                self.cnter_cb_called += 1
                return self.secret[self.cnter_cb_called % Cipher.BS] * Cipher.BS


        def encrypt(self, raw):
                secret = os.urandom( Cipher.BS ) #random choose a "secret" which is not secret
                self._reset_counter_callback_state( secret )
                cipher = AES.new( self.key, AES.MODE_CTR, counter = self._counter_callback )
                raw_padded = Cipher.pad( raw )
                enc_padded = cipher.encrypt( raw_padded )
                return secret+enc_padded #yes, it is not secret

        def decrypt(self, enc):
                secret = enc[:Cipher.BS]
                self._reset_counter_callback_state( secret )
                cipher = AES.new( self.key, AES.MODE_CTR, counter = self._counter_callback )
                enc_padded = enc[Cipher.BS:] #we didn't encrypt the secret, so don't decrypt it
                raw_padded = cipher.decrypt( enc_padded )
                return Cipher.unpad( raw_padded )

Некоторый тест:

>>> from Cipher import Cipher
>>> x = Cipher("this is key")
>>> "a"==x.decrypt(x.encrypt("a"))
True
>>> "b"==x.decrypt(x.encrypt("b"))
True
>>> "c"==x.decrypt(x.encrypt("c"))
True
>>> x.encrypt("a")==x.encrypt("a")
False #though the input is same, the outputs are different

Ссылка:http://packages.python.org/pycrypto/Crypto.Cipher.blockalgo-module.html#MODE_CTR

@ почему, нет? ИМХО, все в порядке.
@curious, до сих пор я думаю, что ключевым моментом атаки является pad-oracle, а не функции padding, поэтому два возможных решения могут быть следующими: 1. заставить наш оракул pad никогда не говорить «нет» любому входу, или 2. добавить аутентификацию информация в зашифрованных данных, т.е.enc=secret+md5sum(secret+private_key)+enc_raw, чтобы всегда говорить «нет» неверным данным.
@curious, нам нужно знать добавленную длину, когда делаем unpad, и мы храним ее, используя символ (он будет не больше 16, поэтому достаточно байта, и мы используем его код ascii).(BS - len(s) % BS) это добавленная длина, иchr(...) возвращает байт (фактически строку в python), затем мы добавляем строку длиной X, где все символы в ней - chr (X).
Я не могу понять вашу функцию заполнения ... Зачем нужно умножение с помощью chr () предопределенного выражения? curious
но это безопасный метод заполнения? curious
2

Я собираюсь уточнить объяснение @ gertvdijk о том, почему шифр вел себя так же, как и в исходном вопросе (мое редактирование было отклонено), но я также укажу, что настройка счетчика для возврата статического значения является серьезным недостатком. и покажи, как правильно его настроить.

Reset the counter for new operations

Причина, по которой вы ведете себя так, как вы описали в вопросе, заключается в том, что ваш обычный текст (4 байта / 32 бита) в четыре раза меньше размера блоков потока ключей, которые шифрователь CTR выводит для шифрования (16 байт / 128 бит) ,

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

 >>> crypto.encrypt('\x00'*16)
'?\\-\xdc\x16`\x05p\x0f\xa7\xca\x82\xdbE\x7f/'
>>> crypto.encrypt('\x00'*16)
'?\\-\xdc\x16`\x05p\x0f\xa7\xca\x82\xdbE\x7f/'

Вы также не сбрасываете состояние шифра перед выполнением дешифрования, поэтому 4 байта зашифрованного текста дешифруются против следующих 4 байтов ключа XOR из первого блока выходного потока. Это также можно наблюдать путем шифрования и дешифрования нулевых байтов:

 >>> crypto.encrypt('\x00' * 4)
'?\\-\xdc'
>>> crypto.decrypt('\x00' * 4)
'\x16`\x05p'

Если бы это работало так, как вы хотели, результат обеих этих операций должен быть одинаковым. Вместо этого вы можете увидеть первые четыре байта 16-байтового блока в первом результате и вторые четыре байта во втором результате.

После того, как вы израсходовали 16-байтовый блок ключа XOR, выполнив четыре операции с четырехбайтовыми значениями (всего 16 байт), генерируется новый блок ключа XOR. Первые четыре байта (как и все остальные) каждого блока ключей XOR одинаковы, поэтому, когда вы вызываете метод расшифровки в этот раз, он возвращает вам открытый текст.

This is really bad! Вы не должны использовать AES-CTR таким образом - это эквивалентно простому шифрованию XOR с 16-байтовым повторяющимся ключом, который может быть довольно легко взломан.

Solution

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

You also need to use a proper counter function это объединяет одноразовый номер со значением счетчика, которое увеличивается каждый раз, когда генерируется новый блок ключевого потока. PyCrypto имеет класс Counter, который может сделать это за вас.

from Crypto.Cipher import AES
from Crypto.Util import Counter
from Crypto import Random

# Set up the counter with a nonce.
# 64 bit nonce + 64 bit counter = 128 bit output
nonce = Random.get_random_bytes(8)
countf = Counter.new(64, nonce) 

key = Random.get_random_bytes(32)  # 256 bits key

# Instantiate a crypto object first for encryption
encrypto = AES.new(key, AES.MODE_CTR, counter=countf)
encrypted = encrypto.encrypt("asdk")

# Reset counter and instantiate a new crypto object for decryption
countf = Counter.new(64, nonce)
decrypto = AES.new(key, AES.MODE_CTR, counter=countf)
print decrypto.decrypt(encrypted) # prints "asdk"
2
Start with a new crypto object for new operations

Причина, по которой вы ведете себя так, как вы описали в вопросе, заключается в том, что ваш обычный текст (4 байта / 32 бита) в четыре раза меньше размера, на котором работает криптографический движок для выбранного вами режима AES (128 бит), а также многократного использования того же самого экземпляр криптообъекта. Просто не используйте один и тот же объект повторно, если вы выполняете операцию с новым потоком данных (или другую операцию с ним). Ваша проблема будет решена путем создания новойcrypto объект для расшифровки, как это:

# *NEVER* USE A FIXED LIKE COUNTER BELOW IN PRODUCTION CODE. READ THE DOCS.
counter = os.urandom(16)
key = os.urandom(32)  # 256 bits key

# Instantiate a crypto object first for encryption
encrypto = AES.new(key, AES.MODE_CTR, counter=lambda: counter)
encrypted = encrypto.encrypt("asdk")

# Instantiate a new crypto object for decryption
decrypto = AES.new(key, AES.MODE_CTR, counter=lambda: counter)
print decrypto.decrypt(encrypted) # prints "asdk"
Why it is not about padding with AES-CTR

Этот ответ начался как ответ наответ Маркуса, в котором он первоначально указал, что использование отступа решит это. Хотя я понимаю, что это похоже на симптомы проблемы с заполнением, это, конечно, не так.

Весь смысл AES-CTR в том, что выdo not need paddingкак этоstream cipher (в отличие от ECB / CBC и так далее)! Потоковые шифры работают с потоками данных, а не группируют данные в блоках и не связывают их в фактические криптографические вычисления.

1

В дополнение к тому, что говорит Маркус,Crypto.Util.Counter Класс может быть использован для построения вашей функции счетчика блоков.

Похожие вопросы