C# не может проверить цифровую подпись ECDSA (384-разрядную) base 64 из команд консоли OpenSSL

[Модераторы, у меня возникли проблемы с вживлением этого вопроса в лимит персонажей, пожалуйста, будьте милостивы.]

Вариант использования: использование OpenSSL на сервере Linux для подписания файла лицензии (в виде обычного текста) с помощью 384-битного алгоритма цифрового сервера по эллиптическим кривым (ECDSA), проверка цифровой подписи выполняется в настольной операционной системе Windows клиента, работающей полностью (Windows) .).NET Framework.

Файл лицензии и цифровая подпись в кодировке Base 64 отправляются по электронной почте заказчику (который не находится в общей корпоративной сети) . Заказчик использует приложение.NET Framework (версия для Windows), написанное на C#, и проверка лицензии и цифровой подписи разблокирует платные функции.

Теперь я говорю о Linux, но приведенный ниже пример кода на стороне сервера еще не написан на языке сценариев Linux. Я создаю прототипы с VBA, работающей на Windows 8, в конце концов я перейду на язык сценариев Linux, но пока потерплю.

Дело в том, что я использую консольные команды OpenSSL и не компилируюсь ни с одним из комплектов разработки программного обеспечения OpenSSL (заголовки C++ и т. Д.).

Одна сложная часть (и, возможно, это лучшее место для начала проверки кода) - это поиск координат X и Y, которые образуют открытый ключ из файла DER. Файл ключа DER - это двоичный кодированный файл, который использует абстрактную синтаксическую нотацию (ASN1), существуют бесплатные программы с графическим интерфейсом, такие как Code Project ASN1. Редактор, который позволяет легко проверить, вот скриншот файла открытого ключа

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

C:\OpenSSL-Win64\bin\openssl.exe asn1parse -inform DER -in n:\ECDSA\2017-11-03T193106\ec_pubkey.der
    0:d=0  hl=2 l= 118 cons: SEQUENCE
    2:d=1  hl=2 l=  16 cons: SEQUENCE
    4:d=2  hl=2 l=   7 prim: OBJECT            :id-ecPublicKey
   13:d=2  hl=2 l=   5 prim: OBJECT            :secp384r1
   20:d=1  hl=2 l=  98 prim: BIT STRING

Таким образом, по смещению 20 есть 98 байтов, которые содержат координаты X и Y, в байте 20 - тег (0x03), указывающий, что строка следует, а в байте 21 - длина, 98 (любая длина ниже 127 требует только одного байта) . Таким образом, настоящие 98 байтов данных начинаются с байта 22, поэтому я прочитал всего 100 байтов (98+2). Байт 22 равен 0x00, поэтому начинаются биты (см. Пункт 5). Байт 23 равен 0x04, что указывает на то, что следуют и X, и Y, что называется несжатой формой (можно задать значение X и вычислить Y, в этом случае используйте 0x02 или 0x03). После 0x04 идут координаты X и Y, по 48 байтов каждая, потому что 8 бит в байте и 8*48=384.

Таким образом, каждый выкапывает два (X & Y) очень длинных шестнадцатеричных числа в виде строк. Следующая проблема заключается в создании XML-файла, подходящего для кода C#. Ключевым классом является ECDsaCng в C#, а импортируемый метод - FromXmlString, и ожидается, что файл будет реализовывать стандарт Rfc4050. Xml-файл, который импортирует C# ECDsaCng, требует, чтобы X и Y были в десятичном, а не шестнадцатеричном формате, поэтому нам нужно написать другую функцию для преобразования, я перевел с другого языка, взятого из другого вопроса переполнения стека.

Вот код VBA (его довольно много), и вам нужно изменить, куда он будет записывать свои рабочие файлы. Два блока кода для запускаEntryPoint1_RunECDSAKeyGenerationBatch_RunOnceа такжеEntryPoint2_RunHashAndSignBatch

Это следует считать прочитанным, что OpenSSL установлен, моя версия находится в C:\OpenSSL-Win64\

Полный код VBA находится здесь, потому что SO имеет ограничение 30000 символов. Код вероятного виновника

Option Explicit
Option Private Module

