如何將 DER ECDSA 簽名轉換為 ASN.1?
我無法驗證使用 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,即錯誤是良性的情況。)