28.07.2006 15:20:09Шифрование по ГОСТ 28147-89 Ответов: 7
Алексей
Здравствуйте!
Почему параметр ключа KP_BLOCKLEN для ключа полученного с помощью CryptDeriveKey(рProv, 26142, hHash, CRYPT_EXPORTABLE or CRYPT_CREATE_SALT, hKey), то есть для симметричного алгоритма ГОСТ 28147-89 равен 8192, хотя по идее он должен быть по умолчанию равен 64?

Привожу всю последовательность действий:
// Запрос контекста
if not CryptAcquireContext(hProv, nil, nil, 2, CRYPT_VERIFYCONTEXT) then
begin
MessageDlg('Ошибка запроса контекста (' + ErrToStr(GetLastError) + ')', mtError, [mbOK], 0);
Exit;
end;

if not CryptCreateHash(
hProv,
32798,
0,
0,
hHash) then
begin
MessageDlg('Ошибка создания хеша (CryptCreateHash) (' + ErrToStr(GetLastError) + ')', mtError, [mbOK], 0);
CryptReleaseContext(hProv, 0);
Exit;
end;

szPwd := 'Пароль';
pszPwd := @szPwd[1];

if not CryptHashData(
hHash,
pszPwd,
Length(szPwd),
0) then
begin
MessageDlg('Ошибка получения хеша (CryptHashData) (' + ErrToStr(GetLastError) + ')', mtError, [mbOK], 0);
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
Exit;
end;

if not CryptDeriveKey(
hProv,
26142,
hHash,
CRYPT_EXPORTABLE or CRYPT_CREATE_SALT,
hKey) then
begin
MessageDlg('Ошибка получения ключа из хеша (CryptDeriveKey) (' + ErrToStr(GetLastError) + ')', mtError, [mbOK], 0);
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
Exit;
end;

CryptDestroyHash(hHash);

BlockCSize := 4;

if not CryptGetKeyParam(hKey, KP_BLOCKLEN, @BlockLen, BlockCSize, 0) then
begin
MessageDlg('Ошибка: ' + ErrToStr(GetLastError) + ' ' + IntToStr(IVLen), mtError, [mbOK], 0);
exit;
end;
MessageDlg('Длина блока: ' + IntToStr(BlockLen), mtInformation, [mbOK], 0); // Выводится 8192 !!!
 
Ответы:
28.07.2006 15:28:47Василий
Цитата из доки на CSP (*.chm):

KP_BLOCKLEN Длина блока шифрования, обрабатываемого провайдером. Задается величиной DWORD. В случае сессионного ключа если для ключа установлен режим блочного шифрованя (CRYPT_MODE_ЕСB, CRYPT_MODE_CBС), в параметре pbData устанавливается размер блока алгоритма ГОСТ 28147-89, если установлен режим поточного шифрования (CRYPT_MODE_OFB, CRYPT_MODE_CFB), в параметре pbData устанавливается значение оптимального с точки зрения скорости шифрования размера блока, принимаемого от приложения (в настоящее время 8Кбит). В случае ключевой пары в параметре pbData устанавливается число бит модуля q как для ключей на экспонециальной логике (ГОСТ 34.10-94), так и для ключей на базе эллиптической кривой (ГОСТ 34.10-2001).
28.07.2006 16:14:50Алексей
Спасибо за быстрый ответ.
Тем не менее вопрос остается:
Устанавливая параметр ключа KP_MODE равным CRYPT_MODE_CBC
CryptSetKeyParam(hKey, KP_MODE, @Mode, 0)
и получая после этого размер блока
CryptGetKeyParam(hKey, KP_BLOCKLEN, @BlockLen, BlockCSize, 0) снова выводится 8192.
Далее при шифрации видно, что длина шифротекста не увеличивается, чтобы быть кратной длине блока (8).
Может быть нужно установить еще какой-нибудь параметр?


