Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Системное программирование и WinAPI > Crypto api RC2


Автор: frying 2.5.2012, 20:13
Доброго времени суток!
Проблема такая: почему-то зашифрованные данные ничем не отличаются от исходных. В чём ошибка не понятно.
Код

#define _WIN32_WINNT 0x0400

#include <iostream>
#include <stdlib.h>
#include <fstream>
#include <Windows.h>
#include <WinCrypt.h>
#pragma comment(lib, "advapi32.lib")

using namespace std;

int main(){
    HCRYPTPROV hProv = 0;
    HCRYPTKEY hSessionKey = 0;

    // Получение контекста криптопровайдера
    if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
      cout << "CryptAcquireContext Error";
      cin.get();
      //return 1;
    } else {
        std::cout << "Cryptographic provider initialized" << std::endl;
    }
    // Генерация сессионного ключа
    if (!CryptGenKey(hProv, CALG_RC2, 40<<16, &hSessionKey)) {
      cout << "CryptGenKey Error";
      cin.get();
      //return 1;
    } else {
        std::cout << "Session key generated" << std::endl;
        std::cout << &hSessionKey << std::endl;
    }

    ifstream ifs;
    ifs.open("out.txt");
    ofstream ofs;
    ofs.open("in.txt");
    if (!ifs.is_open() || !ofs.is_open()) {
        cout << "File is not exist" << endl;
    } else {
        while (!ifs.eof())
        {
            const size_t buf3_size = 5;
            char buf3[buf3_size] = { 0 };
            DWORD count3 = strlen(buf3);
            ifs.read(buf3, buf3_size);
            bool flag = ifs.eof();
            // Шифрование данных
            if (!CryptEncrypt(hSessionKey, 0, flag, 0, (BYTE*)buf3, &count3, 40)) {
                cout << "CryptEncrypt Error";
                cin.get();
                return 1;
            } else {
                for (size_t i = 0; i < ifs.gcount(); ++i)
                {
                    ofs << buf3[i];
                }
            }
        }
        ifs.close();
        ofs.close();
    }
    cin.get();
}


Проверялось на Visual C++.

Автор: feodorv 2.5.2012, 22:36
А это что такое:
Цитата(frying @  2.5.2012,  21:13 Найти цитируемый пост)
            char buf3[buf3_size] = { 0 };
            DWORD count3 = strlen(buf3);

Почему strlen? Как это вообще могло работать???

Автор: GremlinProg 3.5.2012, 06:27
Цитата(feodorv @  3.5.2012,  00:36 Найти цитируемый пост)
Как это вообще могло работать???

в принципе, могло бы:
Цитата(frying @  2.5.2012,  22:13 Найти цитируемый пост)
const size_t buf3_size = 5;

а вот здесь, думаю есть ошибка:
Цитата(frying @  2.5.2012,  22:13 Найти цитируемый пост)
for (size_t i = 0; i < ifs.gcount(); ++i)

CryptEncrypt возвращает чсисло зашифрованных байт в count3,
так что если там 0, вполне возможно, что данные на выходе остались неизменными,
наверное, что-то не так с логикой: флаги, порядок вызовов API и т.п.

Автор: frying 3.5.2012, 06:39
Попробовал сделать так:
Код

for (size_t i = 0; i < count3; ++i)
{
    cout << count3 << endl;
    ofs << buf3[i];
}

На экране всё время выводится 8.
Насчёт флагов - ведь там сейчас всего один действующий flag, который показывает последний блок из файла или нет будет передан.

Автор: GremlinProg 3.5.2012, 06:53
Цитата(frying @  2.5.2012,  22:13 Найти цитируемый пост)
CryptEncrypt(hSessionKey, 0, flag, 0, (BYTE*)buf3, &count3, 40)

флаг тут может быть только 0 либо CRYPT_OAEP, ничего другого быть не должно,
а последний параметр должен принимать размер буфера buf3 в байтах,
ни где не вижу, чтобы он был распределен на 40 байт,

чтобы определить необходимый размер буфера, который требуется выделить для CryptEncrypt, необходимо вызвать его с pbData=NULL, тогда размер буфера будет возвращен в параметре pdwDataLen

документация: http://msdn.microsoft.com/en-us/library/windows/desktop/aa379924(v=vs.85).aspx

Автор: feodorv 3.5.2012, 17:10
Цитата(GremlinProg @  3.5.2012,  07:27 Найти цитируемый пост)
CryptEncrypt возвращает чсисло зашифрованных байт в count3,

Я, в сущности, к тому, что CryptEncrypt и принимает число приготовленных байт для шифрования тоже в count3, а если там 0?

Вот сколько байт считано из файла:
Цитата(frying @  2.5.2012,  21:13 Найти цитируемый пост)
ifs.read(buf3, buf3_size);


