Python中的Pycrypto重复Windows加密服务提供程序问题
编辑和更新
2013年3月24日:
我在Python中得到的输出哈希现在和C++中的哈希匹配了,前提是我把它转换成utf-16格式,并在遇到任何'e'或'm'字节之前停止。然而,解密的结果却不一致。我知道我的SHA1哈希是20个字节(160位),而RC4的密钥长度可以从40位到2048位不等,所以可能在WinCrypt中有一些默认的盐值处理,我需要模仿一下。CryptGetKeyParam KP_LENGTH或KP_SALT
2013年3月24日:
CryptGetKeyParam KP_LENGTH告诉我我的密钥长度是128位。我给它输入的是160位的哈希。所以可能它只是丢弃了最后32位……或者4个字节。现在正在测试。
2013年3月24日:
没错,就是这样。如果我在Python中丢弃SHA1哈希的最后4个字节……我得到的解密结果就一致了。
快速信息:
我有一个C++程序用来解密数据块。它使用Windows加密服务提供者,所以只能在Windows上运行。我希望它能在其他平台上也能工作。
方法概述:
在Windows Crypto API中,ASCII编码的密码字节被转换为宽字符表示,然后用SHA1进行哈希,以生成RC4流密码的密钥。
在Python PyCrypto中,ASCII编码的字节字符串被解码为Python字符串。根据经验观察到的字节进行截断,这些字节导致mbctowcs在C++中停止转换。这个截断后的字符串然后被编码为utf-16,实际上在字符之间填充了0x00字节。这个新的截断、填充后的字节字符串被传递给SHA1哈希,哈希结果的前128位被传递给PyCrypto的RC4对象。
问题 [已解决]
我似乎无法在Python 3.x中使用PyCrypto得到相同的结果。
C++代码框架:
HCRYPTPROV hProv = 0x00;
HCRYPTHASH hHash = 0x00;
HCRYPTKEY hKey = 0x00;
wchar_t sBuf[256] = {0};
CryptAcquireContextW(&hProv, L"FileContainer", L"Microsoft Enhanced RSA and AES Cryptographic Provider", 0x18u, 0);
CryptCreateHash(hProv, 0x8004u, 0, 0, &hHash);
//0x8004u is SHA1 flag
int len = mbstowcs(sBuf, iRec->desc, sizeof(sBuf));
//iRec is my "Record" class
//iRec->desc is 33 bytes within header of my encrypted file
//this will be used to create the hash key. (So this is the password)
CryptHashData(hHash, (const BYTE*)sBuf, len, 0);
CryptDeriveKey(hProv, 0x6801, hHash, 0, &hKey);
DWORD dataLen = iRec->compLen;
//iRec->compLen is the length of encrypted datablock
//it's also compressed that's why it's called compLen
CryptDecrypt(hKey, 0, 0, 0, (BYTE*)iRec->decrypt, &dataLen);
// iRec is my record that i'm decrypting
// iRec->decrypt is where I store the decrypted data
//&dataLen is how long the encrypted data block is.
//I get this from file header info
Python代码框架:
from Crypto.Cipher import ARC4
from Crypto.Hash import SHA
#this is the Decipher method from my record class
def Decipher(self):
#get string representation of 33byte password
key_string= self.desc.decode('ASCII')
#so far, these characters fail, possibly others but
#for now I will make it a list
stop_chars = ['e','m']
#slice off anything beyond where mbstowcs will stop
for char in stop_chars:
wc_stop = key_string.find(char)
if wc_stop != -1:
#slice operation
key_string = key_string[:wc_stop]
#make "wide character"
#this is equivalent to padding bytes with 0x00
#Slice off the two byte "Byte Order Mark" 0xff 0xfe
wc_byte_string = key_string.encode('utf-16')[2:]
#slice off the trailing 0x00
wc_byte_string = wc_byte_string[:len(wc_byte_string)-1]
#hash the "wchar" byte string
#this is the equivalent to sBuf in c++ code above
#as determined by writing sBuf to file in tests
my_key = SHA.new(wc_byte_string).digest()
#create a PyCrypto cipher object
RC4_Cipher = ARC4.new(my_key[:16])
#store the decrypted data..these results NOW MATCH
self.decrypt = RC4_Cipher.decrypt(self.datablock)
怀疑的[编辑:确认]原因
1. mbstowcs转换密码时,输入到SHA1哈希的“原始数据”在Python和C++中并不相同。mbstowcs在0x65和0x6D字节处停止了转换。原始数据以宽字符编码结束,但只包含原始33字节密码的一部分。
- RC4的密钥长度可以变化。在增强的Win Crypt服务提供者中,默认长度是128位。未指定密钥长度时,取的是“原始数据”的160位SHA1哈希的前128位。
我如何调查的
编辑:根据我自己的实验和@RolandSmith的建议,我现在知道我的一个问题是mbctowcs的行为超出了我的预期。它似乎在“e”(0x65)和“m”(0x6D)处停止写入sBuf(可能还有其他字节)。所以我描述中的密码“Monkey”(ASCII编码字节),在sBuf中看起来像“M o n k”,因为mbstowcs在“e”处停止了,并根据我系统上的2字节wchar类型定义在字节之间放置了0x00。我是通过将转换结果写入文本文件发现这一点的。
BYTE pbHash[256]; //buffer we will store the hash digest in
DWORD dwHashLen; //store the length of the hash
DWORD dwCount;
dwCount = sizeof(DWORD); //how big is a dword on this system?
//see above "len" is the return value from mbstowcs that tells how
//many multibyte characters were converted from the original
//iRec->desc an placed into sBuf. In some cases it's 3, 7, 9
//and always seems to stop on "e" or "m"
fstream outFile4("C:/desc_mbstowcs.txt", ios::out | ios::trunc | ios::binary);
outFile4.write((const CHAR*)sBuf, int(len));
outFile4.close();
//now get the hash size from CryptGetHashParam
//an get the acutal hash from the hash object hHash
//write it to a file.
if(CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE *)&dwHashLen, &dwCount, 0)) {
if(CryptGetHashParam(hHash, 0x0002, pbHash, &dwHashLen,0)){
fstream outFile3("C:/test_hash.txt", ios::out | ios::trunc | ios::binary);
outFile3.write((const CHAR*)pbHash, int(dwHashLen));
outFile3.close();
}
}
参考资料:
宽字符在不同环境下会导致问题
VC++ 6.0和VS 2008之间的Windows加密服务差异
将utf-8转换为utf-16字符串
Python - 从二进制文件转换宽字符字符串为Python Unicode字符串
PyCrypto RC4示例
https://www.dlitz.net/software/pycrypto/api/current/Crypto.Cipher.ARC4-module.html
http://msdn.microsoft.com/en-us/library/windows/desktop/aa379916(v=vs.85).aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/aa375599(v=vs.85).aspx
1 个回答
你可以用一个简单的测试程序(用C语言写)来检查 wchar_t
的大小:
#include <stdio.h> /* for printf */
#include <stddef.h> /* for wchar_t */
int main(int argc, char *argv[]) {
printf("The size of wchar_t is %ld bytes.\n", sizeof(wchar_t));
return 0;
}
如果你能从终端运行C++程序,也可以在你的C++代码里用 printf()
来输出,比如 iRec->desc
和 sbuf
中的哈希结果到屏幕上。否则,你可以用 fprintf()
把它们写入一个文件。
为了更好地模拟C++程序的行为,你甚至可以在Python代码中使用 ctypes
来调用 mbstowcs()
。
编辑:你写道:
一个问题肯定是出在 mbctowcs 上。它似乎把一个不可预测(对我来说)的字节数传输到我的缓冲区中进行哈希。
请记住,mbctowcs
返回的是转换后的宽字符数量。换句话说,在多字节编码中,一个33字节的缓冲区可以根据使用的编码包含从5个(UTF-8的6字节序列)到33个字符的任何数量。
编辑2:你在 CryptDeriveKey
的 dwFlags
参数中使用了0。根据它的 文档,高16位应该包含密钥长度。你应该检查 CryptDeriveKey
的返回值,以确认调用是否成功。
编辑3:你可以在Python中测试 mbctowcs
(我这里使用的是 IPython):
In [1]: from ctypes import *
In [2]: libc = CDLL('libc.so.7')
In [3]: monkey = c_char_p(u'Monkey')
In [4]: test = c_char_p(u'This is a test')
In [5]: wo = create_unicode_buffer(256)
In [6]: nref = c_size_t(250)
In [7]: libc.mbstowcs(wo, monkey, nref)
Out[7]: 6
In [8]: print wo.value
Monkey
In [9]: libc.mbstowcs(wo, test, nref)
Out[9]: 14
In [10]: print wo.value
This is a test
注意,在Windows上,你可能应该使用 libc = cdll.msvcrt
而不是 libc = CDLL('libc.so.7')
。