使用Python和Javascript的AES CTR模式的奇怪问题

时间:2013-04-16 14:57:24

标签: javascript python cryptography pycrypto cryptojs

我正在尝试使用PyCrypto解密CryptoJS创建的密文。我使用的是AES-256-CTR,带有12字节随机前缀和4字节计数器。到目前为止,我的成功有限。 Please read this previous post我在那里做了第一次尝试。

这适用于Javascript:

  1. 安装CryptoCat扩展
  2. 运行CryptoCat
  3. 启动开发者控制台(Chrome / Firefox中的F12)
  4. 运行这些代码行

  5. key = 'b1df40bc2e4a1d4e31c50574735e1c909aa3c8fda58eca09bf2681ce4d117e11';
    msg = 'LwFUZbKzuarvPR6pmXM2AiYVD2iL0/Ww2gs/9OpcMy+MWasvvzA2UEmRM8dq4loB\ndfPaYOe65JqGQMWoLOTWo1TreBd9vmPUZt72nFs=';
    iv = 'gpG388l8rT02vBH4';
    opts = {mode: CryptoJS.mode.CTR, iv: CryptoJS.enc.Base64.parse(iv), padding: CryptoJS.pad.NoPadding};
    CryptoJS.AES.decrypt(msg, CryptoJS.enc.Hex.parse(key), opts).toString(CryptoJS.enc.Utf8);
    

    预期输出:"Hello, world!ImiAq7aVLlmZDM9RfhDQgPp0CrAyZE0lyzJ6HDq4VoUmIiKUg7i2xpTSPs28USU8"


    这是我在Python中编写的一个部分(!)解密密文的脚本:

    import struct
    import base64
    import Crypto.Cipher.AES
    import Crypto.Util.Counter
    
    def bytestring_to_int(s):
        r = 0
        for b in s:
            r = r * 256 + ord(b)
        return r
    
    class IVCounter(object):
        def __init__(self, prefix="", start_val=0):
            self.prefix = prefix
            self.first = True
            self.current_val = start_val
    
        def __call__(self):
            if self.first:
                self.first = False
            else:
                self.current_val += 1
    
            postfix = struct.pack(">I", self.current_val)
            n = base64.b64decode(self.prefix) + postfix
            return n
    
    def decrypt_msg(key, msg, iv):
        k = base64.b16decode(key.upper())
        ctr = IVCounter(prefix=iv)
        #ctr = Crypto.Util.Counter.new(32, prefix=base64.b64decode(iv), initial_value=0, little_endian=False)
        aes = Crypto.Cipher.AES.new(k, mode=Crypto.Cipher.AES.MODE_CTR, counter=ctr)
        plaintext = aes.decrypt(base64.b64decode(msg))
        return plaintext
    
    if __name__ == "__main__":
            #original:
            key = 'b1df40bc2e4a1d4e31c50574735e1c909aa3c8fda58eca09bf2681ce4d117e11'
            msg = 'LwFUZbKzuarvPR6pmXM2AiYVD2iL0/Ww2gs/9OpcMy+MWasvvzA2UEmRM8dq4loB\ndfPaYOe65JqGQMWoLOTWo1TreBd9vmPUZt72nFs='
            iv = 'gpG388l8rT02vBH4'
    
            decrypted = decrypt_msg(key, msg, iv)
            print "Decrypted message:", repr(decrypt_msg(key, msg, iv))
            print decrypted
    

    输出结果为:

    'Hello, world!Imi\xfb+\xf47\x04\xa0\xb1\xa1\xea\xc0I\x03\xec\xc7\x13d\xcf\xe25>l\xdc\xbd\x9f\xa2\x98\x9f$\x13a\xbb\xcb\x13 \ XD2#\ xc9T \ XF4 | \ XD8 \ XCB aO)\x94\x9aq<\xa7\x7f\x14\x11\xb5\xb0\xb6\xb5GQ\x92'

    问题是,只有输出的前16个字节与预期输出的前16个字节匹配!

    Hello, world!ImiAq7aVLlmZDM9RfhDQgPp0CrAyZE0lyzJ6HDq4VoUmIiKUg7i2xpTSPs28USU8

    当我修改脚本时:

    def __init__(self, prefix="", start_val=1):
    

    self.current_val += 0 #do not increment
    

    使计数器每次调用时输出相同的值(\x00\x00\x00\x01),明文为:

    \xf2?\xaf:=\xc0\xfd\xbb\xdf\xf6h^\x9f\xe8\x16I\xfb+\xf47\x04\xa0\xb1\xa1\xea\xc0I\x03\xec\xc7\x13dQgPp0CrAyZE0lyzJ\xa8\xcd!?h\xc9\xa0\x8b\xb6\x8b\xb3_*\x7f\xf6\xe8\x89\xd5\x83H\xf2\xcd'\xc5V\x15\x80k]
    

    其中第二个16字节块(dQgPp0CrAyZE0lyzJ)与预期输出匹配。

    当我将计数器设置为发出\x00\x00\x00\x02\x00\x00\x00\x03时,我得到了类似的结果 - 随后显示​​了16字节的块。唯一的例外是0s,显示前32个字节。

    All 0s: reveals first 32 bytes.
    'Hello, world!ImiAq7aVLlmZDM9RfhD\xeb=\x93&b\xaf\xaf\x8d\xc9\xdeA\n\xd2\xd8\x01j\x12\x97\xe2i:%}G\x06\x0f\xb7e\x94\xde\x8d\xc83\x8f@\x1e\xa0!\xfa\t\xe6\x91\x84Q\xe3'
    All 1s: reveals next 16 bytes.
    "\xf2?\xaf:=\xc0\xfd\xbb\xdf\xf6h^\x9f\xe8\x16I\xfb+\xf47\x04\xa0\xb1\xa1\xea\xc0I\x03\xec\xc7\x13dQgPp0CrAyZE0lyzJ\xa8\xcd!?h\xc9\xa0\x8b\xb6\x8b\xb3_*\x7f\xf6\xe8\x89\xd5\x83H\xf2\xcd'\xc5V\x15\x80k]"
    All 2s: reveals next 16 bytes.
    'l\xba\xcata_2e\x044\xb2J\xe0\xf0\xd7\xc8e\xae\x91yX?~\x7f1\x02\x93\x17\x93\xdf\xd2\xe5\xcf\xe25>l\xdc\xbd\x9f\xa2\x98\x9f$\x13a\xbb\xcb6HDq4VoUmIiKUg7i\x17P\xe6\x06\xaeR\xe8\x1b\x8d\xd7Z\x7f"'
    All 3s: reveals next 13 bytes.
    'I\x92\\&\x9c]\xa9L\xb1\xb6\xbb`\xfa\xbet;@\x86\x07+\xa5=\xe5V\x84\x80\x9a=\x89\x91q\x16\xea\xca\xa3l\x91\xde&\xb6\x17\x1a\x96\x0e\t/\x188\x13`\xd2#\xc9T\xf4|\xd8\xcb`aO)\x94\x9a2xpTSPs28USU8'
    

    如果你连接“正确”的块,你将获得预期的明文:

    Hello, world!ImiAq7aVLlmZDM9RfhDQgPp0CrAyZE0lyzJ6HDq4VoUmIiKUg7i2xpTSPs28USU8
    

    这真的很奇怪。我肯定在Python端做错了,因为事情可以被解密,但不是一次性完成。如果有人可以提供帮助,我将非常感激。谢谢。

