Fabric的简单国密改造实验方案
最近公司在做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 】