12

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

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

我猜它不会回到原始消息,因为计数器在加密和解密之间发生了变化。但是,无论如何,它在理论上应该如何工作?我究竟做错了什么?无论如何,我不得不求助于欧洲央行,直到我弄清楚:(

4

5 回答 5

13

正如您所直觉的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)
于 2010-07-01T04:55:32.807 回答
9

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

点击率是一种操作模式。它是一种基于分组密码生成流密码的算法,可以加密和解密任意长度的消息。

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

  • 从功能上讲,计数器的连续值是多少无关紧要,只要加密和解密端使用相同的序列即可。通常计数器被视为一个 256 位的数字,并为每个连续的块递增,并随机选择一个初始值。因此,通常情况下,递增方法被嵌入到代码中,但解密方需要知道初始值是什么,因此加密方在加密消息的开头发送或存储初始计数器值。
  • 为了安全起见,永远不要使用给定的 key 重复相同的计数器值是至关重要的。因此,对于一次性密钥,可以从'\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:])
于 2017-08-11T19:08:07.570 回答
2

我可能肯定迟到了,我可能忽略了以前的答案,但我没有找到一个明确的说明,说明应该如何(至少恕我直言)根据 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
于 2013-10-31T15:59:07.353 回答
2

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

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

笔记:

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

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

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

使用 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.'
于 2013-10-07T17:54:23.847 回答
1

The initialization vector ("counter") needs to stay the same, just as the key does, between encryption and decryption. It is used so that you can encode the same text a million times, and get different ciphertext each time (preventing some known plaintext attacks and pattern matching / attacks). You still need to use the same IV when decrypting as when encrypting. Usually when you start decrypting a stream, you initialize the IV to the same value that you started with when you started encrypting that stream.

See http://en.wikipedia.org/wiki/Initialization_vector for info on initialization vectors.

Note that os.urandom(16) is not 'deterministic', which is a requirement for counter functions. I suggest you use the increment function, as that is how CTR mode is designed. The initial counter value should be random, but the successive values should be fully predictable from the initial value (deterministic). The initial value may even be taken care of for you (I don't know the details)

About the key, IV, and input sizes, it sounds like the cipher you chose has a block size of 16 bytes. Everything you describe fits that and seems normal to me.

于 2010-07-01T04:45:00.363 回答