Strange issue with AES CTR mode with Python and Javascript

女生的网名这么多〃 提交于 2019-12-12 18:26:06

问题


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.


回答1:


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.




回答2:


When using crypto mode CryptoJS.mode.CTR (where CTR stands for counter) the Initailization vector together with a counter is encrypted and then applied to the data to encrypt. This is done for each block of data you encrypt.

You explain that different parts of the message are decrypted correctly, when you apply different values to start_val, so I suspect that the counter is simply not correctly increased with each decrypt of a block.

Take a look at Block Cipher Mode: CTR at wikipedia

Caution: Please note that when using the CTR mode, the combination of the initialization vector + the counter should never be repeated.




回答3:


Fixed. I simply made the counter start with 0 twice. Does anyone know if this is a vulnerability?

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


来源:https://stackoverflow.com/questions/16040367/strange-issue-with-aes-ctr-mode-with-python-and-javascript

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!