使用AES + CTR的PyCrypto问题

时间:2010-07-01 03:55:54

标签: python cryptography aes encryption-symmetric pycrypto

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

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字节的倍数?这是正常的吗?

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

5 个答案:

答案 0 :(得分: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

点击率是一个密码,因此看起来让您感到惊讶的“一次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)

答案 1 :(得分:4)

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

点击率是mode of operation。它是一对基于分组密码构建的算法,用于生成流密码,可以加密和解密任意长度的消息。

CTR通过将连续的消息块与计数器的连续值的加密相结合来工作。计数器的大小需要是一个块,因此它是块密码的有效输入。

  • 从功能上讲,只要加密和解密端使用相同的序列,计数器的连续值是什么并不重要。通常,计数器被视为256位数,并且对于每个连续的块递增,并且随机选择初始值。因此,通常,增量方法被烘焙到代码中,但解密方需要知道初始值是什么,因此加密方在加密消息的开头发送或存储初始计数器值。
  • 为了安全起见,永远不要用给定的密钥重复相同的计数器值。因此,对于一次性密钥,可以从compilation: cloud_properties: instance_type: m3.medium availability_zone: us-east-1d network: consul1 reuse_compilation_vms: true workers: 6 director_uuid: $YOUR_ID jobs: - instances: 3 name: consul networks: - name: consul1 persistent_disk: 4096 properties: consul: join_host: 0.consul-z1.consul1.consul-aws.microbosh services: example: {} networks: apps: consul1 resource_pool: small_z1 templates: - consumes: consul_servers: from: consul_leaders name: consul provides: consul_servers: as: consul_leaders release: consul update: canaries: 0 max_in_flight: 50 name: consul-aws networks: - cloud_properties: {} name: floating type: vip - cloud_properties: subnet: $YOUR_SUBNET security_groups: - default availability_zone: us-east-1d name: consul1 type: dynamic properties: {} releases: - name: consul version: latest resource_pools: - cloud_properties: instance_type: m3.medium availability_zone: us-east-1d name: small_z1 network: consul1 stemcell: name: bosh-aws-xen-hvm-ubuntu-trusty-go_agent version: latest update: canaries: 0 canary_watch_time: 1000-60000 max_in_flight: 50 serial: true update_watch_time: 1000-60000 开始。但是如果密钥被多次使用,则第二条消息不允许重用第一条消息使用的任何计数器值,并且最简单的方法是确保随机生成初始计数器值(使用2 ^ 128)空间,碰撞的可能性可以忽略不计。)

通过让呼叫者选择一个计数器功能,PyCrypto库可以为您提供足够的绳索。你应该使用'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',而不仅仅是“为了更好的性能”,正如文档所说,但是因为它比你自己想象的更安全。即便如此,请注意使用随机初始值,这不是默认值。

Crypto.Util.Counter

答案 2 :(得分:2)

  

为什么当我的密钥是32字节

时需要16字节

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

备注:

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

      

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

    使用scrypt。 scrypt包含encryptdecrypt,其中使用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.'
    

答案 3 :(得分:1)

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

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

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

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

答案 4 :(得分: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