2016-04-21 2 views
5

Я пытаюсь выполнить подписку и проверку SHA256 в Delphi с помощью OpenSSL libeay32.dll. Therefor в качестве первого шага я создал RSA 2048-битный ключ пары с помощью следующей команды OpenSSL:Проверка подписи SHA256 с OpenSSL в Delphi завершается

openssl genrsa -out private.pem 2048 
openssl rsa -in private.pem -outform PEM -pubout -out public.pem 

Это далеко, что легко. Следующий шаг я создавал функцию, которая была в состоянии прочитать открытые и закрытые ключи из файлов PEM:

function TSignSHA256.ReadKeyFile(aFileName : String; aType : TKeyFileType) : pEVP_PKEY; 
var locFile : RawByteString; 
    locBIO : pBIO; 
begin 
    locFile := UTF8Encode(aFileName); 

    locBIO := BIO_new(BIO_s_file()); 

    try 
    BIO_read_filename(locBIO, PAnsiChar(locFile)); 

    result := NIL; 
    case aType of 
     kfPrivate : result := PEM_read_bio_PrivateKey(locBIO, result, nil, nil); 
     kfPublic : result := PEM_read_bio_PUBKEY(locBIO, result, nil, nil); 
    end; 
    finally 
    BIO_free(locBIO); 
    end; 
end; 

Это, казалось, работать. Таким образом, я осуществил некоторые процедуры знак:

procedure TSignSHA256.Sign; 
var locData : RawByteString; 
    locKey : pEVP_PKEY; 
    locCtx : pEVP_MD_CTX; 
    locSHA256 : pEVP_MD; 
    locSize : Cardinal; 
    locStream : TBytesStream; 
begin 
    locKey := ReadKeyFile('private.pem', kfPrivate); 
    locData := ReadMessage('message.txt'); 

    locCtx := EVP_MD_CTX_create; 
    try 
    locSHA256 := EVP_sha256(); 

    EVP_DigestSignInit(locCtx, NIL, locSHA256, NIL, locKey); 
    EVP_DigestSignUpdate(locCtx, PAnsiChar(locData), Length(locData)); 
    EVP_DigestSignFinal(locCtx, NIL, locSize); 

    locStream := TBytesStream.Create; 
    try 
     locStream.SetSize(locSize); 
     EVP_DigestSignFinal(locCtx, PAnsiChar(locStream.Memory), locSize); 
     WriteSignature('message.sig', locStream.Bytes, locSize); 
    finally 
     FreeAndNIL(locStream); 
    end; 
    finally 
    EVP_MD_CTX_destroy(locCtx); 
    end; 
end; 

Как вы можете видеть, что процедура читает файл с именем message.txt, вычисление подписи и хранения, что сиговых в message.sig. Если я запускаю следующую команду OpenSSL результат является Проверенно OK:

openssl dgst -sha256 -verify public.pem -signature message.sig message.txt 

Таким образом, кажется, что моя процедура подписания также работает правильно. Так что я, наконец, реализовал процедуру верификации:

function TSignSHA256.Verify : Boolean; 
var locData : RawByteString; 
    locSig : TArray<Byte>; 
    locKey : pEVP_PKEY; 
    locCtx : pEVP_MD_CTX; 
    locSHA256 : pEVP_MD; 
    locSize : Cardinal; 
    locStream : TBytesStream; 
begin 
    locKey := ReadKeyFile('public.pem', kfPublic); 
    locData := ReadMessage('message.txt'); 
    locSig := ReadSignature('message.sig'); 
    locSize := Length(locSig); 

    locCtx := EVP_MD_CTX_create; 
    try 
    locSHA256 := EVP_sha256(); 

    EVP_DigestVerifyInit(locCtx, NIL, EVP_sha256(), NIL, locKey); //Returns 1 
    EVP_DigestVerifyUpdate(locCtx, PAnsiChar(locData), Length(locData)); //Returns 1 

    locStream := TBytesStream.Create(locSig); 
    try 
     result := (EVP_DigestVerifyFinal(locCtx, PAnsiChar(locStream.Memory), locSize) = 1); //Returns false! WHY??? 
    finally 
     FreeAndNIL(locStream); 
    end; 
    finally 
    EVP_MD_CTX_destroy(locCtx); 
    end; 
