最近公司在做BaaS,底層上主要的工作就是做Fabric的國密改造,雖然不是我的任務,不過對這個事情還是很感興趣的,很久很久之前,我也動過將Fabric改成成國密算法的念頭,最后改的時候發現太復雜了,涉及的地方太多了,於是放棄了。現在公司有Fabric高手在搞這方面的工作,那么我也就參與進來,用自己的思路再試一試通過簡單的方法來實現Fabric國密的改造吧。經過測試,基本完成了Fabric的國密改造工作,於是寫下這篇博客總結一下吧。
一、改造思路
所謂做國密改造,就是從底層上將國密的SM2、SM3、SM4替換系統默認支持的以美國標准主導的算法。如果我們要改成通過配置的形式讓Fabric在啟動時讀取配置,然后決定使用國密算法還是美國那一套算法,則改動量特別大,而且判斷的地方也很多,比較難。所以我打算直接從最底層的SHA256、ECDSA、AES等算法進行替換,而且只是代碼實現上的替換,而所有的包名、對外接口(公共變量、公共方法)都保持不變,這樣修改量特別小,出錯的可能性也降低了很多。
二、國密改造涉及的庫
2.1 國密改造涉及的系統庫
2.1.1 哈希算法
在Fabric中默認采用的SHA256的哈希算法,對應的國密算法是SM3,所以我們只需要建立github.com/studyzy/crypto/sha256來替換系統的crypto/sha256,而在實現上,主要是包括:
func init() //注冊哈希函數 func New() hash.Hash func Sum256(data []byte) (sum256 [Size]byte)
另外還有New224() hash.Hash和Sum224(data []byte) (sum224 [Size224]byte) 也是存在於這個包的,但是我們不會用到,所以隨便實現一些即可。
2.1.2 數字簽名算法
發Fabric中默認采用ECDSA的簽名算法,對應的國密算法是SM2,所以我們對應建立github.com/studyzy/crypto/ecdsa來替換系統庫:crypto/ecdsa,在ecdsa包中,主要實現了:
func GenerateKey(c elliptic.Curve, rand io.Reader) (*PrivateKey, error) func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error) func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool
這里的參數PrivateKey和PublicKey是新包的,所以還需要做個和系統包的對象類似的實現。
2.1.3 數字證書X509
有了哈希算法和數字簽名算法,我們就可以構建數字證書算法了,數字證書的包crypto/x509內容比較多,但是大部分內容我們都不需要調整,只需要調整import的包名,把引用系統包改為我們自定義的包,然后還有一個特別的地方是在證書中會保存其使用的哈希算法和數字簽名算法的OID,這是一個預先規定好的值,我們找到對應的OID,並進行替換即可。具體內容如下:
//oidSignatureECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2} oidSignatureECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 156, 10197, 1, 501} //oidSignatureSM2WithSM3 //oidNamedCurveP256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 3, 1, 7} oidNamedCurveP256 = asn1.ObjectIdentifier{1, 2, 156, 10197, 1, 301} //oidNamedCurveP256SM2
2.1.4 對稱加密算法
對稱加密算法主要是用在網絡加密通訊也就是TLS的時候使用,系統TLS默認使用的是crypto/aes,於是我們使用SM4產生新的包:github.com/studyzy/crypto/aes,實現如下接口:
const BlockSize func NewCipher(key []byte) (cipher.Block, error)
2.1.5 加密通訊TLS
TLS整體過程很復雜,但是好在我們都不需要做任何修改,只需要將import的包名進行替換即可,最終形成我們自己的TLS包:github.com/studyzy/crypto/tls
2.1.6 其他關聯庫
按理來說只有把上面提到的幾個庫進行替換就行了,但是在實際編譯的時候,發現有諸多問題,所以還需要將其他相關聯的包也遷移出來,但是我們並不需要做過多源碼邏輯上的修改。主要包括:
crypto //系統的RegisterHash在Fabric啟動時被莫名調用,導致SM3的注冊SHA256覆蓋,所以獨立出一個crypto包
crypto/elliptic //初始化sm2 p256這條曲線的參數
crypto/rsa //import包名替換,無邏輯修改
net/http //import包名替換,無邏輯修改
2.2 國密改造涉及的第三方庫
國密改造過程中涉及的第三方庫,只有通過vendor模式,將所有依賴包下載到當前項目下,然后通過Replace的方式,將所有涉及到的系統包替換成對應的國密包:github.com/studyzy/xxxx。
2.2.1 GRPC
在Fabric中,最重要的通信協議就是GRPC了,基本上節點之間的通信都是靠這個協議實現,而這個協議是基於HTTP協議基礎上的,所以在啟用TLS的情況下,必然會依賴TLS包和net/http包。
2.2.2 Docker Client
在第三方Docker客戶端庫github.com/fsouza/go-dockerclient中,有大量的關於HTTP和TLS的代碼。
2.2.3 其他
除了前面提到了比較核心的GRPC和Docker外,還有很多直接或者間接用到的第三方庫,比如prometheus、go-digest等,大部分都是因為使用到HTTP協議,而協議中要支持HTTPS必然就需要對引用的包進行替換。
三、Fabric主代碼的國密改造
3.1 Fabric環境准備
首先需要裝Go環境,這里最好與我們要編譯的Fabric所使用的版本一致。以Fabric1.4.9為例,官方使用的Go版本是1.13.12。
git clone最新的Fabric代碼到$GOPATH/github.com/hyperledger文件夾,這里我們以v1.4.9穩定版為基礎,建立自己的國密改造分支,啟用go module,將所有依賴包下載到vendor文件夾。
go mod init
go mod tidy
go mod vendor
通過以下命令保證當前環境是能夠正常編譯和使用的。
make native make docker
3.2 國密包的替換
接下來進行無腦替換,直接使用IDE的Replace功能,將所有import的crypto/sha256替換成github.com/studyzy/crypto/sha256,將crypto/ecdsa替換成github.com/studyzy/crypto/ecdsa ……一個一個的把前面提到的系統包都替換了。以下是替換列表:
替換完后我們需要將github.com/studyzy/crypto和github.com/studyzy/net添加到go module里面,也就是要添加到vendor文件夾。
3.3 源碼的修改
有些地方,在Fabric源碼里面是寫死了關於SHA256和ECDSA的,比如前面提到過的OID,在Fabric中也有對應的OID,我們需要進行替換。另外還有一些小地方,是因為引用的國密包對象,而代碼里面又使用到了系統包對象,所以造成對象的不匹配,只需要簡單修改即可。
3.4 編譯
我們編譯Fabric分為二進制編譯和docker編譯兩種,對應的都是Makefile里面的命令,所以我們需要對Makefile進行調整。主要包括:
go install 命令需要增加 -mod=vendor參數,保證編譯的時候是使用修改后的vendor文件夾,而不是系統緩存的第三方庫。
3.5 Docker鏡像生成
我們首先運行docker images可以查看當前的環境有哪些鏡像文件,如果已經存在官方的Fabric鏡像,需要執行docker rmi鏡像刪除。
Fabric的鏡像分為節點鏡像和依賴鏡像,節點鏡像有peer、orderer、tools,而搭建一個Fabric網絡可能還依賴的鏡像包括:buildenv、ccenv等,而與國密改造相關的主要就是peer、orderer、tools、ccenv。節點的鏡像生成很簡單,make docker命令即可。
而其中的鏈碼編譯與執行環境ccenv最為復雜。ccenv我們就以go chaincode的支持為例,Java和其他語言的我們就不管了。ccenv在構建時,需要把ChainCode依賴的所有相關代碼Copy到鏡像中,而這些依賴文件是從$GOPATH/src下copy過來的。所有我們需要做一個准備工作:
將$GOPATH/src下面除Hyperledger Fabric外的包刪除(因為這些包里面可能引用了系統的密碼學庫),然后從fabric/vendor文件夾,將所有文件copy一份到$GOPATH/src下。這樣我們的依賴代碼就都是國密替換后的版本。
然后我們再執行:
make ccenv
即可構建支持國密的ChainCode編譯執行鏡像環境。
我已經將所有代碼和鏡像按前面的步驟准備完畢,大家也可以直接使用。Fabric國密源碼:
https://github.com/studyzy/fabric/tree/gm
鏡像文件我也生成,在:https://hub.docker.com/u/studyzy
四、Fabric CA的國密改造
已經有了Fabric主代碼的國密改造,那么Fabric CA的改造就依葫蘆畫瓢,按部就班就行了。具體步驟如下:
1. git clone fabric-ca代碼到本地$GOPATH/github.com/hyperledger文件夾,並基於某穩定版本建立新分支gm。
2. 啟用go module並將依賴包下載到vendor文件夾。
3. 確保-mod=vendor 模式下,能夠正常編譯。
4. 替換import相關的系統包,使用國密包。
5. 修改Makefile,使得編譯的時候帶上-mod=vendor參數。
6. 編譯通過,生成鏡像 make docker
最終代碼提交到了:https://github.com/studyzy/fabric-ca/tree/gm 對應生成的docker鏡像,我提交到了:https://hub.docker.com/r/studyzy/fabric-ca
五、Fabric SDK Go的國密改造
Fabric SDK Go代碼是最混亂的,也是修改最復雜的。因為Fabric SDK不是一個最終應用的程序,不可能打包到docker鏡像中,而是被另外的golang源碼進行引用,所以我們前面那種修改vendor代碼的方式是不靠譜的。我們需要將所有vendor代碼中涉及到的包,都轉移到third_party中,當然所有import的路徑也要對應修改。其次我們不能再使用hyperledger/fabric-sdk-go這個包名,而是使用我們自己的包名,這里就全部改成studyzy/fabric-sdk-go。下面是改造步驟:
1.在$GOPATH/src/github.com/studyzy目錄下git clone https://github.com/hyperledger/fabric-sdk-go
2. 使用go mod vendor命令將所有依賴的第三方庫下載到vendor文件夾。
3. 隨便建一個cmd/main.go引用本包中的文件,確保編譯成功。
4. 替換import相關的系統包,使用國密包。
5. 搜索vendor文件夾,關鍵字studyzy,找到所有需要修改國密引用的第三方包,並將這些包轉移到third_party文件夾下,並對包名進行更正。這里要注意,間接引用了國密包的所有第三方包也需要轉移到third_party文件夾下。並最終確保vendor文件夾下除了studyzy/crypto 和studyzy/net外,不會有其他的studyzy包,並且整個sdk能夠編譯通過。
代碼已經提交到:https://github.com/studyzy/fabric-sdk-go
六、基於Fabric Samples測試國密改造效果
最終,我們國密改造后的產出物為:
Docker鏡像文件:studyzy/fabric-ccenv studyzy/fabric-peer studyzy/fabric-orderer studyzy/fabric-tools studyzy/fabric-ca
二進制文件:configtxgen configtxlator cryptogen discover idemixgen orderer peer fabric-ca-client fabric-ca-server
Fabric SDK Go包:github.com/studyzy/fabric-sdk-go
下面我們就以官方給出的構建第一個Fabric區塊鏈網絡的腳本為例,測試一下我們國密改造后的效果。
git clone -b gm https://github.com/studyzy/fabric-samples
cd fabric-samples
如果是一個全新的環境,並沒有本地編譯Fabric代碼,而是想直接使用我編譯好的國密鏡像,那么直接運行dockerpull.sh文件,下載國密鏡像。如果還是本地通過Fabric代碼自己編譯好了所有鏡像,那么可以不用運行這個命令。
然后進入first-network,執行以下命令構建一個測試網絡。
./byfn.sh up -a -s couchdb
下面我們測試一下fabric-sdk-go的情況,寫一個簡單的測試函數cmd/main.go
然后將編譯好的程序放到first-network/scripts文件夾,然后我們就可以到cli中執行這個程序,看看執行的效果了。
docker exec -it cli bash
cd scripts
./cmd
【提醒:這里提出的這種改造方案只是一種實驗性質的改造,並不是正宗的國密改造方法,正宗的國密標准關於TLS部分比較復雜,只是我這里提到的簡單替換是不符合國密TLS部分的規范的,所以如果別人有另外的正宗的國密改造的節點,那么和我們這里改造的節點是無法正常通訊的,所以說這里只是實驗性質的練手項目,不建議用於生產。最正宗的國密改造Fabric歡迎大家關注“Fabric國密改造工作組” https://github.com/Hyperledger-TWGC/fabric-gm-wiki/wiki 】