Проблема PyCrypto с использованием AES + CTR - PullRequest
10 голосов
/ 01 июля 2010

Я пишу кусок кода для шифрования текста с использованием симметричного шифрования.Но результат не вернется ...

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 байтам?Это нормально?

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

Ответы [ 5 ]

12 голосов
/ 01 июля 2010

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 - это блочный шифр, поэтому ограничение "16 за один раз", которое вас удивляет, вполне естественное.

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

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)
4 голосов
/ 11 августа 2017

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

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

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(s), 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:])
2 голосов
/ 31 октября 2013

Я могу определенно опоздать и, возможно, пропустил предыдущие ответы, но я не нашел четкого заявления о том, как это (по крайней мере, ИМХО) сделать в соответствии с пакетами 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
2 голосов
/ 07 октября 2013

почему он должен быть 16 байтов, когда мой ключ 32 байта

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

Примечания:

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

Стандартный отказ от ответственности: Крипто сложно.Если вы не понимаете, что делаете, вы ошибетесь .

Я просто хочу сохранить несколько паролей между сеансами.

Использовать 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.'
1 голос
/ 01 июля 2010

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...