Я изучаю C ++ самостоятельно, и у меня есть одна проблема, которую я не могу решить больше недели. Я надеюсь, что вы можете мне помочь.
Мне нужно получить SHA1-дайджест строки Unicode (например, Привет
), но я не знаю, как это сделать.
Я пытался сделать это так, но он возвращает неправильный дайджест!
За 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;
}
Мне нужно получить SHA1-дайджест строки Unicode (например, Привет), но я не знаю, как это сделать.
Хитрость здесь в том, что вам нужно знать, как кодировать строку Unicode. В Windows wchar_t
2 октета; в то время как в Linux wchar_t
это 4 октета. На ней есть вики-страница Crypto ++ по адресу Особенности набора символов, но это не так хорошо.
Для наиболее эффективного взаимодействия всегда используйте UTF-8. Это означает, что вы конвертируете UTF-16 или UTF-32 в UTF-8. Поскольку вы находитесь на Windows, вы хотите позвонить Функция WideCharToMultiByte конвертировать его с помощью CP_UTF8
, Если бы вы были на Linux, то вы бы использовали libiconv
.
Crypto ++ имеет встроенную функцию под названием StringNarrow
который использует C ++. Это в файле misc.h
. Обязательно позвони setlocale
перед его использованием.
У переполнения стека есть несколько вопросов по использованию функции Windows. Смотрите, например, Как правильно использовать 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;
}
Вы говорите «но он возвращает неправильный дайджест» — с чем вы его сравниваете?
Ключевой момент: дайджесты, такие как SHA-1, работают не с последовательностями символов, а с последовательностями байтов.
То, что вы делаете в этом фрагменте кода, генерирует ad-hoc кодирование символов Юникода в строке "Ы"
, Эта кодировка будет (как оказывается) соответствовать кодировке UTF-16 если все символы в строке находятся в BMP («базовая многоязычная плоскость», что в данном случае верно) и если числа, которые заканчиваются в wcharacter
являются целыми числами, представляющими кодовые точки Юникода (что-то вроде правильно, но, я думаю, не гарантировано).
Если дайджест, с которым вы сравниваете его, превращает входную строку в последовательность байтов, используя кодировку UTF-8 (что вполне вероятно), то это приведет к тому, что последовательность байтов будет отличаться от вашей, так что дайджест SHA-1 эта последовательность будет отличаться от дайджеста, который вы рассчитываете здесь.
Так:
Проверьте, какую кодировку использует ваша тестовая строка.
Лучше всего использовать некоторые библиотечные функции для конкретной генерации кодировки UTF-16 или UTF-8 (в зависимости от случая) строки, которую вы хотите обработать, чтобы убедиться, что последовательность байтов, с которой вы работаете, соответствует вашим ожиданиям. является.
Там есть отличное введение в Unicode и кодировки в метко названном документе Абсолютный минимум Каждый разработчик программного обеспечения должен абсолютно точно знать о юникоде и наборах символов (никаких оправданий!)
Это, кажется, работает хорошо для меня.
Вместо того, чтобы возиться с попытками извлечь фрагменты, я просто приводил буфер широких символов к 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