'******* Requires Tools->References to the following libraries
'* Microsoft ActiveX Data Objects 6.1 Library           C:\Program Files (x86)\Common Files\System\ado\msado15.dll
'* Microsoft Scripting Runtime                          C:\Windows\SysWOW64\scrrun.dll
'* Microsoft XML, v.6.0                                 C:\Windows\SysWOW64\msxml6.dll
'* Windows Script HostObject Model                      C:\Windows\SysWOW64\wshom.ocx
'* Microsoft VBScript Regular Expressions 5.5           C:\Windows\SysWOW64\vbscript.dll\3

Private fso As New Scripting.FileSystemObject
Private Const sOPENSSL_BIN As String = "C:\OpenSSL-Win64\bin\openssl.exe"  '* installation for OpenSSL
Private msBatchDir As Variant '* hold over so we can sign multiple times

Private Function ExportECDSAToXml(ByVal sPublicKeyFile As String, ByVal sXmlFile As String) As Boolean

    '* C#'s ECDsaCng class has a FromXmlString method which imports public key from a xml file Rfc4050
    '* In this subroutine we use OpenSSL's asn1parse command to determine where the X and Y coordinates
    '* are to be found, we dig them out and then markup an Xml file

    '* sample output

    '<ECDSAKeyValue xmlns="http://www.w3.org/2001/04/xmldsig-more#">
    '  <DomainParameters>
    '    <NamedCurve URN="urn:oid:1.3.132.0.34" />
    '  </DomainParameters>
    '  <PublicKey>
    '    <X Value="28988690734503506507042353413239022820576378869683128926072865549806544603682841538004244894267242326732083660928511" xsi:type="PrimeFieldElemType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
    '    <Y Value="26760429725303641669535466935138151998536365153900531836644163359528872675820305636066450549811202036369304684551859" xsi:type="PrimeFieldElemType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
    '  </PublicKey>
    '</ECDSAKeyValue>


    Dim sAS1ParseCmd As String
    sAS1ParseCmd = sOPENSSL_BIN & " asn1parse -inform DER -in " & sPublicKeyFile

    Dim eAS1ParseStatus As WshExecStatus, sAS1ParseStdOut As String, sAS1ParseStdErr As String
    eAS1ParseStatus = RunShellAndWait(sAS1ParseCmd, sAS1ParseStdOut, sAS1ParseStdErr)
    Debug.Print sAS1ParseStdOut

    '* sample output from standard out pipe is given blow.
    '* we need to dig into the BIT STRING which is the final item
    '* we need offset and length which is always 20 and 98 for 384 bit ECDSA
    '* but I have written logic in case we want to upgrade to 512 or change of curve etc.
    '    0:d=0  hl=2 l= 118 cons: SEQUENCE
    '    2:d=1  hl=2 l=  16 cons: SEQUENCE
    '    4:d=2  hl=2 l=   7 prim: OBJECT            :id-ecPublicKey
    '   13:d=2  hl=2 l=   5 prim: OBJECT            :secp384r1
    '   20:d=1  hl=2 l=  98 prim: BIT STRING



    Dim vOutputSplit As Variant
    vOutputSplit = VBA.Split(sAS1ParseStdOut, vbNewLine)

    '* remove the traling blank line
    If Trim(vOutputSplit(UBound(vOutputSplit))) = "" Then ReDim Preserve vOutputSplit(0 To UBound(vOutputSplit) - 1)

    '* final line should be the long bit string, i.e. contain 'BIT STRING'
    Debug.Assert StrComp("BIT STRING", Right$(Trim(vOutputSplit(UBound(vOutputSplit))), 10)) = 0

    '* use regular expression to dig out offset and length
    Dim lOffset As Long, lLength As Long
    RegExpOffsetAndLengthFromASN1Parse Trim(vOutputSplit(UBound(vOutputSplit))), lOffset, lLength

    Dim abytes() As Byte
    Dim asHexs() As String  '* for debugging

    '* read in the whole file into a byte array
    ReadFileBytesAsBytes sPublicKeyFile, abytes

    '* for debugging create an array of hexadecimals
    ByteArrayToHexStringArray abytes, asHexs


    Dim bitString() As Byte
    '* need extra 2 bytes because of leading type and length bytes
    CopyArraySlice abytes, lOffset, lLength + 2, bitString()

    '* some asserts which pin down structure of the bytes
    Debug.Assert bitString(0) = 3  '* TAG for BIT STRING
    Debug.Assert bitString(1) = lLength

    '* From Point 5 at http://certificate.fyicenter.com/2221_View_Website_Server_Certificate_in_Google_Chrome.html
    '* "ASN.1 BIT STRING value is stored with DER encoding as the value itself with an extra leading byte of 0x00. "
    Debug.Assert bitString(2) = 0

    '* 0x04 means by x and y values follow, i.e. uncompressed
    '* (instead of just one from which the other can be derived, leading with 0x02 or 0x03)
    '* https://en.bitcoin.it/wiki/Elliptic_Curve_Digital_Signature_Algorithm
    Debug.Assert bitString(3) = 4
    'Stop



    Dim x() As Byte
    Dim y() As Byte

    '* slice out the 48 bits for nopth x and y
    '* why 48?  because 48*8=384 bits(change for 512)
    CopyArraySlice bitString, 4, 48, x()
    CopyArraySlice bitString, 52, 48, y()

    '* convert bytes to hex string for x coord
    Dim sHexX As String
    sHexX = ByteArrayToHexString(x(), "")

    Debug.Print "sHexX:" & sHexX

    '* convert bytes to hex string for y coord
    Dim sHexY As String
    sHexY = ByteArrayToHexString(y(), "")

    Debug.Print "sHexY:" & sHexY

    '* convert hexadeciumal to plain decimal
    '* as Xml file requires it
    Dim sDecX As String
    sDecX = HexToDecimal(sHexX)

    Debug.Print "sDecX:" & sDecX

    '* convert hexadeciumal to plain decimal
    '* as Xml file requires it
    Dim sDecY As String
    sDecY = HexToDecimal(sHexY)

    Debug.Print "sDecY:" & sDecY


    '* create the xml file from a template
    Dim dom2 As MSXML2.DOMDocument60
    Set dom2 = New MSXML2.DOMDocument60
    dom2.LoadXML ECDSAXml(sDecX, sDecY)
    Debug.Assert dom2.parseError.ErrorCode = 0


    dom2.Save sXmlFile

    Debug.Print dom2.XML
    Set dom2 = Nothing


    Debug.Assert CreateObject("Scripting.FileSystemObject").FileExists(sXmlFile)


