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:37407743276271579329804703064876533532537408218368858949720169306023437854945515421210341789026319167790678153234028DomainParameters>
Я трижды проверил этот код. Я сделал лицензионный файл очень коротким "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();
}
Если вы все делаете правильно - подпись проверит просто отлично.