У меня есть два произвольных источника, скажем, StringSource из подписи и FileSource из соответствующего подписанного файла.Теперь я хочу проверить подпись файлов ...
Использование нескольких источников в одной цепочке фильтров может быть сложно.Я знаю, что в библиотеке есть несколько запеченных классов, но они мне никогда не нравились.Они принимают несколько входных каналов и демультиплексируют их в один канал.Вы можете увидеть их в действии в test.cpp
, функциях SecretRecoverFile
(около строки 650) и InformationRecoverFile
(около строки 700).
Есть лиспособ передать два произвольных источника, где один представляет подпись, а другой - подписанное содержимое (может быть файл или строка), объекту проверки?
Вот как я справлюсь с тем, что выхотеть сделать.В приведенном ниже примере используются два источника и используется общая цепочка фильтров.Я уменьшил сложность, хэшируя две строки, используя HashFilter
.В вашем примере используются сообщение, подпись, пары ключей и SignatureVerificationFilter
, но он сложнее, чем нужно, чтобы показать вам, как это сделать.
Пример состоит из четырех частей:
- Part 0 - настроить данные.Создаются две строки 16K ASCII.Одна строка также записывается в файл.
- Часть 1 - распечатайте данные.
Hash(s1)
, Hash(s2)
и Hash(s1+s2)
напечатаны. - Part 2 - используйте два строковых источника.
Hash(s1+s2)
создается с использованием двух StringSources
- Part 3 - используйте один источник строки и один источник файла.
Hash(s1+s2)
создается с использованием одного StringSource
и одного FileSource
Чтобы указать очевидное, в упрощенном примере вычисляется Hash(s1+s2)
.В вашем контексте это операция Verify(key, s1+s2)
, где key
- открытый ключ, s1
- подпись и s2
- содержимое файла.
Часть 0 - Данные приведены ниже.Это довольно скучно.Примечание s3
представляет собой объединение s1
и s2
.
std::string s1, s2, s3;
const size_t size = 1024*16+1;
random_string(s1, size);
random_string(s2, size);
s3 = s1 + s2;
Part 1 - данные напечатаны ниже.Хеши s1
, s2
и s3
напечатаны.s3
является важным.s3
- это то, к чему мы должны прийти, используя два отдельных источника.
std::string r;
StringSource ss1(s1, true, new HashFilter(hash, new StringSink(r)));
std::cout << "s1: ";
hex.Put((const byte*)r.data(), r.size());
std::cout << std::endl;
r.clear();
StringSource ss2(s2, true, new HashFilter(hash, new StringSink(r)));
std::cout << "s2: ";
hex.Put((const byte*)r.data(), r.size());
std::cout << std::endl;
r.clear();
StringSource ss3(s3, true, new HashFilter(hash, new StringSink(r)));
std::cout << "s3: ";
hex.Put((const byte*)r.data(), r.size());
std::cout << std::endl;
Вывод выглядит так:
$ ./test.exe
s1: 45503354F9BC56C9B5B61276375A4C60F83A2F01
s2: 6A3AD5B683DE7CA57F07E8099268A8BC80FA200B
s3: BFC1882CEB24697A2B34D7CF8B95604B7109F28D
...
Часть 2 - Здесь все становится интересно.Мы используем два разных StringSource
для обработки s1
и s2
по отдельности.
StringSource ss4(s1, false);
StringSource ss5(s2, false);
HashFilter hf1(hash, new StringSink(r));
ss4.Attach(new Redirector(hf1));
ss4.Pump(LWORD_MAX);
ss4.Detach();
ss5.Attach(new Redirector(hf1));
ss5.Pump(LWORD_MAX);
ss5.Detach();
hf1.MessageEnd();
std::cout << "s1 + s2: ";
hex.Put((const byte*)r.data(), r.size());
std::cout << std::endl;
Он производит следующий вывод:
$ ./test.exe
s1: 45503354F9BC56C9B5B61276375A4C60F83A2F01
s2: 6A3AD5B683DE7CA57F07E8099268A8BC80FA200B
s3: BFC1882CEB24697A2B34D7CF8B95604B7109F28D
s1 + s2: BFC1882CEB24697A2B34D7CF8B95604B7109F28D
...
Вкод выше.Во-первых, мы динамически подключаем и отсоединяем цепочку хеш-фильтров к источникам ss4
и ss5
.
Во-вторых, когда фильтр подключен, мы используем Pump(LWORD_MAX)
для закачки всех данных из источника в фильтр.цепь.Мы не используем PumpAll()
, потому что PumpAll()
сигнализирует об окончании текущего сообщения и генерирует MessageEnd()
.Мы обрабатываем одно сообщение несколькими частями;мы не обрабатываем несколько сообщений.Поэтому нам нужен только один MessageEnd()
, когда мы определим.
В-третьих, как только мы закончим с источником, мы называем Detach
, поэтому StringSource
деструкторы не вызывают ложноеMessageEnd()
сообщение для входа в цепочку фильтров.Опять же, мы обрабатываем одно сообщение несколькими частями;мы не обрабатываем несколько сообщений.Поэтому нам нужен только один MessageEnd()
, когда мы определим.
В-четвертых, когда мы закончим отправку наших данных в фильтр, мы вызываем hf.MessageEnd()
, чтобы сообщить фильтру обработать все ожидающие или буферизованные данные.Это когда нам нужен вызов MessageEnd()
, а не раньше.
В-пятых, мы вызываем Detach()
, когда закончим, а не Attach()
.Detach()
удаляет существующую цепочку фильтров и предотвращает утечки памяти.Attach()
присоединяет новую цепочку, но не удаляет существующий фильтр или цепочку.Так как мы используем Redirector
, наш HashFilter
выживет.HashFilter
в конечном итоге очищается как переменная автоматического стека.
Кроме того, если бы использовались ss4.PumpAll()
и ss5.PumpAll()
(или позволяли деструкторам отправлять MessageEnd()
в цепочку фильтров), то вы бы получили объединение Hash(s1)
и Hash(s2)
, потому что это выглядело бы какдва разных сообщения на фильтр вместо одного сообщения на две части.Приведенный ниже код неверен:
StringSource ss4(s1, false);
StringSource ss5(s2, false);
HashFilter hf1(hash, new StringSink(r));
ss4.Attach(new Redirector(hf1));
// ss4.Pump(LWORD_MAX);
ss4.PumpAll(); // MessageEnd
ss4.Detach();
ss5.Attach(new Redirector(hf1));
// ss5.Pump(LWORD_MAX);
ss5.PumpAll(); // MessageEnd
ss5.Detach();
// Third MessageEnd
hf1.MessageEnd();
Приведенный выше неправильный код приводит к Hash(s1) || Hash(s2) || Hash(<empty string>)
:
$ ./test.exe
s1: 45503354F9BC56C9B5B61276375A4C60F83A2F01
s2: 6A3AD5B683DE7CA57F07E8099268A8BC80FA200B
s3: BFC1882CEB24697A2B34D7CF8B95604B7109F28D
s1 + s2: 45503354F9BC56C9B5B61276375A4C60F83A2F016A3AD5B683DE7CA57F07E8099268A8BC80FA200BDA39A3EE5E6B4B0D3255BFEF95601890AFD80709
Часть 3 - Этоваш вариант использования.Мы используем StringSource
и FileSource
для обработки s1
и s2
по отдельности.Помните, что строка s2
была записана в файл с именем test.dat
.
StringSource ss6(s1, false);
FileSource fs1("test.dat", false);
HashFilter hf2(hash, new StringSink(r));
ss6.Attach(new Redirector(hf2));
ss6.Pump(LWORD_MAX);
ss6.Detach();
fs1.Attach(new Redirector(hf2));
fs1.Pump(LWORD_MAX);
fs1.Detach();
hf2.MessageEnd();
std::cout << "s1 + s2 (file): ";
hex.Put((const byte*)r.data(), r.size());
std::cout << std::endl;
Вот как работает полный пример:
$ ./test.exe
s1: 45503354F9BC56C9B5B61276375A4C60F83A2F01
s2: 6A3AD5B683DE7CA57F07E8099268A8BC80FA200B
s3: BFC1882CEB24697A2B34D7CF8B95604B7109F28D
s1 + s2: BFC1882CEB24697A2B34D7CF8B95604B7109F28D
s1 + s2 (file): BFC1882CEB24697A2B34D7CF8B95604B7109F28D
Notice s3
=s1 + s2
= s1 + s2 (file)
.
$ cat test.cxx
#include "cryptlib.h"
#include "filters.h"
#include "files.h"
#include "sha.h"
#include "hex.h"
#include <string>
#include <iostream>
void random_string(std::string& str, size_t len)
{
const char alphanum[] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
const size_t size = sizeof(alphanum) - 1;
str.reserve(len);
for (size_t i = 0; i < len; ++i)
str.push_back(alphanum[rand() % size]);
}
int main(int argc, char* argv[])
{
using namespace CryptoPP;
////////////////////////// Part 0 //////////////////////////
// Deterministic
std::srand(0);
std::string s1, s2, s3, r;
const size_t size = 1024*16+1;
random_string(s1, size);
random_string(s2, size);
// Concatenate for verification
s3 = s1 + s2;
// Write s2 to file
StringSource(s2, true, new FileSink("test.dat"));
// Hashing, resets after use
SHA1 hash;
// Printing hex encoded string to std::cout
HexEncoder hex(new FileSink(std::cout));
////////////////////////// Part 1 //////////////////////////
r.clear();
StringSource ss1(s1, true, new HashFilter(hash, new StringSink(r)));
std::cout << "s1: ";
hex.Put((const byte*)r.data(), r.size());
std::cout << std::endl;
r.clear();
StringSource ss2(s2, true, new HashFilter(hash, new StringSink(r)));
std::cout << "s2: ";
hex.Put((const byte*)r.data(), r.size());
std::cout << std::endl;
r.clear();
StringSource ss3(s3, true, new HashFilter(hash, new StringSink(r)));
std::cout << "s3: ";
hex.Put((const byte*)r.data(), r.size());
std::cout << std::endl;
////////////////////////// Part 2 //////////////////////////
r.clear();
StringSource ss4(s1, false);
StringSource ss5(s2, false);
HashFilter hf1(hash, new StringSink(r));
ss4.Attach(new Redirector(hf1));
ss4.Pump(LWORD_MAX);
ss4.Detach();
ss5.Attach(new Redirector(hf1));
ss5.Pump(LWORD_MAX);
ss5.Detach();
hf1.MessageEnd();
std::cout << "s1 + s2: ";
hex.Put((const byte*)r.data(), r.size());
std::cout << std::endl;
////////////////////////// Part 3 //////////////////////////
r.clear();
StringSource ss6(s1, false);
FileSource fs1("test.dat", false);
HashFilter hf2(hash, new StringSink(r));
ss6.Attach(new Redirector(hf2));
ss6.Pump(LWORD_MAX);
ss6.Detach();
fs1.Attach(new Redirector(hf2));
fs1.Pump(LWORD_MAX);
fs1.Detach();
hf2.MessageEnd();
std::cout << "s1 + s2 (file): ";
hex.Put((const byte*)r.data(), r.size());
std::cout << std::endl;
return 0;
}
И:
$ g++ test.cxx ./libcryptopp.a -o test.exe
$ ./test.exe
s1: 45503354F9BC56C9B5B61276375A4C60F83A2F01
s2: 6A3AD5B683DE7CA57F07E8099268A8BC80FA200B
s3: BFC1882CEB24697A2B34D7CF8B95604B7109F28D
s1 + s2: BFC1882CEB24697A2B34D7CF8B95604B7109F28D
s1 + s2 (file): BFC1882CEB24697A2B34D7CF8B95604B7109F28D
Вот класс, который может облегчить вашу боль.Это объединяет понятия выше в MultipleSources
классе.MultipleSources
является лишь частичной реализацией интерфейса Source
, но в нем должны быть все необходимые компоненты.
class MultipleSources
{
public:
MultipleSources(std::vector<Source*>& source, Filter& filter)
: m_s(source), m_f(filter)
{
}
void Pump(lword pumpMax, bool messageEnd)
{
for (size_t i=0; pumpMax && i<m_s.size(); ++i)
{
lword n = pumpMax;
m_s[i]->Attach(new Redirector(m_f));
m_s[i]->Pump2(n);
m_s[i]->Detach();
pumpMax -= n;
}
if (messageEnd)
m_f.MessageEnd();
}
void PumpAll()
{
for (size_t i=0; i<m_s.size(); ++i)
{
m_s[i]->Attach(new Redirector(m_f));
m_s[i]->Pump(LWORD_MAX);
m_s[i]->Detach();
}
m_f.MessageEnd();
}
private:
std::vector<Source*>& m_s;
Filter &m_f;
};
Вы бы назвали это так:
StringSource ss(s1, false);
FileSource fs("test.dat", false);
HashFilter hf(hash, new StringSink(r));
std::vector<Source*> srcs;
srcs.push_back(&ss);
srcs.push_back(&fs);
MultipleSources ms(srcs, hf);
ms.Pump(LWORD_MAX, false);
hf.MessageEnd();
Или вы можете использовать PumpAll
и получить тот же результат, но в этом случае вы не вызываете hf.MessageEnd();
, потому что PumpAll
сигнализирует об окончании сообщения.
MultipleSources ms(srcs, hf);
ms.PumpAll();