3 个答案:

答案 0 :(得分:2)

这里有几个问题。首先,消息不是块大小的倍数,并且您没有使用填充。第二个 - 也是这个问题最关键的 - 是IV也不是正确的大小。它应该是16个字节,但你只有12个。可能两个实现都会失败并出现异常,而在CryptoJS的下一个主要版本中,情况就是这样。

这是由于这个错误而发生的事情:当计数器第一次递增时,它会尝试递增未定义的值,因为缺少IV的最后一个字节。未定义+ 1是NaN和NaN | 0是0.这就是你最终得到0两次的方式。

答案 1 :(得分:1)

当使用加密模式CryptoJS.mode.CTR(其中CTR代表计数器)时,Initailization向量与计数器一起被加密,然后应用于要加密的数据。这是为您加密的每个数据块完成的。

您解释说,当您对start_val应用不同的值时,消息的不同部分会被正确解密,因此我怀疑计数器在每次解密块时都没有正确增加。

查看Block Cipher Mode: CTR at wikipedia

注意:请注意,使用CTR模式时,不应重复初始化矢量+计数器的组合。

答案 2 :(得分:0)

固定。我只是让计数器以0开始两次。有谁知道这是否是一个漏洞?

import struct
import base64
import Crypto.Cipher.AES
import Crypto.Util.Counter
import pdb

def bytestring_to_int(s):
    r = 0
    for b in s:
        r = r * 256 + ord(b)
    return r

class IVCounter(object):
    def __init__(self, prefix="", start_val=0):
        self.prefix = prefix
        self.zeroth = True
        self.first = False
        self.current_val = start_val

    def __call__(self):
        if self.zeroth:
            self.zeroth = False
            self.first = True
        elif self.first:
            self.first = False
        else:
            self.current_val += 1

        postfix = struct.pack(">I", self.current_val)
        n = base64.b64decode(self.prefix) + postfix
        return n

def decrypt_msg(key, msg, iv):
    k = base64.b16decode(key.upper())
    ctr = IVCounter(prefix=iv)
    #ctr = Crypto.Util.Counter.new(32, prefix=base64.b64decode(iv), initial_value=0, little_endian=False)
    aes = Crypto.Cipher.AES.new(k, mode=Crypto.Cipher.AES.MODE_CTR, counter=ctr)
    plaintext = aes.decrypt(base64.b64decode(msg))
    return plaintext

if __name__ == "__main__":
        #original:
        key = 'b1df40bc2e4a1d4e31c50574735e1c909aa3c8fda58eca09bf2681ce4d117e11'
        msg = 'LwFUZbKzuarvPR6pmXM2AiYVD2iL0/Ww2gs/9OpcMy+MWasvvzA2UEmRM8dq4loB\ndfPaYOe65JqGQMWoLOTWo1TreBd9vmPUZt72nFs='
        iv = 'gpG388l8rT02vBH4'

        decrypted = decrypt_msg(key, msg, iv)
        print "Decrypted message:", repr(decrypt_msg(key, msg, iv))
        print decrypted