End Function

Вот вывод в непосредственное окно VBA, которое иллюстрирует команды консоли и ответы для запускаEntryPoint1_RunECDSAKeyGenerationBatch_RunOnce,

Создание каталога пакетов:n:\ECDSA\2017-11-03T193106
C:\OpenSSL-Win64\bin\openssl.exe ecparam -genkey -name secp384r1 -out n:\ECDSA\2017-11-03T193106\ec_key.pem

C:\OpenSSL-Win64\bin\openssl.exe ec -pubout -outform DER -in n:\ECDSA\2017-11-03T193106\ec_key.pem -out n:\ECDSA\2017-11-03T193106\ec_pubkey.der
C:\OpenSSL-Win64\bin\openssl.exe ec -pubout -outform PEM -in n:\ECDSA\2017-11-03T193106\ec_key.pem -out n:\ECDSA\2017-11-03T193106\ec_pubkey.pem

C:\OpenSSL-Win64\bin\openssl.exe ec -noout -text -in n:\ECDSA\2017-11-03T193106\ec_key.pem -out n:\ECDSA\2017-11-03T193106\ec_key.txt

Закрытый ключ: (384 бит)
собств:
    00: 98: 78: 0d: с7:29:10:1c:9f:4d:75:b2:95:01:01:
    A9: d2: 36: 72: 0d: 77: 6а:5с:57:8d:51:a0:53:27:05:
    9b:22:1c: с9: 0a: 1e: е1: 27: 06: 92: с1: 6c: 2a: с4: бб:
    46: 91: 98: f6