вот столько и надо определять в count3:
Цитата(frying @  2.5.2012,  21:13 Найти цитируемый пост)
            if (!CryptEncrypt(hSessionKey, 0, flag, 0, (BYTE*)buf3, &count3, 40)) {

на каждом шаге цикла чтения из файла.


Цитата(GremlinProg @  3.5.2012,  07:53 Найти цитируемый пост)
ни где не вижу, чтобы он был распределен на 40 байт,

+1

Автор: frying 4.5.2012, 07:01
GremlinProg, флаг нужен, в спецификации он называется Final.
Пробую получить размер буфера:
Код

const int buf3_size = 8;
char buf3[buf3_size] = { 0 };
DWORD pdwDataLen;
CryptEncrypt(hSessionKey, 0, true, 0, 0, &pdwDataLen , buf3_size);
cout << pdwDataLen  << endl;


Но pdwDataLen  всегда 0. Почему?

Автор: GremlinProg 4.5.2012, 07:12
Цитата(frying @  4.5.2012,  09:01 Найти цитируемый пост)
GremlinProg, флаг нужен, в спецификации он называется Final

извиняюсь smile, спутал с dwFlags, след. параметр  

Автор: feodorv 4.5.2012, 09:59
Цитата(frying @  4.5.2012,  08:01 Найти цитируемый пост)
Но pdwDataLen  всегда 0. Почему? 

А чем оно инициализируется?
Цитата(frying @  4.5.2012,  08:01 Найти цитируемый пост)
DWORD pdwDataLen; // <-- ничем...
CryptEncrypt(hSessionKey, 0, true, 0, 0, &pdwDataLen , buf3_size);

Скорее всего, там 0, что в купе с true даёт 0 в pdwDataLen...

Добавлено через 3 минуты и 47 секунд
Цитата

pdwDataLen 
[in, out] Pointer to a DWORD value holding the length of the data buffer. Before calling this function, the DWORD value is set to the number of bytes to be encrypted. Upon return, the DWORD value contains the number of bytes needed to hold the encrypted data

Кстати, переменную лучше назвать как dwDataLen, так как p... намекает на указатель smile 

Автор: frying 4.5.2012, 15:53
feodorv спасибо за совет, но я думал, что если dwDataLen будет всё равно инициализироваться в функции, зачем задавать начальное значение. Сейчас, если инициализирую нулём, то функция с флагом final = true даёт значение 8, если же final = false, то значение 0.
Посмотрите, пожалуйста, переделанный код
Код

#define _WIN32_WINNT 0x0400

#include <iostream>
#include <stdlib.h>
#include <fstream>
#include <Windows.h>
#include <WinCrypt.h>
#pragma comment(lib, "advapi32.lib")
#include <WinError.h>

using namespace std;

HCRYPTKEY getSessionKey()
{
    HCRYPTPROV hProv = 0;
    HCRYPTKEY hSessionKey = 0;

    // Получение контекста криптопровайдера
    if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
      cout << "CryptAcquireContext Error";
      cin.get();
      //return 1;
    } else {
        std::cout << "Cryptographic provider initialized" << std::endl;
    }
    // Генерация сессионного ключа
    if (!CryptGenKey(hProv, CALG_RC2, 64<<16, &hSessionKey)) {
      cout << "CryptGenKey Error";
      cin.get();
      //return 1;
    } else {
        std::cout << "Session key generated" << std::endl;
        std::cout << hSessionKey << std::endl;
    }
    return hSessionKey;
}

int errorReturn()
{
    cout << GetLastError() << endl;
    cin.get();
    return 1;
}

int main(){

    HCRYPTKEY hSessionKey = getSessionKey();
    std::cout << hSessionKey << std::endl;

    ifstream ifs;
    ifs.open("out.txt", ios::in | ios::binary);
    ofstream ofs;
    ofs.open("in.txt", ios::out | ios::binary);
    ofstream ofs1;
    ofs1.open("in1.txt", ios::out | ios::binary);
    //Если удалось открыть файл для чтения и для записи.
    if (!ifs.is_open() || !ofs.is_open()) {
        cout << "File is not exist" << endl;
    } else {
        const int buf3_size = 8;
        char buf3[buf3_size] = { 0 };
        DWORD dwDataLen = 0;
        CryptEncrypt(hSessionKey, 0, true, 0, 0, &dwDataLen , buf3_size);
        while (!ifs.eof())
        {
            ifs.read(buf3, buf3_size);
            bool flag = ifs.eof();

            cout << "Plain Text is ";
            for (int i = 0; i < buf3_size; ++i)
            {
                cout << buf3[i];
            }
            cout << endl;

            // Шифрование данных
            cout << dwDataLen  << endl;
            if (!CryptEncrypt(hSessionKey, 0, flag, 0, (BYTE*)buf3, &dwDataLen , buf3_size)) {
                ifs.close();
                ofs.close();
                ofs1.close();
                cout << "CryptEncrypt Error" << endl;
                return errorReturn();
            } else {
                cout << "Encrypted text is ";
                for (size_t i = 0; i < dwDataLen; ++i)
                {
                    cout << buf3[i];
                }
                ofs.write(buf3, dwDataLen);
                cout << endl;
            }

            // Дешифрование данных
            if (!CryptDecrypt(hSessionKey, 0, flag, 0, (BYTE*)buf3, &dwDataLen )) {
                ifs.close();
                ofs.close();
                ofs1.close();
                cout << "CryptDecrypt Error";
                return errorReturn();
            }
            cout << "Decrypted text is ";
            for (int i = 0; i < buf3_size ; ++i)
            {
                cout << buf3[i];
            }
            ofs1.write(buf3, dwDataLen);
            cout << endl;
        }
        ifs.close();
        ofs.close();
        ofs1.close();
    }
    cin.get();
}


