詳解Node.js 中使用 ECDSA 簽名遇到的坑
最近有個(gè)朋友問(wèn)我關(guān)于 Node.js 下使用 ECDSA 的問(wèn)題,主要是使用 Node.js 的 Crypto 模塊無(wú)法校驗(yàn)網(wǎng)絡(luò)傳輸過(guò)來(lái)的簽名結(jié)果。在踩坑無(wú)數(shù)后,終于搞清楚了原因。
坑 0x00:簽名輸出格式
在排除了證書(shū)、消息不一致的可能之后,我開(kāi)始對(duì)比使用 Node.js 簽名的結(jié)果與網(wǎng)絡(luò)傳輸過(guò)來(lái)的簽名,發(fā)現(xiàn)長(zhǎng)度不一致,大約差了5~7個(gè)字節(jié)。于是去網(wǎng)上搜索了一下,才知道原來(lái) Node.js (基于 OpenSSL)簽名得到的是 DER 格式的內(nèi)容,而網(wǎng)絡(luò)上常用的 ECDSA 簽名結(jié)果是 IEEE P1363 格式的。(也可以寫(xiě)作 R|S)
參考:https://stackoverflow.com/a/39575576
知道問(wèn)題了就好解決了。但是,DER 和 IEEE P1363 兩個(gè)格式互轉(zhuǎn)也不是那么容易的。
簡(jiǎn)單科普一下,ECDSA 是指基于 ECC 橢圓加密算法的簽名方式,簽名結(jié)果是兩個(gè)整數(shù) R 和 S。 R 和 S 一般長(zhǎng)度相同,或者接近。如果長(zhǎng)度不同,在各自前面補(bǔ)字節(jié) 0x00 直到等長(zhǎng)。把 R 和 S 以大頭字節(jié)序表示,然后依次前后拼接,就是所謂 IEEE P1363 格式。
坑 0x01:DER 的整數(shù)問(wèn)題
先來(lái)了解一下 ECDSA 的 DER 輸出格式,大概如下:
SEQUENCE <LENGTH> INTEGER <INTEGER_LENGTH> <INTEGER_VALUE...> # 整數(shù) R INTEGER <INTEGER_LENGTH> <INTEGER_VALUE...> # 整數(shù) S
其中
SEQUENCE 是 DER 數(shù)組(串?)標(biāo)頭,用一個(gè)字節(jié) 0x30 表示
<LENGTH> 是 SEQUENCE 的長(zhǎng)度,用一個(gè)字節(jié)表示,不包括標(biāo)頭和這個(gè)長(zhǎng)度本身
INTEGER 是整數(shù)標(biāo)頭,用一個(gè)字節(jié) 0x02 表示
<INTEGER_LENGTH> 是整數(shù)的字節(jié)長(zhǎng)度,用一個(gè)字節(jié)表示。
<INTEGER_VALUE> 是整數(shù)的內(nèi)容,以大頭字節(jié)序表示。
另一個(gè)坑我也已經(jīng)寫(xiě)出來(lái)了,不知道有人發(fā)現(xiàn)沒(méi)有?沒(méi)想到的話,繼續(xù)往下。
IEEE P1363 格式下,R 和 S 都是等長(zhǎng)的。所以只要把 IEEE P1363 格式的簽名從中間切分就可以得到 R 和 S 的內(nèi)容了。而且 IEEE P1363 格式下,R 和 S 也是以大頭字節(jié)序表示的,因此沒(méi)有字節(jié)序轉(zhuǎn)換問(wèn)題了?,F(xiàn)在,只需要按上面的格式構(gòu)造一個(gè) DER 即可。
坑 0x01.0:缺少整數(shù)前置字節(jié) 0x00
我第一次嘗試將 IEEE P1363 格式的簽名轉(zhuǎn)換成 DER 格式,并沒(méi)有失敗,但是當(dāng)我換一個(gè)簽名結(jié)果,卻失敗了……我對(duì)比了 DER 和 IEEE P1363 的區(qū)別,發(fā)現(xiàn)了一個(gè)特點(diǎn),在 DER 格式下,R 和 S 偶爾會(huì)有前置字節(jié) 0x00,但不是一定的。
查資料后才明白,DER 下沒(méi)有“無(wú)符號(hào)整數(shù)”之說(shuō),也就是說(shuō)整數(shù)都是有符號(hào)的。如果 INTEGER 所表示的整數(shù)最高字節(jié)大于 0x7F,也就是最高位(符號(hào)位)為 1,則表示負(fù)數(shù)。如果要表示正數(shù),必須在前面補(bǔ)一個(gè)字節(jié) 0x00……
參考 https://bitcointalk.org/index.php?topic=215205.msg2258789#msg2258789
坑 0x01.1:多余的整數(shù)前置字節(jié) 0x00
在我修改代碼后,雖然提高了成功率,可仍然有失敗的情況,仔細(xì)看了下,原來(lái)是因?yàn)?IEEE P1363 格式里,R 和 S 可能被補(bǔ)了不止 1 個(gè)字節(jié) 0x00……
而 DER 下雖然要求補(bǔ)字節(jié) 0x00,卻是有且只能有一個(gè)字節(jié) 0x00。
到此,問(wèn)題都解決了——直到我測(cè)試了 521-bit (是的,你沒(méi)看錯(cuò),不是 512) 長(zhǎng)度的密鑰時(shí),完全失敗,毫無(wú)例外。
坑 0x02:DER SEQUENCE 的長(zhǎng)度超過(guò) 0x7F
前面說(shuō)了,<LENGTH> 只能用一個(gè)字節(jié)表示,這是一個(gè)整數(shù),前文我提到的整數(shù)正負(fù)問(wèn)題,這里也存在!
即是說(shuō),ECDSA 簽名使用 DER 輸出格式時(shí),如果使用 521-bit (是的,你沒(méi)看錯(cuò),不是 512) 長(zhǎng)度的密鑰時(shí),DER的長(zhǎng)度將超出 0x7F,使得 <LENGTH> 變成了負(fù)數(shù)!
而解決方案不是補(bǔ)字節(jié) 0x00,而是用字節(jié) 0x81 填充 <LENGTH>,再在下一個(gè)字節(jié)用一個(gè)無(wú)符號(hào)整數(shù)的表示長(zhǎng)度(0 ~ 255)。
參考:https://stackoverflow.com/a/47099047
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
詳解從Node.js的child_process模塊來(lái)學(xué)習(xí)父子進(jìn)程之間的通信
這篇文章主要介紹了從Node.js的child_process模塊來(lái)學(xué)習(xí)父子進(jìn)程之間的通信,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-03-03關(guān)于express與koa的使用對(duì)比詳解
很多人都在問(wèn)到底該用Koa還是express,所以下面這篇文章就來(lái)給大家再次的對(duì)比了關(guān)于express與koa的相關(guān)資料,通過(guò)對(duì)比大家可以更好的進(jìn)行選擇,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-01-01Nodejs實(shí)現(xiàn)的操作MongoDB數(shù)據(jù)庫(kù)功能完整示例
這篇文章主要介紹了Nodejs實(shí)現(xiàn)的操作MongoDB數(shù)據(jù)庫(kù)功能,結(jié)合完整實(shí)例形式分析了nodejs針對(duì)MongoDB數(shù)據(jù)庫(kù)的連接及增刪改查基本操作技巧,需要的朋友可以參考下2019-02-02NodeJS使用遞歸算法和遍歷算法來(lái)遍歷目錄的方法
遍歷目錄是操作文件時(shí)的一個(gè)常見(jiàn)需求,比如寫(xiě)一個(gè)程序,需要找到并處理指定目錄下的所有JS文件時(shí),就需要遍歷整個(gè)目錄,NodeJS遍歷目錄可以使用遞歸算法、遍歷算法,遍歷算法又分為同步遍歷、異步遍歷兩種,本文介紹NodeJS使用遞歸算法和遍歷算法來(lái)遍歷目錄的方法2023-11-11koa上傳excel文件并解析的實(shí)現(xiàn)方法
這篇文章主要介紹了koa上傳excel文件并解析的實(shí)現(xiàn)方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08