Dsa

如何將 DER ECDSA 簽名轉換為 ASN.1?

  • April 5, 2014

我無法驗證使用 Java/BouncyCastle 客戶端 JavaScript 簽名的 ECDSA 簽名。

javascript簽名函式源碼

sign: function (hash, priv) {
       var d = priv;
       var n = ecparams.getN();
       var e = BigInteger.fromByteArrayUnsigned(hash);

       do {
           var k = ECDSA.getBigRandom(n);
           var G = ecparams.getG();
           var Q = G.multiply(k);
           var r = Q.getX().toBigInteger().mod(n);
       } while (r.compareTo(BigInteger.ZERO) <= 0);

       var s = k.modInverse(n).multiply(e.add(d.multiply(r))).mod(n);

 return ECDSA.serializeSig(r, s);
},

serializeSig: function (r, s) {
       var rBa = r.toByteArrayUnsigned();
       var sBa = s.toByteArrayUnsigned();

       var sequence = [];
       sequence.push(0x02); // INTEGER
       sequence.push(rBa.length);
       sequence = sequence.concat(rBa);

       sequence.push(0x02); // INTEGER
       sequence.push(sBa.length);
       sequence = sequence.concat(sBa);

       sequence.unshift(sequence.length);
       sequence.unshift(0x30) // SEQUENCE

       return sequence;
},

伺服器端驗證函式

 /**
    * Verifies the given ASN.1 encoded ECDSA signature against a hash using the public key.
    *
    * @param data      Hash of the data to verify.
    * @param signature ASN.1 encoded signature.
    * @param pub       The public key bytes to use.
    */
   public static boolean verify(byte[] data, byte[] signature, byte[] pub) {
       ECDSASigner signer = new ECDSASigner();
       ECPublicKeyParameters params = new ECPublicKeyParameters(ecParams.getCurve().decodePoint(pub), ecParams);
       signer.init(false, params);
       try {
           ASN1InputStream decoder = new ASN1InputStream(signature);
           DERSequence seq = (DERSequence) decoder.readObject();
           DERInteger r = (DERInteger) seq.getObjectAt(0);
           DERInteger s = (DERInteger) seq.getObjectAt(1);
           decoder.close();
           return signer.verifySignature(data, r.getValue(), s.getValue());
       } catch (IOException e) {
           throw new RuntimeException(e);
       }
   }

有誰知道 javascript 簽名的格式是什麼(DER?),如果知道,如何將其轉換為 ASN.1?

我試過:

public static byte[] toASN1(byte[] data) {
           try {
               ByteArrayOutputStream baos = new ByteArrayOutputStream(400);
               ASN1OutputStream encoder = new ASN1OutputStream(baos);

               encoder.write(data);
               encoder.close();
               return baos.toByteArray();
           } catch (IOException e) {
               throw new RuntimeException(e);  // Cannot happen, writing to memory stream.
           }
}

但驗證仍然失敗。

免責聲明:我不懂 Javascript,也沒有練習 BouncyCastle。但是,我確實了解 Java 和 ASN.1。

ASN.1是結構化數據的表示法,DER 是一組規則,用於將資料結構(在 ASN.1 中描述)轉換為字節序列並返回。

這是 ASN.1,即 ECDSA 簽名所展示的結構的描述:

ECDSASignature ::= SEQUENCE {
   r   INTEGER,
   s   INTEGER
}

當在 DER 中編碼時,這將變成以下字節序列:

0x30 b1 0x02 b2 (vr) 0x02 b3 (vs)

在哪裡:

  • b1是一個單字節值,等於剩餘字節列表的長度(以字節為單位)(從0x02編碼的第一個到結尾);
  • b2是一個單字節值,等於 的長度(以字節為單位)(vr)
  • b3是一個單字節值,等於 的長度(以字節為單位)(vs)
  • (vr)是值的有符號大端編碼“ $ r $ “,最小長度;
  • (vs)是值的有符號大端編碼“ $ s $ “,長度最短。

“最小長度的有符號大端編碼”意味著數值必須被編碼為字節序列,這樣最低有效字節排在最後(這就是“大端”的意思),總長度是最短的表示值(即“最小長度”),第一個字節的第一位指定值的符號(即“有符號”)。對於 ECDSA, $ r $ 和 $ s $ 值是正整數,所以第一個字節的第一位必須是 0;即(vr)(分別(vs))的第一個字節必須有一個介於0x00和之間的值0x7F

例如,如果我們要對數值 117 進行編碼,它將使用單個字節0x75,而不是兩個字節的序列0x00 0x75(因為最小化)。但是,值 193必須編碼為兩個字節0x00 0xC1,因為單個字節0xC1本身將表示負整數,因為第一個(也稱為“最左邊”)位0xC1是 1(值的單個字節0xC1表示值 - 63)。

我堅持這些細節,因為我的猜測是 Javascript 程式碼做錯了。Javascript 程式碼呼叫了一個名為toByteArrayUnsigned;的方法。該名稱讓人聯想到轉換為無符號表示(即始終為正,即使第一位是 1),這對於 DER 來說是錯誤的。我邀請您將原始簽名保存在一個文件中並“手動”對其進行解碼,以查看它是否與 ASN.1 編碼的精細細節相匹配,如上所述(該openssl asn1parse命令也可能有幫助)。

(如果我的猜測是正確的,那麼您仍然應該有大約 1/4 的機率獲得正確的簽名——當值 $ r $ 和 $ s $ 當使用無符號編碼時,恰好足夠短以將高位設置為 0,即錯誤是良性的情況。)

引用自:https://crypto.stackexchange.com/questions/1795