问题
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:
- Install the CryptoCat extension
- Run CryptoCat
- Fire up the developer console (F12 in Chrome/Firefox)
- 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