end; 

Как вы можете видеть, я реализовал эту процедуру точно так же, как и я осуществить процедуру подписания. К сожалению, результатом этого является false. Код ошибки, возвращаемый OpenSSL является

error04091077:lib(4):func(145):reason:(119) 

Это приводит к ошибке в Lib RSA, функция int_rsa_verify, причина Неверная длина подписи. Я искал Google, но я не нашел полезной информации об этой ошибке. Я также попытался понять источники OpenSSL, но я не настолько глубоко в C, и кажется, что это может занять много времени, пока я не смогу понять это.

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

Почему проверка подписи не удалась?

+0

Скучаешь обработки ошибок, начните с проверки, если '' EVP_DigestVerifyInit' и EVP_DigestVerifyUpdate' успеха (проверить возвращаемые значения) – Remko

+2

См [ EVP Signing and Verification] (http://wiki.openssl.org/index.php/EVP_Signing_and_Verifying) в вики OpenSSL. Это дает вам примеры, которые работают из коробки. – jww

+0

@Remko: Я просто оставил обработку ошибок для удобочитаемости. EVP_DigestVerifyInit и EVP_DigistVerifyUpdate возвращают 1, что означает успех. Я редактировал мой код, чтобы сделать это более понятным. –

ответ

1

Подпись не является текстовой подписью. Он состоит из массива байтов, для которого байты могут иметь любое значение. Вы преобразовываете этот массив байтов непосредственно в строки ANSI и из них. Это произойдет, если массив содержит значения вне диапазона ANSI (что бы это ни было, я бы предположил ASCII).

Вам необходимо обработать подпись как двоичные данные. Вы можете использовать базовый 64-кодек, если вам нужно рассматривать его как строку (содержащую текст).

+0

Изменение вызова функции из 'result: = (EVP_DigestVerifyFinal (locCtx, PAnsiChar (locStream.Memory), locSize) = 1);' to 'result: = (EVP_DigestVerifyFinal (locCtx, @ locStream.Memory, locSize) = 1); 'не имеет значения. –

+0

Не могли бы вы напрямую сравнить двоичные значения подписи? Я не вижу код, который находится между ними. Вы можете сделать то же самое с бинарным кодированным текстом и модулем открытого и закрытого ключа (которые должны быть одинаковыми). –

+0

Ну, я точно не знаю, что происходит между ними. EVP_DigestVerifyFinal - это прямой вызов в libeay32.dll. Связывание выполняется с помощью функции EVP_DigestVerifyFinal (ctx: pEVP_MD_CTX; const d: PAnsiChar; var cnt: Cardinal): Integer; cdecl; 'и' function EVP_DigestVerifyFinal; external 'libeay32.dll'; 'в части реализации. Я также попытался использовать функцию EVP_DigestVerifyFinal (ctx: pEVP_MD_CTX; const d: Pointer; var cnt: Cardinal): Integer; cdecl; 'но это не имело значения. –

3

ОК, я нашел решение. На самом деле мне пришлось иметь дело с двумя ошибками. Первая ошибка заключалась в том, что я неправильно передал подпись в EVP_DigestVerifyFinal. Вот что сказал Маартен Бодвис в своем ответе, и я приму это в ответ на мой вопрос.

Вторая проблема была в моем определении точки входа в DLL.Я объявил третий параметр EVP_DigistVerifyFinal в качестве параметра var. Вероятно, копия & прошлой ошибкой в ​​качестве третьего параметра EVP_DigistSignFinal IS является параметром var.

Для всех, кто когда-либо будет делать то же самое, я размещаю свое решение здесь. Он был вдохновлен чтением EVP Signing and Verifying, DelphiOpenSSL и источниками OpenSSL (главным образом dgst.c). Код был реализован и протестирован с помощью Delphi XE2.

Помните, что мой код не обрабатывает ошибки и не заботится о освобождении памяти. Это означает, что код не готов к производству, и вы должны использовать его с осторожностью!

Ввоз блок:

unit uOpenSSLCrypt; 

interface 

type 
    pBIO = Pointer; 
    pBIO_METHOD = Pointer; 

    pEVP_MD_CTX = Pointer; 
    pEVP_MD = Pointer; 

    pEVP_PKEY_CTX = Pointer; 
    pEVP_PKEY = Pointer; 

    ENGINE = Pointer; 

    TPWCallbackFunction = function(buffer : PAnsiChar; length : Integer; verify : Integer; data : Pointer) : Integer; cdecl; 

    //Error functions 
    function ERR_get_error : Cardinal; cdecl; 
    function ERR_error_string(e : Cardinal; buf : PAnsiChar) : PAnsiChar; cdecl; 

    function ERR_GetErrorMessage : String; 

    //BIO functions 
    function BIO_new(_type : pBIO_METHOD) : pBIO; cdecl; 
    function BIO_new_file(const aFileName : PAnsiChar; const aMode : PAnsiChar) : pBIO; cdecl; 
    function BIO_free(a: pBIO): integer; cdecl; 
    function BIO_s_file : pBIO_METHOD; cdecl; 
    function BIO_f_md : pBIO_METHOD; cdecl; 
    function BIO_ctrl(bp : pBIO; cmd : Integer; larg : Longint; parg : Pointer) : Longint; cdecl; 
    function BIO_read(b : pBIO; buf : Pointer; len : Integer) : integer; cdecl; 
    function BIO_get_md_ctx(bp: pBIO; mdcp: Pointer): Longint; 
    function BIO_read_filename(bp : pBIO; filename : PAnsiChar) : Integer; 

    function PEM_read_bio_PrivateKey(bp : pBIO; x : pEVP_PKEY; cb : TPWCallbackFunction; u : pointer) : pEVP_PKEY; cdecl; 
    function PEM_read_bio_PUBKEY(bp : pBIO; x : pEVP_PKEY; cb : TPWCallbackFunction; u : Pointer) : pEVP_PKEY; cdecl; 

    //EVP functions 
    function EVP_MD_CTX_create() : pEVP_MD_CTX; cdecl; 
    procedure EVP_MD_CTX_destroy(ctx : pEVP_MD_CTX); cdecl; 
    function EVP_sha256() : pEVP_MD; cdecl; 

    function EVP_PKEY_size(key: pEVP_PKEY): integer; cdecl; 
    function EVP_DigestSignInit(aCtx : pEVP_MD_CTX; aPCtx : pEVP_PKEY_CTX; aType : pEVP_MD; aEngine : ENGINE; aKey : pEVP_PKEY ) : Integer; cdecl; 
    function EVP_DigestSignUpdate(ctx : pEVP_MD_CTX; const d : Pointer; cnt : Cardinal) : Integer; cdecl; 
    function EVP_DigestSignFinal(ctx : pEVP_MD_CTX; const d : PByte; var cnt : Cardinal) : Integer; cdecl; 

    function EVP_DigestVerifyInit(aCtx : pEVP_MD_CTX; aPCtx : pEVP_PKEY_CTX; aType : pEVP_MD; aEngine : ENGINE; aKey : pEVP_PKEY ) : Integer; cdecl; 
    function EVP_DigestVerifyUpdate(ctx : pEVP_MD_CTX; const d : Pointer; cnt : Cardinal) : Integer; cdecl; 
    function EVP_DigestVerifyFinal(ctx : pEVP_MD_CTX; const d : PByte; cnt : Cardinal) : Integer; cdecl; 

    function CRYPTO_malloc(aLength : LongInt; const f : PAnsiChar; aLine : Integer) : Pointer; cdecl; 
    procedure CRYPTO_free(str : Pointer); cdecl; 

const BIO_C_SET_FILENAME = 108; 
     BIO_C_GET_MD_CTX = 120; 

     BIO_CLOSE = $01; 
     BIO_FP_READ = $02; 

implementation 

uses System.SysUtils, Windows; 

const LIBEAY_DLL_NAME = 'libeay32.dll'; 

function ERR_get_error : Cardinal; external LIBEAY_DLL_NAME; 
function ERR_error_string;   external LIBEAY_DLL_NAME; 

function ERR_GetErrorMessage : String; 
var locErrMsg: array [0..160] of Char; 
begin 
    ERR_error_string(ERR_get_error, @locErrMsg); 
    result := String(StrPas(PAnsiChar(@locErrMsg))); 
end; 

function BIO_new;     external LIBEAY_DLL_NAME; 
function BIO_new_file;   external LIBEAY_DLL_NAME; 
function BIO_free;    external LIBEAY_DLL_NAME; 
function BIO_ctrl;    external LIBEAY_DLL_NAME; 
function BIO_s_file;    external LIBEAY_DLL_NAME; 
function BIO_f_md;    external LIBEAY_DLL_NAME; 
function BIO_read;    external LIBEAY_DLL_NAME; 

function BIO_get_md_ctx(bp : pBIO; mdcp : Pointer) : Longint; 
begin 
    result := BIO_ctrl(bp, BIO_C_GET_MD_CTX, 0, mdcp); 
end; 

function BIO_read_filename(bp : pBIO; filename : PAnsiChar) : Integer; 
begin 
    result := BIO_ctrl(bp, BIO_C_SET_FILENAME, BIO_CLOSE or BIO_FP_READ, filename); 
end; 

function PEM_read_bio_PrivateKey; external LIBEAY_DLL_NAME; 
function PEM_read_bio_PUBKEY;  external LIBEAY_DLL_NAME; 

function EVP_MD_CTX_create; external LIBEAY_DLL_NAME; 
procedure EVP_MD_CTX_destroy; external LIBEAY_DLL_NAME; 
function EVP_sha256;   external LIBEAY_DLL_NAME; 

function EVP_PKEY_size;   external LIBEAY_DLL_NAME; 
function EVP_DigestSignInit; external LIBEAY_DLL_NAME; 
function EVP_DigestSignUpdate; external LIBEAY_DLL_NAME name 'EVP_DigestUpdate'; 
function EVP_DigestSignFinal; external LIBEAY_DLL_NAME; 

function EVP_DigestVerifyInit; external LIBEAY_DLL_NAME; 
function EVP_DigestVerifyUpdate; external LIBEAY_DLL_NAME name 'EVP_DigestUpdate'; 
function EVP_DigestVerifyFinal; external LIBEAY_DLL_NAME; 

function CRYPTO_malloc; external LIBEAY_DLL_NAME; 
procedure CRYPTO_free; external LIBEAY_DLL_NAME; 

end. 

Реализация:

unit uSignSHA256; 

interface 

uses uOpenSSLCrypt; 

type 
    TKeyFileType = (kfPrivate, kfPublic); 

    TSignSHA256 = class(TObject) 
    private 
    function ReadKeyFile(aFileName : String; aType : TKeyFileType) : pEVP_PKEY; 
    function ReadMessage(aName : String) : RawByteString; 
    function ReadSignature(aName : String; var aLength : Cardinal) : Pointer; 
    procedure FreeSignature(aSig : Pointer); 

    procedure WriteSignature(aName : String; aSignature : TArray<Byte>; aLength : Integer); 

    public 
    constructor Create; 
    destructor Destroy; override; 

    procedure Sign(aKeyFile : String; aMsgFile : String; aSigFile : String); 
    function Verify(aKeyFile : String; aMsgFile : String; aSigFile : String) : Boolean; 
    end; 

implementation 

uses System.Classes, System.SysUtils; 

{ TSignSHA256 } 

constructor TSignSHA256.Create; 
begin 

end; 

destructor TSignSHA256.Destroy; 
begin 

    inherited; 
end; 

procedure TSignSHA256.FreeSignature(aSig : Pointer); 
begin 
    CRYPTO_free(aSig); 
end; 

function TSignSHA256.ReadKeyFile(aFileName : String; aType : TKeyFileType) : pEVP_PKEY; 
var locFile : RawByteString; 
    locBIO : pBIO; 
begin 
    locFile := UTF8Encode(aFileName); 

    locBIO := BIO_new(BIO_s_file()); 

    try 
    BIO_read_filename(locBIO, PAnsiChar(locFile)); 

    result := NIL; 
    case aType of 
     kfPrivate : result := PEM_read_bio_PrivateKey(locBIO, nil, nil, nil); 
     kfPublic : result := PEM_read_bio_PUBKEY(locBIO, nil, nil, nil); 
    end; 
    finally 
    BIO_free(locBIO); 
    end; 
end; 

function TSignSHA256.ReadMessage(aName : String) : RawByteString; 
var locFileStream : TFileStream; 
    locSize  : Cardinal; 
    locBytes  : TArray<Byte>; 
    locText  : String; 
begin 
    locFileStream := TFileStream.Create(aName, fmOpenRead); 
    try 
    locSize := locFileStream.Size; 

    SetLength(locBytes, locSize); 
    locFileStream.Read(locBytes[0], locSize); 
    finally 
    FreeAndNIL(locFileStream); 
    end; 

    SetString(locText, PAnsiChar(locBytes), locSize); 
    result := UTF8Encode(locText); 
end; 

function TSignSHA256.ReadSignature(aName : String; var aLength : Cardinal) : Pointer; 
var locSigBio : pBIO; 
    locFile : RawByteString; 
    locMode : RawByteString; 
begin 
    locFile := UTF8Encode(aName); 
    locMode := UTF8Encode('rb'); 

    locSigBio := BIO_new_file(PAnsiChar(locFile), PAnsiChar(locMode)); 
    try 
    result := CRYPTO_malloc(aLength, NIL, 0); 
    aLength := BIO_read(locSigBio, result, aLength); 
    finally 
    BIO_free(locSigBio); 
    end; 
end; 

procedure TSignSHA256.Sign(aKeyFile : String; aMsgFile : String; aSigFile : String); 
var locData : RawByteString; 
    locKey : pEVP_PKEY; 
    locCtx : pEVP_MD_CTX; 
    locSHA256 : pEVP_MD; 
    locSize : Cardinal; 
    locStream : TBytesStream; 
begin 
    locKey := ReadKeyFile(aKeyFile, kfPrivate); 
    locData := ReadMessage(aMsgFile); 

    locCtx := EVP_MD_CTX_create; 
    try 
    locSHA256 := EVP_sha256(); 

    EVP_DigestSignInit(locCtx, NIL, locSHA256, NIL, locKey); 
    EVP_DigestSignUpdate(locCtx, PAnsiChar(locData), Length(locData)); 
    EVP_DigestSignFinal(locCtx, NIL, locSize); 

    locStream := TBytesStream.Create; 
    try 
     locStream.SetSize(locSize); 
     EVP_DigestSignFinal(locCtx, PByte(locStream.Memory), locSize); 
     WriteSignature(aSigFile, locStream.Bytes, locSize); 
    finally 
     FreeAndNIL(locStream); 
    end; 
    finally 
    EVP_MD_CTX_destroy(locCtx); 
    end; 
end; 

function TSignSHA256.Verify(aKeyFile : String; aMsgFile : String; aSigFile : String) : Boolean; 
var locData : RawByteString; 
    locSig : Pointer; 
    locKey : pEVP_PKEY; 
    locBio : pBIO; 
    locCtx : pEVP_MD_CTX; 
    locKeyCtx : pEVP_PKEY_CTX; 
    locSHA256 : pEVP_MD; 
    locSize : Cardinal; 
    locStream : TBytesStream; 
begin 
    locKey := ReadKeyFile(aKeyFile, kfPublic); 
    locData := ReadMessage(aMsgFile); 
    locSize := EVP_PKEY_size(locKey); 

    locBio := BIO_new(BIO_f_md); 
    try 
    BIO_get_md_ctx(locBio, @locCtx); 
    locSHA256 := EVP_sha256(); 

    EVP_DigestVerifyInit(locCtx, NIL, locSHA256, NIL, locKey); 
    EVP_DigestVerifyUpdate(locCtx, PAnsiChar(locData), Length(locData)); 

    try 
     locSig := ReadSignature(aSigFile, locSize); 
     result := (EVP_DigestVerifyFinal(locCtx, PByte(locSig), locSize) = 1); 
    finally 
     FreeSignature(locSig); 
    end; 
    finally 
    BIO_free(locBio); 
    end; 
end; 

procedure TSignSHA256.WriteSignature(aName : String; aSignature : TArray<Byte>; aLength : Integer); 
var locFileStream : TFileStream; 
begin 
    locFileStream := TFileStream.Create(aName, fmCreate); 
    try 
    locFileStream.Write(aSignature[0], aLength); 
    finally 
    FreeAndNIL(locFileStream); 
    end; 
end; 

end. 
Смежные вопросы