奇怪的问题与AES CTR模式与Python和Javascript [英] Strange issue with AES CTR mode with Python and Javascript

查看:1713
本文介绍了奇怪的问题与AES CTR模式与Python和Javascript的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图解密由CryptoJS使用PyCrypto创建的密文。我使用AES-256-CTR,一个12字节的随机前缀和4字节的计数器。到目前为止,我的成功有限。 请阅读此前的帖子,我在此首次尝试。



这适用于Javascript:


  1. 安装CryptoCat扩充功能

  2. 运行CryptoCat

  3. 启动开发者控制台(Chrome / Firefox中的F12)

  4. 运行这些代码行






  key ='b1df40bc2e4a1d4e31c50574735e1c909aa3c8fda58eca09bf2681ce4d117e11'; 
msg ='LwFUZbKzuarvPR6pmXM2AiYVD2iL0 / Ww2gs / 9OpcMy + MWasvvzA2UEmRM8dq4loB\\\
dfPaYOe65JqGQMWoLOTWo1TreBd9vmPUZt72nFs =';
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):
如果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))
返回纯文本

如果__name__ ==__main__:
#original:
键='b1df40bc2e4a1d4e31c50574735e1c909aa3c8fda58eca09bf2681ce4d117e11'
味精='LwFUZbKzuarvPR6pmXM2AiYVD2iL0 / Ww2gs / 9OpcMy + MWasvvzA2UEmRM8dq4loB\\\
dfPaYOe65JqGQMWoLOTWo1TreBd9vmPUZt72nFs ='
IV ='gpG388l8rT02vBH4'

解密= decrypt_msg(key,msg,iv)
printDecrypted message:,repr(decrypt_msg(key,msg,iv))
打印解密
pre>

输出为:



'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# 

这使得计数器输出相同的值( \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 ,I得到相似的结果 - 后续的16字节块被揭示。唯一的例外是使用0,显示前32个字节。

 全0:显示前32个字节。 
'Hello,world!ImiAq7aVLlmZDM9RfhD\xeb = \x93& b\xaf\xaf\x8d\xc9\xdeA\\\
\xd2\xd8\x01j\x12\\ \\ x97 \xe2i:%} G\x06\x0f\xb7e\x94\xde\x8d\xc83\x8f @ \x1e\xa0!\xfa\t\xe6 \x91\x84Q\xe3'
全1:显示接下来的16个字节。
\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]
全2:显示下16个字节。
'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\xeR\xe8\x1b\x8d\xd7Z\x7f'
全部3个:显示接下来的13个字节
'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 

这真的很奇怪。

解决方案

如果有人可以帮助,我会非常感谢。 >这里有几个问题。首先,消息不是块大小的倍数,并且您不使用填充。第二 - 也是这个问题最关键的 - 是IV也不是正确的大小。它应该是16字节,但是你只有12.可能两个实现都应该失败与一个异常,并在CryptoJS的下一个主要版本,这将是这种情况。



这里是由于这个错误发生了什么:当计数器第一次增加时,它试图增加未定义的值,因为IV的最后一个字节丢失。未定义+1是NaN,NaN | 0是0.这就是你最终得到0两次。


I'm trying to decrypt a ciphertext created by CryptoJS using PyCrypto. I am using AES-256-CTR, with a 12-byte random prefix and 4-byte counter. So far, I've had limited success. Please read this previous post where I made a first attempt.

This works in Javascript:

  1. Install the CryptoCat extension
  2. Run CryptoCat
  3. Fire up the developer console (F12 in Chrome/Firefox)
  4. Run these lines of code


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

Expected output: "Hello, world!ImiAq7aVLlmZDM9RfhDQgPp0CrAyZE0lyzJ6HDq4VoUmIiKUg7i2xpTSPs28USU8".


Here is a script I wrote in Python that partially(!) decrypts the ciphertext:

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

The output is:

'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\xcbaO)\x94\x9aq<\xa7\x7f\x14\x11\xb5\xb0\xb6\xb5GQ\x92'

The problem is, only the first 16 bytes of the output match the first 16 bytes of the expected output!

Hello, world!ImiAq7aVLlmZDM9RfhDQgPp0CrAyZE0lyzJ6HDq4VoUmIiKUg7i2xpTSPs28USU8

When I modify the script to do this:

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

and

self.current_val += 0 #do not increment

which makes the counter output the same value (\x00\x00\x00\x01) every time it is called, the plaintext is:

\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]

where the 2nd block of 16 bytes (dQgPp0CrAyZE0lyzJ) matches the expected output.

When I set the counter to emit \x00\x00\x00\x02 and \x00\x00\x00\x03, I get similar results- subsequent 16-byte blocks are revealed. The only exception is that with 0s, the first 32 bytes are revealed.

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'

If you concat the "correct" blocks, you'll get the expected plaintext:

Hello, world!ImiAq7aVLlmZDM9RfhDQgPp0CrAyZE0lyzJ6HDq4VoUmIiKUg7i2xpTSPs28USU8

This is really strange. I am definitely doing something wrong on the Python end as things can be decrypted, but not all in one go. If anyone can help, I would be really grateful. Thank you.

解决方案

There are a couple problems here. First, the message is not a multiple of the block size, and you're not using padding. And second -- and the most crucial to this issue -- is that the IV is also not the correct size. It should be 16 bytes, but you only have 12. Probably both implementations should fail with an exception, and in the next major revision of CryptoJS, this will be the case.

Here's what happens due to this mistake: When the counter increments for the first time, it tries to increment the undefined value, because the last byte of the IV is missing. Undefined + 1 is NaN, and NaN | 0 is 0. That's how you end up getting 0 twice.

这篇关于奇怪的问题与AES CTR模式与Python和Javascript的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