RESOLVED - OpenSSL - цифровая подпись, созданная с помощью кода, не соответствует подписи, сгенерированной в командной строке

РЕДАКТИРОВАТЬ - РАЗРЕШЕНО

Как обычно, проблема в моей неспособности прочитать спецификацию. Я передавал хэшированное значение SignedInfo EVP_DigestSign* функции, а не фактический текст, поэтому я переваривал дайджест и генерировал подпись из этого. Так что, конечно, это не сработало.

ОРИГИНАЛ

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

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

Я создал MessageSigner класс для управления делами libcrypto. Закрытый ключ и сертификат являются атрибутами класса:

class MessageSigner 
{
  ...
  private:

  EVP_PKEY *private_key;
  X509     *certificate;
};

Я инициализирую их NULL когда экземпляр создан:

MessageSigner::MessageSigner() : 
  private_key( NULL ),
  certificate( NULL )
{
}

Я загружаю закрытый ключ следующим образом:

void MessageSigner::addKeyFile( const std::string& filename, const std::string& passphrase )
{
  FILE *fp = ::fopen( keyfile.c_str(), "r" );
  if ( !fp )
    // throw exception

  private_key = PEM_read_PrivateKey( fp, NULL, NULL, passphrase.c_str() );
  ::fclose( fp );

  if ( !private_key )
    // throw exception
}

И я генерирую подпись как

void MessageSigner::signMessage( const std::vector< unsigned char >& msg, std::vector< unsigned char >& signature )
{
  unsigned char *msgbuf = new unsigned char [msg.size()];
  std::copy( msg.begin(), msg.end(), msgbuf );

  unsigned char *sigbuf;
  size_t sigbuf_length;

  EVP_MD_CTX *ctx = EVP_MT_CTX_create();
  if ( !ctx )
    // throw exception

  if ( EVP_DigestSignInit( ctx, NULL, EVP_sha256(), NULL, private_key ) != 1)
    // throw exception

  if ( EVP_DigestSignUpdate( ctx, msgbuf, msg.size() ) != 1 )
    // throw exception

  if ( EVP_DigestSignFinal( ctx, NULL, &sigbuf_length ) != 1 )
    // throw exception

  sigbuf = (unsigned char *)OPENSSL_malloc( sizeof *sigbuf * sigbuf_length );
  if ( !sigbuf )
    // throw exception

  if ( EVP_DigestSignFinal( ctx, sigbuf, &sigbuf_length ) != 1 )
    // throw exception

  std::copy( sigbuf, sigbuf + sigbbuf_length, std::back_inserter( signature ) );

  EVP_MD_CTX_destroy( ctx );
  OPENSSL_free( sigbuf ); // yes, there's potential for a memory leak, but I'm just trying to get this bastard to work.
}

Опять же, подпись, сгенерированная из этого кода, не согласуется с результатом

openssl rsautl -sign -inkey keyfile.pem -keyform PEM -in msg.txt -out signature

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

Я смотрю на это и тяну свои волосы в течение двух дней. Будем весьма благодарны за любые предложения, советы, рекомендации, грубые замечания и т. Д.

Благодарю.

РЕДАКТИРОВАТЬ

Некоторые примеры должны проиллюстрировать, с чем я столкнулся.

Вот SignedInfo генерируется для данного сообщения:

<ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
  <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2006/12/xml-exc-c14n#">
    <ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="alws soapenv"/>
  </ds:CanonicalizationMethod>
  <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
  <ds:Reference URI="#TS-5b171864-232b-11e9-846f-00505695541c">
    <ds:Transforms>
      <ds:Transform Algorithm="http://www.w3.org/2006/12/xml-exc-c14n#">
        <ec:InclusiveNamespaces xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#" PrefixList="wsse alws soapenv"/>
      </ds:Transform>
    </ds:Transforms>
    <ds:DigestValue>eeLn6ak1glbbbWE48q7olsxO0CO/fL85bZ+8hzcjrvE=
</ds:DigestValue>
    <ds:DigestMethod Algorithm="https//www.w3.org/2001/04/xmlenc#sha256"/>
  </ds:Reference>
</ds:SignedInfo>

Значение дайджеста вычисляется из отметки времени:

<wsu:Timestamp xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="TS-5b171864-232b-11e9-846f-00505695541c">
  <wsu:Created>2019-01-28T18:34:33Z</wsu:Created>
  <wsu:Expires>2019-01-28T18:34:38Z</wsu:Expires>
</wsu:Timestamp>

Когда я вычисляю дайджест для этой временной метки из командной строки следующим образом:

$ openssl dgst -sha256 -binary timestamp > timestamp.dgst

Я получаю то же значение дайджеста:

$ openssl base64 -in timestamp.dgst
eeLn6ak1glbbbWE48q7olsxO0CO/fL85bZ+8hzcjrvE=

Все идет нормально. Этот хэш добавлен в SignedInfo тогда я беру хэш SignedInfo:

cQaWLGHi8D/c1kXPG9i49xzAupeBuypvMvuMQlzA/wo=

И использовать его для генерации подписи. Теперь, вот где дела полностью сходят с рельсов. Когда я генерирую подпись, используя приведенный выше код, я получаю:

HtQ4LkYq4Eao4bMOpV4SBpMxHi2a+0ilxDXS9jIQZWdCC8HCNlpvVU4rWMZG2Zd/
LplCWmUHIaB35FKv6uKjCjJPVDAJT2agyp7FnSKxaBI44Y/YsdvKyxJTAMiAlF8i
dd1MB8ljYsfayrzq5e76kt2cbHlYkT/RM3SvwJtjZiYsNpfcXD0Bi6JhRshHxQ8s
6/errruOe7jUqbKh7UOPJokadCX0OTSSwRgcs+sm7VjnS9MYILaGzFFT3Js9xI6d
TL4B6A/JGIkEqLO+GA1lrokAeIBr9OVUu7OEzaBb7DaiP9Gv1diu0j1sbZ4uT5Cf
CjYJPYU72Xx8F+MKdSJteg==

Когда я использую инструмент командной строки, я получаю:

PvfCDqPl86/8USbFU0XR5r1Dhl5JbWd2va3L4W1IW1zw6xdes04F4lYjol6gMKio
jyr8DdmWBquroVlo4vW8kmhr6760qMcpK6mfsZ26ftu7XRC+Z4b9ge6ICOemsGlE
04Yoh9EpECP+ei5yS4E1sbntteiSoQcjotmVcIbPaEG5DIDcd4JKfoCWmsnuZESs
qctIJAQy4YY9HJsVGJ2JG7QashFcEQJabtInFgYeKuxla0ZSXBfOBkwHZT/cSv+k
n/NqPMCyEl4B2LiPBVa36GaTUd6fx0SXnIh0Fm+jw6b6j3EjU0QfMJ/JBAlL+oWZ
fXO/pS5L7W+OWk8Fh//iKA==

Когда я проверяю подпись, сгенерированную в командной строке, я возвращаю исходный хеш:

 $ openssl base64 -d -in signature.b64 > signature.reversed

 $ openssl rsautl -verify -inkey cert.pem -certin -in signature.reversed > signature.reversed.dgst

$ openssl base64 -in signature.reversed.dgst
cQaWLGHi8D/c1kXPG9i49xzAupeBuypvMvuMQlzA/wo=

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

$ openssl base64 -d -in badsig.b64 > badsig

$ openssl rsautl -verify -inkey cert.pem -certin -in badsig > badsig.dgst

$ openssl base64 -in badsig.dgst
MDEwDQYJYIZIAWUDBAIBBQAEIHEGlixh4vA/3NZFzxvYuPccwLqXgbsqbzL7jEJc
wP8K

Что особенно расстраивает, так это то, что с тех пор я добавил код для проверки подписи после его создания, и этот код проходит. Но это, очевидно, неправильно - удаленный сервис отклоняет наши запросы, потому что хэши не совпадают и не совпадают с тем, что генерируется инструментом командной строки openssl. Я проверил, что и инструмент командной строки, и библиотека, которую я использую, имеют одинаковую версию (1.0.1).

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

Похоже, существует не менее 3 различных способов подписать сообщение с помощью процедур libcrypto - EVP_Sign(), EVP_DigestSign(), EVP_PKEY_sign() - и я не уверен, какой из них является правильным для использования в моем случае. Все три дают мне плохие подписи (то есть при расшифровке с открытым ключом исходное хеш-значение не выдается).

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

БОЛЬШЕ РЕДАКТИРОВАТЬ

Также пытается проверить с помощью dgst следующим образом, снова используя SignedInfo блок выше:

$ openssl dgst -sha256 -binary -sign fx-realtime.fundsxpress.com.pem -out signature signedinfo

$ openssl dgst -sha256 -binary -verify publickey.pem -signature signature signedinfo
Verified OK

Обращая подпись, мой код генерирует:

$ echo -n 'XfgP1A08UTwz3sUHIVvvV+fq1n3act6+lVBZ8ieDtgh28k1r1/M0tm9MntvK+Hm4
> Be+LjguX2gxhZ4PvVcoCBCugDIsrhxplDeB4bYeY2PEedQL6+IZFX+kFrz6o3RQa
> W7sXK7czogxWpdLAmKnhDJOk2BmKFihkRMTjo9D4z/qylZI9nnX29HNdg3uV2BYw
> zHh8GvYO8fy1ugqfFW80na+hLBAtBP6fwTTv10DS2L8n+ixQcnxlKW5pyBOXlR/r
> mZEqwU+A996G0573HkGFeFvXzArlRFg/7mkKoyUHyqyDzkf5eC+vTnpEy1CP75Yc
> lvd7ldSrwREisPnyxu47sg=='> computed_signature.b64

$ openssl base64 -e -in computed_signature.b64 > computed_signature

$ openssl dgst -sha256 -binary -verify publickey.pem -signature computed_signature signedinfo
Verification Failure

1 ответ

Код в порядке и сгенерированная подпись в порядке. Что вам не хватает, так это то, что каждый раз, когда вы генерируете подпись, она будет отличаться каждый раз. Если вы запустите подписи через проверку, все они будут работать. Я не знаю причину, почему, но я предполагаю, что есть причина безопасности, почему это делается таким образом.

Из подробностей здесь не ясно, как обрабатываются подписанные данные (ввод).

команды openssll будут читать их как двоичные (не текстовые), и могут быть данные новой строки, которые изменяются, если вы используете чтение текста (например, в Windows 2 символа CR/LF -> переход к 1 CR), прежде чем вы подпишете ввод.

отсутствие подписи одного и того же ввода создаст дайджест различий (очевидно).

Другие вопросы по тегам