2013年3月24日:
我的Python输出哈希现在在转换为utf-16之后匹配来自c ++的哈希,并在命中任何'e'或'm'字节之前停止。但是解密的结果不匹配。我知道我的SHA1哈希是20字节= 160位,RC4密钥的长度可以从40到2048位不等,所以在WinCrypt中可能会有一些我需要模仿的默认salting。 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 w / PyCrypto获得相同的结果
C ++ Code Skeleton:
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 Code Skeleton:
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字节密码的一部分的wide_char编码结束。
我如何调查 编辑:根据我自己的实验和@RolandSmith的建议,我现在知道我的一个问题是mbctowcs表现得像我没想到的那样。它似乎停止在“e”(0x65)和“m”(0x6d)(可能是其他)上写入sBuf。因此,在我的描述(Ascii编码的字节)中的passoword“Monkey”在sBuf中看起来像“M o n k”,因为mbstowcs在e处停止,并且在我的系统上基于2字节wchar typedef在字节之间放置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();
}
}
参考文献:
根据环境定义,宽字符会导致问题
Difference in Windows Cryptography Service between VC++ 6.0 and VS 2008
将utf-8转换为utf-16字符串
Python - converting wide-char strings from a binary file to Python unicode strings
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
答案 0 :(得分: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 ++代码中使用printf()
次调用来编写例如如果您可以从终端运行C ++程序,则iRec->desc
和sbuf
中的哈希结果将显示在屏幕上。否则,使用fprintf()
将它们转储到文件中。
为了更好地模仿C ++程序的行为,您甚至可以使用ctypes
在Python代码中调用mbstowcs()
。
编辑:您写道:
mbctowcs肯定存在一个问题。它似乎正在将一个不可预测的(对我来说)字节数转移到我的缓冲区中进行哈希处理。
请记住,mbctowcs
会返回转换后的宽字符数。换句话说,多字节编码中的33字节缓冲区
可以包含5个(UTF-8个6字节序列)中的任何内容,最多33个字符,具体取决于所使用的编码。
Edit2:您使用0作为dwFlags
的{{1}}参数。根据其documentation,高16位应包含密钥长度。您应该检查CryptDeriveKey
的返回值以查看呼叫是否成功。
Edit3 :您可以在Python中测试CryptDeriveKey
(我在这里使用IPython。):
mbctowcs
请注意,在Windows中,您应该使用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
而不是libc = cdll.msvcrt
。