簡體   English   中英

C#無法從OpenSSL控制台行命令驗證ECDSA(384位)base 64數字簽名

[英]C# fails to verify ECDSA (384-bit) base 64 digital signature from OpenSSL console line commands

[主持人,我在將這個問題納入字符數限制時遇到了問題,請保持警惕。]

該用例是在Linux服務器上使用OpenSSL對具有384位橢圓曲線數字服務器算法(ECDSA)的許可證(純文本)文件進行簽名,對數字簽名的驗證發生在客戶的完整運行的Windows桌面操作系統上(Windows ).NET Framework。

許可證文件和Base 64編碼的數字簽名通過電子郵件發送給客戶(不在共享公司網絡上)。 客戶正在運行C#編寫的.NET Framework(Windows版)應用程序,對許可證和數字簽名的驗證將解鎖付費功能。

現在,我說的是Linux,但是下面給出的示例服務器端代碼尚未使用Linux腳本語言。 我正在使用Windows 8上運行的VBA進行原型制作,最終我將轉換為Linux腳本語言,但暫時不接受。

關鍵是我正在使用OpenSSL控制台命令,而不是針對任何OpenSSL軟件開發工具包(C ++頭文件等)進行編譯。

其中一個棘手的部分(也許是開始進行代碼審查的最佳位置)是從DER文件中挖掘出形成公鑰的X和Y坐標。 DER密鑰文件是使用抽象語法符號(ASN1)的二進制編碼文件,那里有免費的GUI程序,例如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)很長的十六進制數字作為字符串。 接下來的麻煩是創建適合C#代碼的Xml文件。 關鍵類是C#的ECDsaCng,導入方法是FromXmlString,它期望文件實現標准Rfc4050。 C#的ECDsaCng導入的Xml文件要求X和Y使用十進制而不是十六進制,因此我們必須編寫另一個函數來轉換,這是我從另一個Stack Overflow問題中獲取的另一種語言翻譯而成的。

這是VBA代碼(有很多),您需要更改將其寫入工作文件的位置。 要運行的兩個代碼塊是EntryPoint1_RunECDSAKeyGenerationBatch_RunOnceEntryPoint2_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的響應。

Creating batch directory :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

Private-Key: (384 bit)
priv:
    00:98:78:0d:c7:29:10:1c:9f:4d:75:b2:95:01:01:
    a9:d2:36:72:0d:77:6a:5c:57:8d:51:a0:53:27:05:
    9b:22:1c:c9:0a:1e:e1:27:06:92:c1:6c:2a:c4:bb:
    46:91:98:f6
pub: 
    04:bd:4a:38:04:69:d5:ba:fa:11:27:0f:a8:ef:70:
    3f:11:8d:e0:0f:e7:fd:26:ac:4d:40:32:7a:b5:9c:
    97:71:c1:80:72:1b:42:25:f8:a4:49:4d:8f:89:bf:
    1b:e9:6c:8c:f3:0b:02:db:89:b3:f7:92:e8:c4:a6:
    ce:04:88:10:51:cc:17:0b:b8:9c:9a:a6:3d:fd:ec:
    d4:99:c3:31:6b:22:1d:b6:41:fa:3c:0e:51:fe:86:
    67:bb:7e:86:ce: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 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        

sHexX:BD4A380469D5BAFA11270FA8EF703F118DE00FE7FD26AC4D40327AB59C9771C180721B4225F8A4494D8F89BF1BE96C8C
sHexY:F30B02DB89B3F792E8C4A6CE04881051CC170BB89C9AA63DFDECD499C3316B221DB641FA3C0E51FE8667BB7E86CE066C
sDecX:29134384736743232303148959866907873847020585008044539704341734517362687803911673703523083044584737202030832217844876
sDecY:37407743276271579329804703064876533532537408218368858949720169306023437854945515421210341789026319167790678153234028
<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>

這是用於運行EntryPoint2_RunHashAndSignBatch的VBA立即窗口輸出...



    C:\OpenSSL-Win64\bin\openssl.exe dgst -sha256 -out n:\ECDSA\2017-11-03T193106\license.sha256 n:\ECDSA\2017-11-03T193106\license.txt

    SHA256(n:\ECDSA\2017-11-03T193106\license.txt)= 185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969

    C:\OpenSSL-Win64\bin\openssl.exe dgst -sha256 -sign n:\ECDSA\2017-11-03T193106\ec_key.pem -out n:\ECDSA\2017-11-03T193106\license.sig n:\ECDSA\2017-11-03T193106\license.txt

    C:\OpenSSL-Win64\bin\openssl.exe base64 -in n:\ECDSA\2017-11-03T193106\license.sig -out n:\ECDSA\2017-11-03T193106\license.sigb64

    C:\OpenSSL-Win64\bin\openssl.exe dgst -sha256 -verify n:\ECDSA\2017-11-03T193106\ec_pubkey.pem -signature n:\ECDSA\2017-11-03T193106\license.sig n:\ECDSA\2017-11-03T193106\license.txt
    Verification success

