在Crypto ++中获取Unicode字符串的SHA1

时间:2015-04-20 18:09:00

标签: c++ unicode sha1 crypto++ digest

我独立学习C ++,我遇到了一个问题,我解决的问题不止一周。我希望你能帮助我。

我需要获取Unicode字符串的SHA1摘要(如Привет),但我不知道该怎么做。

我尝试这样做,但它返回了错误的摘要!

wstring('Ы') 它返回 - A469A61DF29A7568A6CC63318EA8741FA1CF2A7
我需要 - 8dbe718ab1e0c4d75f7ab50fc9a53ec4f0528373

对我的英语表示遗憾和抱歉:)。

CryptoPP 5.6.2 MVC ++ 2013

#include <iostream>
#include "cryptopp562\cryptlib.h"
#include "cryptopp562\sha.h"
#include "cryptopp562\hex.h"

int main() {

    std::wstring string(L"Ы");
    int bs_size = (int)string.length() * sizeof(wchar_t);

    byte* bytes_string = new byte[bs_size];

    int n = 0; //real bytes count
    for (int i = 0; i < string.length(); i++) {
        wchar_t wcharacter = string[i];

        int high_byte = wcharacter & 0xFF00;

        high_byte = high_byte >> 8;

        int low_byte = wcharacter & 0xFF;

        if (high_byte != 0) {
            bytes_string[n++] = (byte)high_byte;
        }

        bytes_string[n++] = (byte)low_byte;
    }

    CryptoPP::SHA1 sha1;
    std::string hash;

    CryptoPP::StringSource ss(bytes_string, n, true,
        new CryptoPP::HashFilter(sha1,
            new CryptoPP::HexEncoder(
                new CryptoPP::StringSink(hash)
            ) 
        ) 
    );

    std::cout << hash << std::endl;

    return 0;
}

3 个答案:

答案 0 :(得分:3)

你说'但它会返回错误的摘要' - 你在比较什么?

关键点:SHA-1等摘要不适用于字符序列,但使用字节序列

您在此代码段中所做的是在字符串"Ы"中生成unicode字符的ad-hoc 编码。这个编码将(事实证明)匹配UTF-16编码如果字符串中的字符都在BMP中(“基本多语言平面”,在这种情况下为真)如果最终在wcharacter中的数字是表示unicode代码点的整数(这可能是正确的,但不是,我认为,保证)。

如果您正在比较的摘要将输入字符串转换为使用UTF-8编码的字节序列(这很可能),那么这将产生与您的字节序列不同的字节序列,以便SHA-该序列的1个摘要将与您在此处计算的摘要不同。

所以:

  • 检查测试字符串使用的编码。

  • 您最好使用一些库函数来专门生成您要处理的字符串的UTF-16或UTF-8(视情况而定)编码,以确保您正在使用的字节序列与你的想法一样。

在恰当命名的文档The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)

中对unicode和编码进行了很好的介绍

答案 1 :(得分:3)

  

我需要获取Unicode字符串的SHA1摘要(如Привет),但我不知道该怎么做。

这里的技巧是你需要知道如何编码Unicode字符串。在Windows上,wchar_t是2个八位字节;在Linux上,wchar_t是4个。在Character Set Considerations上有一个Crypto ++维基页面,但不是那么好。

为了最有效地进行互操作,请始终使用UTF-8。这意味着您将UTF-16或UTF-32转换为UTF-8。由于您使用的是Windows,因此您需要使用CP_UTF8致电WideCharToMultiByte function进行转换。如果您使用的是Linux,则可以使用libiconv

Crypto ++有一个名为StringNarrow的内置函数,它使用C ++。它在文件misc.h中。请务必在使用前致电setlocale

Stack Overflow对使用Windows功能有一些疑问。例如,请参阅How do you properly use WideCharToMultiByte


  

我需要 - 8dbe718ab1e0c4d75f7ab50fc9a53ec4f0528373

什么是哈希值(SHA-1,SHA-256,...)?它是HMAC(键控哈希)吗?信息是否被腌制(如存储中的密码)?它是如何编码的?我不得不问,因为我无法重现你想要的结果:

SHA-1:   2805AE8E7E12F182135F92FB90843BB1080D3BE8
SHA-224: 891CFB544EB6F3C212190705F7229D91DB6CECD4718EA65E0FA1B112
SHA-256: DD679C0B9FD408A04148AA7D30C9DF393F67B7227F65693FFFE0ED6D0F0ADE59
SHA-384: 0D83489095F455E4EF5186F2B071AB28E0D06132ABC9050B683DA28A463697AD
         1195FF77F050F20AFBD3D5101DF18C0D
SHA-512: 0F9F88EE4FA40D2135F98B839F601F227B4710F00C8BC48FDE78FF3333BD17E4
         1D80AF9FE6FD68515A5F5F91E83E87DE3C33F899661066B638DB505C9CC0153D

这是我使用的程序。请务必指定宽字符串的长度。如果不这样做(并使用-1作为长度),则WideCharToMultiByte将在计算中包含终止ASCII-Z。由于我们使用std::string,因此我们不需要包含ASCII-Z终止符的函数。

int main(int argc, char* argv[])
{
    wstring m1 = L"Привет"; string m2;

    int req = WideCharToMultiByte(CP_UTF8, 0, m1.c_str(), (int)m1.length(), NULL, 0, NULL, NULL);
    if(req < 0 || req == 0)
        throw runtime_error("Failed to convert string");

    m2.resize((size_t)req);

    int cch = WideCharToMultiByte(CP_UTF8, 0, m1.c_str(), (int)m1.length(), &m2[0], (int)m2.length(), NULL, NULL);
    if(cch < 0 || cch == 0)
        throw runtime_error("Failed to convert string");

    // Should not be required
    m2.resize((size_t)cch);

    string s1, s2, s3, s4, s5;
    SHA1 sha1; SHA224 sha224; SHA256 sha256; SHA384 sha384; SHA512 sha512;

    HashFilter f1(sha1, new HexEncoder(new StringSink(s1)));
    HashFilter f2(sha224, new HexEncoder(new StringSink(s2)));
    HashFilter f3(sha256, new HexEncoder(new StringSink(s3)));
    HashFilter f4(sha384, new HexEncoder(new StringSink(s4)));
    HashFilter f5(sha512, new HexEncoder(new StringSink(s5)));

    ChannelSwitch cs;
    cs.AddDefaultRoute(f1);
    cs.AddDefaultRoute(f2);
    cs.AddDefaultRoute(f3);
    cs.AddDefaultRoute(f4);
    cs.AddDefaultRoute(f5);

    StringSource ss(m2, true /*pumpAll*/, new Redirector(cs));

    cout << "SHA-1:   " << s1 << endl;
    cout << "SHA-224: " << s2 << endl;
    cout << "SHA-256: " << s3 << endl;
    cout << "SHA-384: " << s4 << endl;
    cout << "SHA-512: " << s5 << endl;

    return 0;
}

答案 2 :(得分:2)

这似乎对我有用。

而不是摆弄尝试提取片段,我只是将宽字符缓冲区转换为const byte*并将其(以及调整后的大小)传递给哈希函数。

int main() {

    std::wstring string(L"Привет");

    CryptoPP::SHA1 sha1;
    std::string hash;

    CryptoPP::StringSource ss(
        reinterpret_cast<const byte*>(string.c_str()), // cast to const byte*
        string.size() * sizeof(std::wstring::value_type), // adjust for size
        true,
        new CryptoPP::HashFilter(sha1,
            new CryptoPP::HexEncoder(
                new CryptoPP::StringSink(hash)
            )
        )
    );

    std::cout << hash << std::endl;

    return 0;
}

<强>输出:

C6F8291E68E478DD5BD1BC2EC2A7B7FC0CEE1420

编辑:要添加。

结果将依赖于encoding。例如,我在Linux上运行此操作,其中wchar_t为4个字节。在Windows我相信wchar_t可能只有2个字节。

为了保持一致性,最好使用UTF8将文本存储在普通std::string中。这也使得调用API更简单:

int main() {

    std::string string("Привет"); // UTF-8 encoded

    CryptoPP::SHA1 sha1;
    std::string hash;

    CryptoPP::StringSource ss(
        string,
        true,
        new CryptoPP::HashFilter(sha1,
            new CryptoPP::HexEncoder(
                new CryptoPP::StringSink(hash)
            )
        )
    );

    std::cout << hash << std::endl;

    return 0;
}

<强>输出:

2805AE8E7E12F182135F92FB90843BB1080D3BE8