28.07.2006 18:56:13Василий
Можно попросить вас проверить, что будет в случае создания нового сессионного ключа ф-ей CryptGenKey для алгоритмс 26142 - после изменения режима этого ключа на CBC что скажут про BLOCKLEN?
31.07.2006 11:54:50Алексей
После CryptGenKey(hProv, 26142, CRYPT_EXPORTABLE or CRYPT_CREATE_SALT, hKey) KP_BLOCKLEN все равно 8192.

В результате CryptEncrypt(hKey, 0, True, 0, pszText, inoutDataLength, 8) длина шифротекста возвращается равная 8 при длине открытого текста 5, хотя szText почему-то содержит 9 символов.
Последующее расшифрование приводит к ошибке NTE_BAD_DATA.

Привожу полный код процедуры на Delphi без обработчиков ошибок.
Цель процедуры: сгенерить ключ, IV, зашифровать и расшифровать данные, получив исходный текст.
В качестве обертки использую WinCrypt.pas.
Данная процедура замечательно работает для базового криптопровайдера Microsoft (симметричный алгоритм RC2).

function TestCryptEncrypt3;
var
cbPwd, cbData : DWORD;
hHash : HCRYPTHASH;
hKey : HCRYPTKEY;
hProv : HCRYPTPROV;
cont: PAnsiChar;
pszPwd : PByte;
szPwd : array [0..255] of char;
pszText : PByte;
szText : array [0..255] of char;
inoutDataLength : Cardinal;
IV : array [0..7] of char;
pIV : PByte;
IVLen : Cardinal;
Mode : cardinal;
BlockLen : cardinal;
BlockCSize : cardinal;
begin
// Запрос контекста
if not CryptAcquireContext(hProv, nil, nil, 75, CRYPT_VERIFYCONTEXT) then
begin
end;

if not CryptGenKey(hProv,
26142, // CALG_RC2,
CRYPT_EXPORTABLE,
hKey) then
begin
MessageDlg('Ошибка генерации ключа (' + ErrToStr(GetLastError) + ')', mtError, [mbOK], 0);
Exit;
end;

Mode := CRYPT_MODE_CBC;
if not CryptSetKeyParam(hKey, KP_MODE, @Mode, 0) then
begin
end;
if Mode = CRYPT_MODE_CBC then
MessageDlg('Режим: CRYPT_MODE_CBC', mtInformation, [mbOK], 0);

szText := 'Текст';
pszText := @szText[1];
inoutDataLength := 5;

pIV := @IV[0];
BlockCSize := 4;

if not CryptGetKeyParam(hKey, KP_BLOCKLEN, @BlockLen, BlockCSize, 0) then
begin
end;
MessageDlg('Длина блока: ' + IntToStr(BlockLen), mtInformation, [mbOK], 0);

//создание IV
IVLen := 8;
if not CryptGetKeyParam(hKey, KP_IV, pIV, IVLen, 0) then
begin
end;
MessageDlg('IV: ' + IV + '(' + IntToStr(IVLen) + ')', mtInformation, [mbOK], 0);

if not CryptGenRandom(hProv, IVLen, pIV) then
begin
end;

if not CryptSetKeyParam(hKey, KP_IV, pIV, 0) then
begin
end;
MessageDlg('IV: ' + IV + '(' + IntToStr(IVLen) + ')', mtInformation, [mbOK], 0);

// Зашифровать данные
if not CryptEncrypt(
hKey,
0,
True,
0,
pszText,
inoutDataLength,
8) then
begin
end;
MessageDlg('Длина шифротекста: ' + IntToStr(inoutDataLength) + '(' + szText + ')', mtInformation, [mbOK], 0);

// Расшифровать данные
if not CryptDecrypt(
hKey,
0,
True,
0,
pszText,
inoutDataLength) then
begin
end;
MessageDlg('Длина открытого текста: ' + IntToStr(inoutDataLength) + '(' + szText + ')', mtInformation, [mbOK], 0);

CryptDestroyKey(hKey);
CryptReleaseContext(hProv,0);

