CTR中的AES如何适用于PyCrypto的Python?

时间:2012-10-02 13:24:54

标签: python encryption cryptography aes pycrypto

我正在使用python 2.7.1 我想在CTR模式下使用AES加密某事。我为python安装了PyCrypto库。我写了以下代码:

secret = os.urandom(16)
crypto = AES.new(os.urandom(32), AES.MODE_CTR, counter=lambda: secret)
encrypted = crypto.encrypt("asdk")
print crypto.decrypt(encrypted)

为了正确获取解密数据,我必须运行crypto.decrypt与明文的字节大小一样多次。我:

encrypted = crypto.encrypt("test")
print crypto.decrypt(encrypted)
print crypto.decrypt(encrypted)
print crypto.decrypt(encrypted)
print crypto.decrypt(encrypted)

最后一次解密会给我回复明文。解密的其他输出是一些乱码。 我想知道这是否正常?我是否必须每次都包含一个大小等于我的明文的循环或者我错了?

4 个答案:

答案 0 :(得分:3)

我将详细阐述@ gertvdijk解释为什么密码在原始问题中的行为方式(我的编辑被拒绝),但也指出设置计数器以返回静态值是一个主要问题缺陷并展示如何正确设置。

重置新操作的计数器

这就像你在问题中描述的那样行为的原因是你的纯文本(4字节/ 32位)是CTR密码为加密输出的密钥流块大小的四倍(16字节/ 128位)。

因为你反复使用相同的固定值而不是实际的计数器,所以密码不断吐出相同的16字节密钥流块。您可以通过重复加密16个空字节来观察这一点:

 >>> crypto.encrypt('\x00'*16)
'?\\-\xdc\x16`\x05p\x0f\xa7\xca\x82\xdbE\x7f/'
>>> crypto.encrypt('\x00'*16)
'?\\-\xdc\x16`\x05p\x0f\xa7\xca\x82\xdbE\x7f/'

在执行解密之前,您也不会重置密码的状态,因此4个字节的密文将针对来自第一个输出流块的下一个4字节的XOR密钥进行解密。通过加密和解密空字节也可以观察到这一点:

 >>> crypto.encrypt('\x00' * 4)
'?\\-\xdc'
>>> crypto.decrypt('\x00' * 4)
'\x16`\x05p'

如果按照您想要的方式工作,那么这两个操作的结果应该是相同的。相反,您可以在第一个结果中看到16字节块的前四个字节,在第二个结果中看到第二个四个字节。

通过对四字节值执行四次操作(总共16字节)用完XOR键的16字节块后,会生成一个新的XOR键块。每个XOR密钥块的前四个字节(以及所有其他字节)是相同的,所以当你这次调用解密时,它会返回明文。

这真的很糟糕!你不应该以这种方式使用AES-CTR - 它相当于使用16字节重复键的简单XOR加密,可以很容易地破解。

解决方案

在对新的数据流(或其上的其他操作)执行操作之前,必须重置密码的状态,因为原始实例将不再处于正确的初始状态。您的问题将通过为解密实例化一个新的crypto对象,以及重置计数器和密钥流位置来解决。

您还需要使用适当的计数器功能,它将随机数与计数器值组合在一起,每次生成新的密钥流块时计数器值都会增加。 PyCrypto有一个Counter类可以为你做这个。

from Crypto.Cipher import AES
from Crypto.Util import Counter
from Crypto import Random

# Set up the counter with a nonce.
# 64 bit nonce + 64 bit counter = 128 bit output
nonce = Random.get_random_bytes(8)
countf = Counter.new(64, nonce) 

key = Random.get_random_bytes(32)  # 256 bits key

# Instantiate a crypto object first for encryption
encrypto = AES.new(key, AES.MODE_CTR, counter=countf)
encrypted = encrypto.encrypt("asdk")

# Reset counter and instantiate a new crypto object for decryption
countf = Counter.new(64, nonce)
decrypto = AES.new(key, AES.MODE_CTR, counter=countf)
print decrypto.decrypt(encrypted) # prints "asdk"

答案 1 :(得分:2)

从新的加密对象开始,用于新操作

这就像你在问题中描述的那样行为的原因是你的纯文本(4字节/ 32位)是加密引擎为你选择的AES模式(128位)工作的大小的四倍,也是重用crypto对象的相同实例。如果您正在对新的数据流(或其上的其他操作)执行操作,则不要重复使用相同的对象。您的问题将通过实例化解密的新crypto对象来解决,如下所示:

# *NEVER* USE A FIXED LIKE COUNTER BELOW IN PRODUCTION CODE. READ THE DOCS.
counter = os.urandom(16)
key = os.urandom(32)  # 256 bits key

# Instantiate a crypto object first for encryption
encrypto = AES.new(key, AES.MODE_CTR, counter=lambda: counter)
encrypted = encrypto.encrypt("asdk")

# Instantiate a new crypto object for decryption
decrypto = AES.new(key, AES.MODE_CTR, counter=lambda: counter)
print decrypto.decrypt(encrypted) # prints "asdk"

为什么不用AES-CTR填充

这个答案最初是作为对the answer by Marcus的回复,他最初表示使用填充可以解决它。虽然我理解它看起来像填充问题的症状,但肯定不是。

AES-CTR的重点在于不需要填充,因为它是流密码(与ECB / CBC等不同)!流密码处理数据流,而不是块中的数据块,并在实际的加密计算中链接它们。

答案 2 :(得分:1)

除了Marcus所说的,Crypto.Util.Counter类可用于构建计数器块功能。

答案 3 :(得分:0)

根据@gertvdijk,AES_CTR是一个流密码,需要填充。所以我删除了相关的代码。

这是我所知道的。

  1. 您必须在加密和解密中使用相同的密钥(AES.new(...)中的第一个参数),并将密钥保密。

  2. 加密/解密方法有状态,这意味着crypto.en(de)crypt("abcd")==crypto.en(de)crypt("abcd") 始终为true。在您的CTR中,您的计数器回调始终返回相同的内容,因此在加密时它变为无状态(我不是100%确定它是原因),但我们仍然发现它在解密时有些有状态。作为结论,我们应该总是使用一个新对象来完成它们。

  3. 加密和解密中的counter callback函数应该表现相同。在你的情况下,它是让他们两个都返回相同的秘密。但我不认为secret是一个“秘密”。您可以使用随机生成的"secret"并通过通信对等方传递它而不进行任何加密,以便对方可以直接使用它,只要secret 不可预测

  4. 所以我会像这样编写我的密码,希望它会提供一些帮助。

    import os
    import hashlib
    import Crypto.Cipher.AES as AES
    
    class Cipher:
    
            @staticmethod
            def md5sum( raw ):
                    m = hashlib.md5()
                    m.update(raw)
                    return m.hexdigest()
    
            BS = AES.block_size
    
            @staticmethod 
            def pad( s ):
                    """note that the padding is no necessary"""
                    """return s + (Cipher.BS - len(s) % Cipher.BS) * chr(Cipher.BS - len(s) % Cipher.BS)"""
                    return s
    
            @staticmethod
            def unpad( s ):
                    """return s[0:-ord(s[-1])]"""
                    return s
    
            def __init__(self, key):
                    self.key = Cipher.md5sum(key)
                    #the state of the counter callback 
                    self.cnter_cb_called = 0 
                    self.secret = None
    
            def _reset_counter_callback_state( self, secret ):
                    self.cnter_cb_called = 0
                    self.secret = secret
    
            def _counter_callback( self ):
                    """
                    this function should be stateful
                    """
                    self.cnter_cb_called += 1
                    return self.secret[self.cnter_cb_called % Cipher.BS] * Cipher.BS
    
    
            def encrypt(self, raw):
                    secret = os.urandom( Cipher.BS ) #random choose a "secret" which is not secret
                    self._reset_counter_callback_state( secret )
                    cipher = AES.new( self.key, AES.MODE_CTR, counter = self._counter_callback )
                    raw_padded = Cipher.pad( raw )
                    enc_padded = cipher.encrypt( raw_padded )
                    return secret+enc_padded #yes, it is not secret
    
            def decrypt(self, enc):
                    secret = enc[:Cipher.BS]
                    self._reset_counter_callback_state( secret )
                    cipher = AES.new( self.key, AES.MODE_CTR, counter = self._counter_callback )
                    enc_padded = enc[Cipher.BS:] #we didn't encrypt the secret, so don't decrypt it
                    raw_padded = cipher.decrypt( enc_padded )
                    return Cipher.unpad( raw_padded )
    

    一些测试:

    >>> from Cipher import Cipher
    >>> x = Cipher("this is key")
    >>> "a"==x.decrypt(x.encrypt("a"))
    True
    >>> "b"==x.decrypt(x.encrypt("b"))
    True
    >>> "c"==x.decrypt(x.encrypt("c"))
    True
    >>> x.encrypt("a")==x.encrypt("a")
    False #though the input is same, the outputs are different
    

    参考:http://packages.python.org/pycrypto/Crypto.Cipher.blockalgo-module.html#MODE_CTR