使用AES + CTR的PyCrypto问题

问题描述 投票:10回答:5

我正在编写一段代码来使用对称加密来加密文本。但它没有以正确的结果回归......

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模式需要一个“计数器”功能来每次提供一个随机计数器,但为什么当我的密钥是32字节时它需要它是16个字节并且它坚持我的消息也是16字节的倍数?这是正常的吗?

我猜它没有回到原始消息,因为计数器在加密和解密之间发生了变化。但那么,它究竟应该在理论上如何运作呢?我究竟做错了什么?无论如何,我被迫回到欧洲央行,直到我弄明白:(

python cryptography aes encryption-symmetric pycrypto
5个回答
12
投票

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-at-a-time”约束非常自然。

当然,所谓的“计数器”在每次调用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)

4
投票

AES是一个block cipher:它是一个算法(更确切地说,一对算法),它接受一个密钥和一个消息块,并加密或解密该块。无论密钥大小如何,块的大小始终为16个字节。

点击率是一个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(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
投票

当我的密钥是32字节时,为什么它需要16字节

它必须与密码的块大小相同。 CTR模式只是对计数器进行加密,并使用加密的计数器块对明文进行异或。

笔记:

  1. 计数器值必须是唯一的 - 如果您使用相同的计数器值来加密同一个键下的两个不同的明文,您只需放弃您的密钥。
  2. 像IV一样,计数器并不是秘密 - 只需将其与密文一起发送即可。如果你通过试图保守秘密使代码变得更加复杂,你可能会在自己的脚下射击。
  3. 计数器值不需要是不可预测的 - 从零开始并为每个块添加一个是完全正常的。但请注意,如果您加密多条消息,则需要跟踪已经消耗的计数器值,即您需要跟踪已使用该密钥加密的块数(并且您不能使用该密钥)键入您的程序的不同实例或在不同的机器上)。
  4. 纯文本可以是任何长度 - CTR模式将块密码转换为流密码。

标准免责声明:加密很难。如果你不明白你在做什么,你就会弄错。

我只想在会话中存储一些密码。

使用scrypt。 scrypt包括使用AES-CTR和密码派生密钥的encryptdecrypt

$ 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
投票

初始化向量(“计数器”)需要与加密和解密之间的密钥保持一致。它被用于使您可以对相同的文本进行一百万次编码,并且每次都获得不同的密文(防止一些已知的明文攻击和模式匹配/攻击)。解密时仍需要使用与加密时相同的IV。通常,当您开始解密流时,将IV初始化为与开始加密该流时开始时相同的值。

有关初始化向量的信息,请参阅http://en.wikipedia.org/wiki/Initialization_vector

注意,os.urandom(16)不是'确定性',这是计数器函数的要求。我建议你使用增量函数,因为这就是CTR模式的设计方式。初始计数器值应该是随机的,但连续值应该从初始值(确定性)完全可预测。甚至可以为您处理初始值(我不知道细节)

关于键,IV和输入大小,听起来您选择的密码具有16字节的块大小。你描述的一切都适合我,对我来说似乎很正常。


1
投票

我可能肯定迟到了,我可能忽略了之前的答案,但我没有找到一个明确的陈述,应该如何(至少恕我直言)根据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
© www.soinside.com 2019 - 2024. All rights reserved.