Result := True;
end;
31.07.2006 18:07:51Василий
Синхровектор IV меняется после процедуры зашифрования.
Для корректного расшифрования надо выставить его значение в то же самое ПЕРЕД CryptDecrypt.

Итак, схема такая:

1. Создание сессионного ключа. Если ключ создаётся ф-ей CryptGenKey, то синхровектор уже случайный.
2. Установка режима CBC.
3. Сохранение значения IV - CryptGetKeyParam(hKey, KP_IV,...)
4. Шифрование данных. Если шифруется 5 байт, то на выходе будет 8 байт (размер блока).
5. Восстановление ранее сохранённого значения IV - CryptSetKeyParam(hKey, KP_IV,...)
6. Расшифрование данных. Длина на входе - 8 байт, на выходе - 5 байт.
31.07.2006 18:24:37Василий
Вот работающий пример (на С) - только что проверил:

HCRYPTPROV hProv=0;
HCRYPTKEY hKey=0;
DWORD dwFlags=0;

DWORD dwDataLen,dwBufLen=100,ParamLen=4,Param=1,IVLen=8,i;
BYTE *buf,*bufIV;
CHAR *txt="Текст";



buf = (BYTE*)malloc(dwBufLen);
bufIV = (BYTE*)malloc(IVLen);








if(CryptAcquireContext(
&hProv,
NULL,
NULL,
75,
CRYPT_VERIFYCONTEXT))
printf("CSP verifycontext acquired.\n");
else
HandleError("Error during CryptAcquireContext.");






{
dwDataLen=strlen(txt);
memcpy(buf,txt,dwDataLen);

dwFlags=CRYPT_EXPORTABLE;// | CRYPT_CREATE_SALT;

if(CryptGenKey(
hProv,
CALG_G28147,
dwFlags,
&hKey))
{
printf("The session key has been created. \n");
}
else
{
HandleError("Error during CryptGenKey.");
}

Param = CRYPT_MODE_CBC;
if(!CryptSetKeyParam(hKey,KP_MODE,(LPBYTE)&Param,0))

if(!CryptGetKeyParam(hKey,KP_IV,bufIV,&IVLen,0))
HandleError("Error during CryptGetKeyParam.");



if(CryptEncrypt(hKey, 0, TRUE, 0, buf, &dwDataLen, dwBufLen))
{
printf("Data encrypted. \n");
}
else
{
HandleError("Error during CryptEncrypt.");
}

if(!CryptSetKeyParam(hKey,KP_IV,bufIV,0))
HandleError("Error during CryptSetKeyParam.");


if(CryptDecrypt(

hKey,
0,
TRUE,
0,
buf,
&dwDataLen
))
{
printf("Data decrypted. \n");
}
else
{
HandleError("Error during CryptDecrypt.");
}

if(strcmp(txt,(CHAR*)buf)==0)
printf("decryption - ok");
else
printf("Error in decryption");

if(!CryptDestroyKey(hKey))
printf("Error number %x.\n", GetLastError());


}


// Release the provider handle.

if(hProv)
if(!CryptReleaseContext(hProv, 0))
printf("Error number %x.\n", GetLastError());

return 0;
31.07.2006 18:31:15Василий
Опечатка небольшая:

После генерации ключа:

if(!CryptGetKeyParam(hKey,KP_BLOCKLEN,(LPBYTE)&Param,&ParamLen,0))
HandleError("Error during CryptGetKeyParam.");

//Здесь возвращается 8192

Param = CRYPT_MODE_CBC;
if(!CryptSetKeyParam(hKey,KP_MODE,(LPBYTE)&Param,0))
HandleError("Error during CryptGenKey for signkey.");


if(!CryptGetKeyParam(hKey,KP_BLOCKLEN,(LPBYTE)&Param,&ParamLen,0))
HandleError("Error during CryptGetKeyParam.");

//Здесь возвращается 64

Далее сохранение синхровектора и по тексту.