08.11.2005 16:03:01Ошибка при использовании Ответов: 8
Mick
Не получается воспроизвести пример кодирования сообщения с помощью Simplified Message функций из MSDN.
Процедура успешно выполняется до первого вызова CryptEncryptMessage, где получаю AV. Объявление прототипа функции проверил много раз, то же самое с другими типами.
Помогите пожалуйста.

Вот мой код:

const CLEAR_TEXT = ’Плохо живется без примеров на Паскале’;

procedure TfCryptDecryptFrame.EncryptData;
var
bResult : boolean;
hProv : HCRYPTPROV;
szContainer : string;
EncryptAlgSize : DWORD;
EncryptParamsSize : DWORD;
EncryptAlgorithm : CRYPT_ALGORITHM_IDENTIFIER;
EncryptParams : CRYPT_ENCRYPT_MESSAGE_PARA;
hStoreHandle : HCERTSTORE;
pRecipientCert : PCCERT_CONTEXT;
RecipientCertArray : array of PCCERT_CONTEXT;
cbContent : DWORD;
cbEncryptedBlob : DWORD;
pbEncryptedBlob : PByte;
pbContent : PByte;
begin
// ******* Контекст криптопровайдера ***********
if not CryptAcquireContext(@hProv,’Mick’,nil,75,0) then
begin
AddToMemo(’CryptAcquireContext failed’);
Exit;
end;
AddToMemo(’Контекст открыт’);
// ******** Хранилище *******************
hStoreHandle := nil;
hStoreHandle := CertOpenSystemStore(hProv,’MY’);
if hStoreHandle = nil then
begin
AddToMemo(’CertOpenSystemStore failed’);
Exit;
end;
AddToMemo(’Хранилище открыто’);
//******** Сертификат получателя ***********
pRecipientCert := GetRecipientCert(hStoreHandle);
if not Assigned(pRecipientCert) then
begin
AddToMemo(’GetRecipientCert failed’);
Exit;
end;
AddToMemo(’Получен сертификат получателя’);
//**************** Create a RecipientCertArray ********************
SetLength(RecipientCertArray,1);
RecipientCertArray[0] := pRecipientCert;
//*********** Initialize the algorithm identifier structure. ******
EncryptAlgSize := SizeOf(EncryptAlgorithm);
ZeroMemory(@EncryptAlgorithm,EncryptAlgSize);
EncryptAlgorithm.pszObjId := szOID_CP_GOST_28147;

//********** Initialize the CRYPT_ENCRYPT_MESSAGE_PARA structure. ***
EncryptParamsSize := SizeOf(EncryptParams);
ZeroMemory(@EncryptParams,EncryptParamsSize);

EncryptParams.cbSize := EncryptParamsSize;
EncryptParams.dwMsgEncodingType := MY_ENCODING_TYPE;
EncryptParams.hCryptProv := hProv;
EncryptParams.ContentEncryptionAlgorithm := EncryptAlgorithm;

//*********************************************
// Пробуем получить длину будущего сообщения
//*********************************************
cbContent := Length(CLEAR_TEXT);
cbEncryptedBlob := 0;
//При вызове CryptEncryptMessage получается Access Violation
if CryptEncryptMessage(@EncryptParams,
1,
RecipientCertArray,
PByte(PChar(CLEAR_TEXT)),
Length(CLEAR_TEXT),
nil,
@cbEncryptedBlob) then
begin
AddToMemo(’cbEncryptedBlob = ’ + IntToStr(cbEncryptedBlob));
end
else
begin
AddToMemo(’CryptEncryptMessage(1) Failed’);
Exit;
end;
//*********************************************
......

end;
 
Ответы:
08.11.2005 18:01:58Mick
Опытным путем удалось установить, что AV внутри crypt32.dll возникает если при первом вызове CryptEncryptMessage в качестве выходного буфера передать явный nil.

