使用 AES+CTR 的 PyCrypto 问题

发布于 2024-09-08 06:27:58 字数 511 浏览 3 评论 0原文

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

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

我猜测它不会返回原始消息,因为计数器在加密和解密之间发生了变化。但是,理论上它应该如何运作呢?我做错了什么?不管怎样,我不得不求助于欧洲央行,直到我弄清楚这个问题:(

I'm writing a piece of code to encrypt a text using symmetric encryption. But it's not coming back with the right result...

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)

Here, the decrypted text is different from the original.

I don't really understand much about cryptography so please bear with me. I understand the CTR mode requires a "counter" function to supply a random counter each time, but why does it need it to be 16 bytes when my key is 32 bytes and it insists that my message is in multiples of 16 bytes too? Is this normal?

I'm guessing that it doesn't get back to the original message because the counter changed between encrypt and decrypt. But then, how is it supposed to work theoretically anyway? What am I doing wrong? Anyway, I'm forced to resort back to ECB until I figure this out :(

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

转角预定愛 2024-09-15 06:27:58

正如您所直觉的那样,计数器在解密时返回的结果必须与在加密时返回的结果相同,因此,一种(完全不安全)方法是:

>>> 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)

The counter must return the same on decryption as it did on encryption, as you intuit, so, one (NOT SECURE AT ALL) way to do it is:

>>> 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 is a block cipher, so the "16-at-a-time" constraint that seems to surprise you is a pretty natural one.

Of course, a so-called "counter" returning the same value at each call is grossly insecure. Doesn't take much to do better, e.g....:

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)
陌生 2024-09-15 06:27:58

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:])

AES is a block cipher: it's an algorithm (more precisely, a pair of algorithms) that takes a key and a message block and either encrypts or decrypts the block. The size of a block is always 16 bytes, regardless of the key size.

CTR is a mode of operation. It's a pair of algorithms that builds on a block cipher to produce a stream cipher, which can encrypt and decrypt messages of arbitrary lengths.

CTR works by combining successive message blocks with the encryption of successive values of a counter. The size of the counter needs to be one block so that it's valid input for the block cipher.

  • Functionally, it doesn't matter what the successive values of the counter are, as long as the encryption and decryption side use the same sequence. Usually the counter is treated as a 256-bit number and incremented for each successive block, with an initial value chosen at random. Thus, usually, the incrementation method is baked into the code, but the decryption side needs to know what the initial value is, so encryption side sends or stores the initial counter value at the beginning of the encrypted message.
  • For security, it is vital to never repeat the same counter value with a given key. So for a single-use key, it's ok to start with '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'. But if the key is used multiple times then the second message is not allowed to reuse any of the counter values used by the first message, and the easiest way to ensure that is to generate the initial counter value at random (with a 2^128 space, the chances of a collision are acceptably negligible).

By letting the caller pick a counter function, the PyCrypto library gives you plenty of rope to hang yourself. You should use Crypto.Util.Counter, not just “for better performance” as the documentation puts it, but because it's easier to build something secure than what you're likely to come up with on your own. And even so, take care to use a random initial value, which is not the default.

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:])
坏尐絯 2024-09-15 06:27:58

我可能肯定迟到了,而且我可能忽略了前面的答案,但我没有找到明确的说明,说明应该如何(至少恕我直言)根据 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

I may be definitely late and I may have overlooked the previous answers, but I didn't find a clear statement of how this should (at least IMHO) be done according with the PyCrypto packages.

The Crypto.Util.Counter package provides callable stateful counters, which are very useful, but it was easy at least for me to use them improperly.

You have to create a counter, with e.g. ctr = Counter.new('parameters here'). Every time your counter is called by your counter mode cipher object to encrypt the message, it is incremented. This is needed for good cryptography practices, otherwise information about equal blocks may leak from the ciphertext.

Now you cannot call the decryption function on the same cipher object, because it would call again the same counter which in the meanwhile has been incremented, possibly several times. What you need to do is to create a new cipher object with a different counter initialized with the same parameters. In this way the decryption works properly, starting the counter from the same point as the encryption was done.

Working example below:

# 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
红颜悴 2024-09-15 06:27:58

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

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

注意:

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

标准免责声明:加密货币很难。如果您不明白自己在做什么,您就会出错。

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

使用 scrypt。 scrypt 包括使用 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.'

why does it need it to be 16 bytes when my key is 32 bytes

It has to be the same length as the cipher's block size. CTR mode just encrypts the counter and XORs the plaintext with the encrypted counter block.

Notes:

  1. the counter value MUST be unique -- if you EVER use the same counter value to encrypt two different plaintexts under the same key, you just gave away your key.
  2. like an IV, the counter is NOT secret -- just send it along with the ciphertext. If you make the code more complicated by trying to keep it secret, you will probably shoot yourself in the foot.
  3. the counter value need not be unpredictable -- starting with zero and adding one for each block is perfectly fine. But note that if you encrypt multiple messages, you need to keep track of the counter values that have already been consumed, i.e. you need to keep track of how many blocks have already been encrypted with that key (and you can't use the same key in different instances of your program or on different machines).
  4. the plain text can be any length -- CTR mode turns a block cipher into a stream cipher.

Standard disclaimer: Crypto is hard. If you don't understand what you are doing, you will get it wrong.

I just want to store some passwords across sessions.

Use scrypt. scrypt includes encrypt and decrypt which use AES-CTR with a password-derived key.

$ 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.'
猥︴琐丶欲为 2024-09-15 06:27:58

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

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

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

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文