接下來,我們創建一個C#經典控制台應用程序,並粘貼以下代碼以驗證數字簽名,同時記住該客戶將收到base64版本的數字簽名。

using System;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
using System.Xml;

namespace ECDSAVerSig
{
    class Program
    {
        static Action<string> feedback { get; set; }
        static byte[] fileContents = null;
        static byte[] signatureContents = null;

        static ECDsaCng client = null;
        static HashAlgorithm hashAlgo = new SHA256Managed();

        static String parentDirectory = null;

        static void Main(string[] args)
        {

            //* the following will be different for you!!!
            //* and will need to match what was output by the VBA script
            parentDirectory = "n:\\ECDSA\\2017-11-03T193106\\";
            Debug.Assert(Directory.Exists(parentDirectory));

            feedback = Console.WriteLine; // Abstract away 

            if (LoadSignature())
            {
                VerifySignature();
            }


        }



        static private Boolean VerifySignature()
        {
            try
            {
                // a byte array to store hash value
                byte[] hashedData = null;

                Debug.Assert(fileContents[0] == 'H');
                Debug.Assert(fileContents[1] == 'e');
                Debug.Assert(fileContents[2] == 'l');
                Debug.Assert(fileContents[3] == 'l');
                Debug.Assert(fileContents[4] == 'o');
                hashedData = hashAlgo.ComputeHash(fileContents);
                //'* hard coded check of "Hello" hash 
                Debug.Assert(hashedData[0] == 0x18);
                Debug.Assert(hashedData[1] == 0x5f);

                //* the following is consistently wrong though it is my best guess
                Boolean verified = client.VerifyHash(hashedData, signatureContents); //<-- Help required here StackOverflowers

                feedback("Verification:" + verified);

                if (verified)
                {
                    feedback("Hooray you got this 384 bit ECDSA code working! You absolute star!");
                } else
                {
                    feedback("Oh dear, still does not work.  Please keep twiddling.");

                }
                Debug.Assert(verified);

                return true;

            }
            catch (XmlException ex)
            {
                feedback("Problem with verification (Xml parse error):" + ex.ToString());
                return false;
            }
            catch (Exception ex)
            {
                feedback("Problem with verification :" + ex.ToString());
                return false;
            }
        }

        static private Boolean LoadSignature()
        {

            client = new ECDsaCng();
            try
            {

                System.Xml.XmlDocument dom = new System.Xml.XmlDocument();

                dom.Load(Path.Combine(parentDirectory,"ec_pubkey.xml"));

                string xml = dom.OuterXml;
                feedback(xml);
                client.FromXmlString(xml, ECKeyXmlFormat.Rfc4050);

                fileContents = System.IO.File.ReadAllBytes(Path.Combine(parentDirectory, "license.txt"));

                string base64SignatureContents = System.IO.File.ReadAllText(Path.Combine(parentDirectory, "license.sigB64"));
                signatureContents = Convert.FromBase64String(base64SignatureContents); 


                byte[] hashedData = hashAlgo.ComputeHash(fileContents);
                //'* hard coded check of "Hello" hash
                Debug.Assert(hashedData[0] == 0x18);
                Debug.Assert(hashedData[1] == 0x5f);


                return true;
            }
            catch (XmlException ex)
            {
                feedback("Problem with reading digital signature (Xml parse error):" + ex.ToString());
                return false;
            }

            catch (Exception ex)
            {
                feedback("Problem with reading digital signature:" + ex.ToString());
                return false;
            }
        }
    }
}

我已經三遍檢查了這段代碼。 我已經將許可證文件做了一個很短的“ Hello”,並檢查了字節和編碼。 我也檢查了哈希值。 我不知道下一步該怎么做。 請協助。 提前致謝

假設您正確完成了所有其他操作-問題是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

因此它又是兩個數字的序列,索引(從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();
}

如果您正確執行所有操作-簽名就可以驗證。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM