重复的Windows加密服务提供程序导致Python与Pycrypto

时间:2013-03-21 01:13:44

标签: c++ python utf-8 cryptography utf-16

编辑和更新

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编码结束。

  1. RC4可以有可变长度的键。在Enhanced Win Crypt Sevice提供程序中,默认长度为128位。保留未指定密钥长度的是“原始数据”的160位SHA1摘要的前128位
  2. 我如何调查 编辑:根据我自己的实验和@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

    Hashing a string with Sha256

    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 个答案:

答案 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->descsbuf中的哈希结果将显示在屏幕上。否则,使用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