Если создать переменную Dummy : DWORD и передать указатель на нее в качестве буфера, то AV не возникает.
GetLastError возвращает 0xC0000005L(STATUS_ACCESS_VIOLATION)
Если Dummy предварительно проинициализировать нулем, то вроде наступает счастье и GetLastError возвращает 234L ERROR_MORE_DATA

НО!!!

Но в последнем параметре DWORD* pcbEncryptedBlob у меня ноль. (Сама Dummy при этом имеет значение 4609375 в то время как исходных данных меньше ста байт)

Подскажите, какие еще грабли я не обошел?


09.11.2005 6:21:00Альт
Привет.... вот глянь как у меня сейчас...

function CCryptoLib_class.Encrypt( const pICertificate: ICertificate; const wsText: WideString) : WideString;
var
ll : DWORD;
sText : String;
bResult : Boolean;
pchEncryptText : PChar;
AMessages : PBYTE;
AMessagesLength : DWORD;
pCertificateContext : PCCERT_CONTEXT;
CryptEncryptMessagePara : CRYPT_ENCRYPT_MESSAGE_PARA;
begin
Result := ’’;

if ( Not Assigned( pICertificate )) then
exit;
if ( wsText = ’’ ) then
exit;

FillChar( CryptEncryptMessagePara, SizeOf( CRYPT_ENCRYPT_MESSAGE_PARA ), #0 );
CryptEncryptMessagePara.cbSize := SizeOf( CRYPT_ENCRYPT_MESSAGE_PARA );
CryptEncryptMessagePara.dwMsgEncodingType := Ord( GNICET_PKCS7_ASN ) + Ord( GNICET_X509_ASN );
CryptEncryptMessagePara.ContentEncryptionAlgorithm.pszObjId := szOID_CP_GOST_28147;

sText := wsText;
AMessages := @sText[ 1 ];
AMessagesLength := Length( sText );

pCertificateContext := PCCERT_CONTEXT( pICertificate.Header );

bResult := CryptEncryptMessage( @CryptEncryptMessagePara, 1, @pCertificateContext, AMessages, AMessagesLength, nil, @ll );
if ( bResult ) then
begin
pchEncryptText := StrAlloc( ll );
try
bResult := CryptEncryptMessage( @CryptEncryptMessagePara, 1, @pCertificateContext, AMessages, AMessagesLength, PByte( pchEncryptText ), @ll );
if ( bResult ) then
begin
SetString( sText, pchEncryptText, ll );
Result := sText;
end;
finally
StrDispose( pchEncryptText );
end;
end;
if ( Not bResult ) then
Raise Exception.Create( ’CCryptoLib_class.Encrypt’ + #10#13 + GetSysErrorString( GetLastError )) at @CCryptoLib_class.Encrypt;
end;

Сравни со своим... и еще проверяй всегда, что структуры и описания фунций совпадают с wincrypt.h?
09.11.2005 11:11:24Mick
Привет! Спасибо за отклик.
Вот какие отличия я нашел:
1. Наверное несущественно, но:
CryptEncryptMessagePara.hCryptProv - у тебя не инициализируется, у меня инициализируется контекстом 75 типа

2. Третий параметр CryptEncryptMessage:
у меня : RecipientCertArray : array of PCCERT_CONTEXT;
у тебя : @pCertificateContext (указатель на единичный экземпляр контекста сертификата)

Когда я пытаюсь делать то же самое, компилятор ругается на неверный тип аргумента (хочу типа массив, а ты даешь указатель)

Наверное у нас разные wincrypt2.pas? (Я свой брал на jedi)
Вот мой прототип для CryptEncryptMessage:

function CryptEncryptMessage(pEncryptPara : PCRYPT_ENCRYPT_MESSAGE_PARA;
cRecipientCert : DWORD;
rgpRecipientCert : array of PCCERT_CONTEXT;
const pbToBeEncrypted : PBYTE;
cbToBeEncrypted : DWORD;
pbEncryptedBlob : PBYTE;
pcbEncryptedBlob : PDWORD) : BOOL; stdcall;



В итоге у меня все равно AV.

09.11.2005 12:43:09Альт
По первому пункту, можно открыть провайдера через CryptAcquireContext и уже его хендл уже присваивать CryptEncryptMessagePara.hCryptProv, но у меня работает и так и эдак, потому решил не заморачиваться.

По второму я как раз и предупредил про необходимость сравнивать прототипы с wincrypt.h, у меня тоже wincrypt2.pas от jedi, в нем очень много неточностей и описок подобно твоей. На самом деле ее прототип:

function CryptEncryptMessage(pEncryptPara :PCRYPT_ENCRYPT_MESSAGE_PARA;
cRecipientCert :DWORD;
rgpRecipientCert : PCCERT_CONTEXT; //array of PCCERT_CONTEXT;
const pbToBeEncrypted :PBYTE;
cbToBeEncrypted :DWORD;
pbEncryptedBlob :PBYTE;
pcbEncryptedBlob :PDWORD):BOOL ; stdcall;

Добавил в объявлениях:
arCertContext : TMemoryStream;

Исправил в реализации:
arCertContext := TMemoryStream.Create;
arCertContext.SetSize( arCertContext.Size + SizeOf( Pointer ));
TPointerList( arCertContext.Memory^ )[ 0 ] := PCCERT_CONTEXT( pICertificate.Header );
arCertContext.SetSize( arCertContext.Size + SizeOf( Pointer ));
TPointerList( arCertContext.Memory^ )[ 1 ] := PCCERT_CONTEXT( pICertificate.Header );
// pCertificateContext := PCCERT_CONTEXT( pICertificate.Header );

// bResult := CryptEncryptMessage( @CryptEncryptMessagePara, 1, @pCertificateContext, AMessages, AMessagesLength, nil, @ll );
bResult := CryptEncryptMessage( @CryptEncryptMessagePara, 2, arCertContext.Memory, AMessages, AMessagesLength, nil, @ll );
if ( bResult ) then
begin
pchEncryptText := StrAlloc( ll );
try
bResult := CryptEncryptMessage( @CryptEncryptMessagePara, 2, arCertContext.Memory, AMessages, AMessagesLength, nil, @ll );
// bResult := CryptEncryptMessage( @CryptEncryptMessagePara, 1, @pCertificateContext, AMessages, AMessagesLength, PByte( pchEncryptText ), @ll );
if ( bResult ) then

Ну вот… снова шифрует… правда, как такое расшифровывается я даже примерно не представляю ;(
09.11.2005 16:00:49mick
В общем прокатило с новым прототипом. Сообщение криптуется. Спасибо еще раз.
Сейчас мучаю обратную операцию.
Пока не ясно каким образом CryptDecryptMessage должна догадаться какой контейнер использовать при расшифровании.
Видимо по сертификату, который ищется в массиве контекстов хранилищ.
У меня первый вызов проходит нормально, получаю длину будущих открытых данных.
Второй вызов обламывается, но getlasterror возвращает круглый ноль.
Видимо сертификат получателя должен иметь привязку к ключевому контейнеру?
09.11.2005 16:19:08Альт
Cовершенно верно. Именно с этим и воюю в другом топике
09.11.2005 16:23:26Mick
Я вот какой камент нашел к структуре

CRYPT_DECRYPT_MESSAGE_PARA

Only certificate contexts in the store with one of the following properties, CERT_KEY_PROV_INFO_PROP_ID, or CERT_KEY_CONTEXT_PROP_ID can be used. These properties specify the location of a needed private exchange key.

09.11.2005 16:35:07Альт
Да со связыванием контекста сертификата и контейнера проблем нет. Мне сохранять его в виндовое хранилище не хочется. Пользователь удалить его ручками сможет. А спрятать связанный контекст не получается.