паб: 
    04: шд: 4а:38:04:69:d5: ба: а: 11: 27: 0f: а8: эф: 70:
    3f: 11: 8d: е0: 0f: е7: ФД: 26: AC: 4d: 40: 32: 7a: В5: 9с:
    97: 71: с1:80:72:1b:42:25:f8: а4: 49: 4d: 8f: 89: БФ:
    1b: E9: 6c: 8c: f3: 0b: 02: DB: 89: b3: f7: 92: E8: с4: а6:
    CE:04:88:10:51: сс: 17: 0b: b8: 9с: 9а: а6: 3d: Fd: ес:
    d4: 99: с3: 31: 6b: 22: 1d: В6: 41: FA: 3в:0e:51: Fe: 86:
    67: бб: 7e: 86: се:06:6c
ASN1 OID: secp384r1
NIST CURVE: P-384

C:\OpenSSL-Win64\bin\openssl.exe asn1parse -inform DER -in-n:\ECDSA\2017-11-03T193106\ec_pubkey.der
    0:d=0  hl=2 l= 118 минусов: ПОСЛЕДОВАТЕЛЬНОСТЬ          
    2:d=1 гл = 2 л =  16 минусов: ПОСЛЕДОВАТЕЛЬНОСТЬ          
    4:d=2  hl=2 l=   7 prim: OBJECT:id-ecPublicKey
   13:d=2  hl=2 l=   5 prim: OBJECT:secp384r1
   20:d=1  hl=2 l=  98 прим: BIT STRING        

sHexX:BD4A380469D5BAFA11270FA8EF703F118DE00FE7FD26AC4D40327AB59C9771C180721B4225F8A4494D8F89BF1BE96C8C
sHexY:F30B02DB89B3F792E8C4A6CE04881051CC170BB89C9AA63DFDECD499C3316B221DB641FA3C0E51FE8667BB7E86CE066C
sDecX:29134384736743232303148959866907873847020585008044539704341734517362687803911673703523083044584737202030832217844876
sDecY:37407743276271579329804703064876533532537408218368858949720169306023437854945515421210341789026319167790678153234028
          
            
          
          
            

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

1 ответ

Решение

Предполагая, что вы все сделали правильно - проблема в разных форматах подписей, создаваемых openssl и.NET. Подпись, созданная (и ожидаемая) openssl, (удивительно!) Снова закодирована в ASN.1. Бежать

openssl.exe asn1parse -in license.sig -inform DER

и ты увидишь

0:d=0  hl=2 l= 101 cons: SEQUENCE
2:d=1  hl=2 l=  49 prim: INTEGER           :F25556BBB... big number here
53:d=1  hl=2 l=  48 prim: INTEGER          :3E98E7B376624FF.... big number

Таким образом, это снова последовательность с двумя числами, байты с индексом 1 (на основе 0) - это общая длина, байты с индексом 3 - это длина первого числа, затем идет первое число, после этого байта с длиной второго числа, затем второго числа. Обратите внимание, что может быть необязательное заполнение (0 байт), которое должно быть удалено, поэтому не реализуйте это, как я смутно описал, но прочитайте, как правильно анализировать ASN.1.

Как бы то ни было, .NET ожидает, что эти два числа будут соединены вместе, без каких-либо элементов ASN.1, поэтому вам снова нужно их извлечь. В качестве быстрого теста - возьмите эти два числа, которые вы видите из приведенного выше вывода команды (они в шестнадцатеричном формате), объедините их вместе и преобразуйте из шестнадцатеричной строки в байтовый массив, а затем используйте в своем коде как signatureContents, В качестве альтернативы, используйте этот пример кода (никогда не используйте его для реального извлечения этих чисел), чтобы извлечь числа из вашей существующей подписи (если с этим кодом вы все еще получаете недопустимую подпись - попробуйте подход выше с копированием данных непосредственно из вывода asn1parse):

// only for testing purposes
private static byte[] FromOpenSslSignature(byte[] data) {
    var rLength = data[3];
    byte[] rData = new byte[48];
    Array.Copy(data, 4 + (rLength - 48), rData, 0, 48);
    var sLength = data[5 + rLength];
    byte[] sData = new byte[48];
    Array.Copy(data, 6 + rLength + (sLength - 48), sData, 0, 48);
    return rData.Concat(sData).ToArray();
}

Если вы все делаете правильно - подпись проверит просто отлично.

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