Несколько блоков отрабатывают нормально, потом выбрасывается 234 ошибка cryptencrypt. Скажите в чём ошибка.

Автор: feodorv 4.5.2012, 16:51
Цитата(frying @  4.5.2012,  16:53 Найти цитируемый пост)
я думал, что если dwDataLen будет всё равно инициализироваться в функции, зачем задавать начальное значение.

Нет, нет. Если в CryptEncrypt dwBufLen означает доступный размер буфера, то dwDataLen - число байт в буфере, которые нужно закриптовать. Дело в том, что для потоковых алгоритмов шифрование идёт байт в байт (хотя и в этом случае в конце в хвост зашифрованным данным могут быть записаны дополнительные байты), для блоковых (а CALG_RC2 - это блоковый алгоритм) шифрование осуществляется блоками бит (в Вашем случае - по 64 бита), поэтому если на входе будет, скажем, 2 байта, то на выходе уже 8! Соответственно, в этом случае до вызова CryptEncrypt устанавливаем dwDataLen в 2, а после вызова получаем значение 8. То есть dwDataLen - это аргумент функции, который как передаёт значение в функцию при её вызове, так и получает возвращаемое значение из функции. Поэтому dwDataLen нужно (правильно!) инициализировать до вызова CryptEncrypt

Код посмотрю, но попозже... Кстати, CryptAcquireContext в начале программы Вы делаете, а CryptReleaseContext в конце - нет.

Автор: feodorv 4.5.2012, 18:14
Основное, что я вижу не так, это одновременное шифрование-дешифрование. Это очень плохо.

Во-первых, в общем случае, состояние ключа может зависеть от шифруемых данных, и если мы сначала зашифруем buf1, а потом buf2, то результат может отличаться от того, который получен, если сначала зашифровать buf2, а потом buf1. То же с одновременным шифрованием-дешифрованием: состояние ключа в случае
Код

шифруем buf1
дешифруем buf1
шифруем buf2, финиш
дешифруем buf2, финиш

может отличаться от того, который получим при
Код

шифруем buf1
шифруем buf2, финиш

затем инициализируем ключ по-новой

дешифруем buf1
дешифруем buf2, финиш

В результате мы не сможем правильно дешифровать данные.

У Вас всё ещё хуже: Вы два раза подряд запрашиваете функции шифрования с finish, равной TRUE. При первом TRUE ключ уже невозвратимо искажается:
Цитата

The following extra actions are performed when Final is TRUE:

If the key is a block cipher key, the data is padded to a multiple of the block size of the cipher. To find the block size of a cipher, use CryptGetKeyParam to get the KP_BLOCKLEN value of the key. 
If the cipher is operating in a chaining mode, the next CryptEncrypt operation resets the cipher's feedback register to the KP_IV value of the key. 
If the cipher is a stream cipher, the next CryptEncrypt resets the cipher to its initial state. 

При втором TRUE ключ переиницилизирован, и CryptDecrypt дешифрует неизвестно что...

То есть: надо сначала (непрерывно) всё закриптовать (до финального TRUE) и уже потом всё разкриптовать (то же до финального TRUE).

Добавлено @ 18:28
Кстати:
Цитата
CryptEncrypt
If the buffer allocated for pbData is not large enough to hold the data, GetLastError returns ERROR_MORE_DATA and stores the required buffer size, in bytes, in the DWORD value pointed to by pdwDataLen.

Автор: feodorv 4.5.2012, 19:43
Цитата(feodorv @  4.5.2012,  19:14 Найти цитируемый пост)
Кстати:

Есть небольшая тонкость: при финализировании данных, выровненных по границе блока, CryptEncrypt в конец буфера добавляет дополнительный блок в 8 байт. То есть буфер требуется большей длины (на те самые 8 байт). То есть:
Код

       char buf3[buf3_size+8] = { 0 };
       ...
       if (!CryptEncrypt(hSessionKey, 0, flag, 0, (BYTE*)buf3, &dwDataLen , buf3_size+8)) {


Но вообще, логику программы стоит переделать...

Автор: frying 9.5.2012, 13:48
feodorv,  большое спасибо за развёрнутый ответ.
Сделал сейчас по-другому: просто весь текст передаю одним блоком и шифрую. Основная моя ошибка была в том, что 
Цитата

Note that, depending on the algorithm used, the encrypted text can be larger than the original plaintext. In this case, the pbData buffer needs to be large enough to contain the encrypted text and any padding.

Т.е. для шифрованного теста буфер должен изначально подходить.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)