2015-06-21 4 views
2

Я сгенерировал открытый ключ с использованием OpenSSL со следующими командами для создания 1024-битного ключа RSA, а затем экспортировал открытый ключ, предоставив мне открытый ключ в файле .pem.Как загрузить открытый открытый ключ открытого ключа RSA1024 OpenSSL с помощью CryptoAPI?

> openssl genrsa -out private_key.pem 1024 
> openssl rsa -pubout -in private_key.pem -out public_key.pem 

Я с помощью закрытого ключа, чтобы подписать какие-то данные в PHP, и хотите, чтобы проверить подпись в ОС Windows с помощью CryptoAPI. Я бы предпочел не связываться с портом Windows OpenSSL, если это возможно.

Открытый ключ в текстовом формате. Как я могу успешно загрузить эту версию и использовать ее для проверки подписи?

Текущий подход

Я пытаюсь использовать CryptImportKey импортировать публичный ключ блоб с форматом:

  • PUBLICKEYSTRUC
  • Размер следующего массива байтов, DWORD
  • Key как массив байтов (узкая/ANSI строка)

в упакованном виде cture.

Ключевой массив байтов - это просто файл открытого ключа .pem, включая операторы BEGIN и END, в виде строки ANSI (не Unicode). Обратите внимание, что я использую Unicode-приложение, но API-интерфейсы Crypto, похоже, не входят в версии ANSI/Unicode. (У них?)

Когда я это делаю, CryptImportKey терпит неудачу с GetLastError, имеющим значение 0x80090005, NTE_BAD_DATA. Я не уверен, почему это ... поэтому вопрос :)

Код

Ключевая структура общественного блоб определяется как:

TPublicKeyBlob = packed record 
    strict private 
    FHeader : BLOBHEADER; 
    FKeyLength : DWORD; 
    FKey : array[0..4095] of Byte; 
    public 
    procedure Init(const KeyStr : string); 
    function Size : DWORD; 
    end; 

и методы есть:

procedure TPublicKeyBlob.Init(const KeyStr: string); 
var 
    KeyAnsi : AnsiString; 
begin 
    KeyAnsi := AnsiString(KeyStr); 
    if Length(KeyAnsi) > Length(FKey) then 
    raise Exception.Create('Key too long'); 

    FHeader.bType := PLAINTEXTKEYBLOB; 
    FHeader.bVersion := CUR_BLOB_VERSION; 
    FHeader.reserved := 0; 
    FHeader.aiKeyAlg := CALG_RSA_KEYX; 

    FKeyLength := Length(KeyAnsi) * sizeof(KeyAnsi[1]); 
    assert(sizeof(KeyAnsi[1]) = 1); // Should be single byte strings! 

    Move(KeyAnsi[1], FKey[0], Length(KeyAnsi) * sizeof(KeyAnsi[1])); 
end; 

function TPublicKeyBlob.Size: DWORD; 
begin 
    // Return only the used size - not all the byte array will be used 
    Result := SizeOf(FHeader) + Sizeof(FKeyLength) + FKeyLength; 
end; 

Это должно создать макет в памяти, соответствующий примеру в Microsoft Importing a Plaintext Key example. Может быть. Действительно ли CALG_RSA_KEYX подходит для RSA 1024-битного ключа (или, скорее, сгенерированного открытого ключа из одного)? Правильно ли используется значение Move()? (Должен быть аналогом memcpy, если вы программист C, но параметры являются источником, dest, size и строками 1-indexed, но массив индексируется 0.) Должен ли Size() возвращать размер всей структуры , или просто ключевой массив?

Параметр - это открытый ключ (внизу) в виде строки.

Это затем используется следующим образом:

function TLicenseInfo.VerifySignature: Boolean; 
const 
    PublicKeyStr = '-----BEGIN PUBLIC KEY-----' + 
    'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCeyh3x8qbGgq+ZlXm4erhjFMKY' + 
    'RGhAyFc8DUupkaLlSSzXDcHHC27VtuxAKtAVvm97OGJMbdbtAUy6rmVH0GSQmP+0' + 
    'Mct+7ncQRfpXJ4kAYg3gpv4dSsBl83rWDuQ06QDPjIT7eMdNMuUTm11GIYFnvyv4' + 
    'C9Vn92hBC2QeRRlslwIDAQAB' + 
    '-----END PUBLIC KEY-----'; 
var 
    hCryptProv, hHash: wcrypt2.HCRYPTPROV; 
    hKey: Cardinal; 
    PublicKeyBlob : TPublicKeyBlob; 
begin 
    Result := False; 

    PublicKeyBlob.Init(PublicKeyStr); 

    if not CryptAcquireContext(@hCryptProv, 'Test', nil, PROV_RSA_FULL, 0) then 
    if not CryptAcquireContext(@hCryptProv, 'Test', nil, PROV_RSA_FULL, CRYPT_NEWKEYSET) then 
     Exit; 

    try 
    if CryptImportKey(hCryptProv, @PublicKeyBlob, PublicKeyBlob.Size, 0, 0, @hKey) then 
    //^this is the line that fails 
    begin 
     // ... check the signature. 
    end; 
    finally 
    CryptReleaseContext(hCryptProv, 0); 
    end; 
end; 

Выделенная линия вызова CryptImportKey терпит неудачу и возвращает GetLastError0x80090005, NTE_BAD_DATA.

Я пробовал несколько вариантов - некоторые варианты алгоритма, экспериментируя с размером, удаляя части BEGIN и END строки ключа и т. Д.Но я не знаком с API Crypto, и я уверен, что это что-то очевидно для других :)

+3

Попробуйте Base64-декодирование ключа первым (открытый текст в этом контексте, я думаю, означает, что он не зашифрован, а не то, что он фактически находится в формате ASCII) –

+0

@JonathanPotter Ах ... Я думаю, что это один из тех, «о, конечно. .. "моменты. Гах. Я попробую его и обновить :) –

+0

Декодирование приводит к тому же коду ошибки в том же месте. Примечание. Мне нужно было отделить части BEGIN/END от ключа, чтобы его декодировать, и я не уверен, что это правильно - я думаю, что ключ открытого текста требует, чтобы они были частью его формата. –

ответ

1

Я ответил на подобный вопрос много лет назад (на 2012 год): https://stackoverflow.com/a/10827239/707093

В принципе, вы должны используйте CryptStringToBinary, чтобы получить декодирование значения BASE64, а затем используйте CryptDecodeObjectEx и CryptImportKey. Образец C, который показывает, как это сделать, находится в https://www.idrix.fr/Root/Samples/capi_pem.cpp. Переводить это на delphi не сложно.

Надеюсь, это поможет.