加密涉及到了内容挺复杂的,是一门专业性很强的学科。笔者没有专门学过,在此只是略讲一些bccsp服务所涉及到的皮毛:
fabric所用到的这些技术的常量名称在/fabric/bccsp/opts.go中开始的部分定义,如ECDSA支持ECDSAP256,ECDSAP384等几种类型。
BCCSP,是blockchain cryptographic service provider的缩写,个人译作区域链加密服务提供者,为fabric项目提供各种加密技术,签名技术,工具的性质很强,MSP服务模块中就使用到了BCCSP。这里需要说明的一点是,工具性强,也就说明了,如果不是想专门学这一领域,其实不用太在乎其实现的细节,只要用就行了。BCCSP服务的代码集中在/fabric/bccsp中,目录结构如下:
从以上可以看出bccsp服务有两种实现:pkcs11和sw。简单明了的解释的话(虽不太精准),就是pckcs11是硬件基础的加密服务实现,sw是软件基础的加密服务实现。这个硬件基础的实现以 https://github.com/miekg/pkcs11 这个库为基础,而HSM是Hardware Security Modules,即硬件安全模块的缩写。相对应的两种bccsp服务实现,这里有两种工厂,两种工厂为其他使用bccsp服务的模块提供了窗口函数(就是给其他模块提供窗口的函数,这些函数一般统一管理自己服务的功能模块,供外界调用,如后文所讲的InitFactories函数)。所有产生的bccsp实例存储在factory/factory.go中所定义的全局变量中。
//在fabric/bccsp/bccsp.go中定义
type BCCSP interface {
//根据key生成选项opts生成一个key
//与key有关的选项opts选项要适合原始的key(与“证书是一级一级的认证”对看)
KeyGen(opts KeyGenOpts) (k Key, err error)
//根据key获取选项opts从k中重新获取一个key
KeyDeriv(k Key, opts KeyDerivOpts) (dk Key, err error)
//根据key导入选项opts从一个key原始的数据中导入一个key
KeyImport(raw interface{}, opts KeyImportOpts) (k Key, err error)
//根据SKI返回与该接口实例有联系的key
GetKey(ski []byte) (k Key, err error)
//根据哈希选项opts哈希一个消息msg,如果opts为空,则使用默认选项
Hash(msg []byte, opts HashOpts) (hash []byte, err error)
//根据哈希选项opts获取hash.Hash实例,如果opts为空,则使用默认选项
GetHash(opts HashOpts) (h hash.Hash, err error)
//根据签名者选项opts,使用k对digest进行签名,注意如果需要对一个特别大的消息的hash值
//进行签名,调用者则负责对该特别大的消息进行hash后将其作为digest传入
Sign(k Key, digest []byte, opts SignerOpts) (signature []byte, err error)
//根据鉴定者选项opts,通过对比k和digest,鉴定签名
Verify(k Key, signature, digest []byte, opts SignerOpts) (valid bool, err error)
//根据加密者选项opts,使用k加密plaintext
Encrypt(k Key, plaintext []byte, opts EncrypterOpts) (ciphertext []byte, err error)
//根据解密者选项opts,使用k对ciphertext进行解密
Decrypt(k Key, ciphertext []byte, opts DecrypterOpts) (plaintext []byte, err error)
}
bccsp文件夹中任何带opt字眼的文件,都是和选项有关的源码。关于对象的配套选项,我们在讲MSP服务的时候就见识过。根据一个选项的不同配置,对象主体可以得到不同的数据或进行不同的操作,这也是一种比较值得学习的语言上的组织技巧。尤其是在bccsp这种涉及的技术比较多,而每个对象自身又分为好多类的情况。在此以哈希选项HashOpts和key导入选项KeyImportOpts作为例子进行说明:
//在/fabric/bccsp/bccsp.go中定义
//哈希选项接口
type HashOpts interface {
Algorithm() string //获取hash算法字符串标识,如"SHA256","SHA3_256"
}
//在/fabric/bccsp/hashopts.go中定义
//哈希选项实现之一,SHA256选项
type SHA256Opts struct {
}
func (opts *SHA256Opts) Algorithm() string {
return SHA256
}
//哈希选项实现之二,SHA384选项
type SHA384Opts struct {
}
func (opts *SHA384Opts) Algorithm() string {
return SHA384
}
--------------------------------------------------
//在/fabric/bccsp/bccsp.go中定义
//key导入选项接口
type KeyImportOpts interface {
Algorithm() string //返回key导入算法字符串标识
Ephemeral() bool //如果生成的key是短暂的(ephemeral),返回true,否则返回false
}
//在/fabric/bccsp/opts.go中定义
//key导入选项接口实现之一,ECDSA公匙的导入选项
type ECDSAPKIXPublicKeyImportOpts struct {
Temporary bool
}
func (opts *ECDSAPKIXPublicKeyImportOpts) Algorithm() string {
return ECDSA
}
func (opts *ECDSAPKIXPublicKeyImportOpts) Ephemeral() bool {
return opts.Temporary
}
//key导入选项接口实现之二,ECDSA私匙的导入选项
type ECDSAPrivateKeyImportOpts struct {
Temporary bool
}
func (opts *ECDSAPrivateKeyImportOpts) Algorithm() string {
return ECDSA
}
func (opts *ECDSAPrivateKeyImportOpts) Ephemeral() bool {
return opts.Temporary
}
//比较特殊的,比如签名选项接口SignerOpts
//由于因为使用的标准库,因此使用到此选项时多赋值为nil,bccsp源码中未实现
BCCSP的SoftWare(SW)实现形式是默认的形式,这点仅从/fabric/bccsp/factory/opts.go中工厂的默认选项DefaultOpts和核心配置文档中关于bccsp的配置就可以看出来。主要使用的包是标准库hash和crypto(包括其中的各种包,如aes,rsa,ecdsa,sha256,elliptic,x509等)。
目录结构:
BCCSP接口实现:
//在/fabric/bccsp/sw/impl.go中定义
//SW bccsp的实例结构体
type impl struct {
conf *config //bccsp实例的配置
ks bccsp.KeyStore //key存储系统对象,存储和获取Key对象
encryptors map[reflect.Type]Encryptor //加密者映射
decryptors map[reflect.Type]Decryptor //解密者映射
signers map[reflect.Type]Signer //签名者映射,Key实现的类型作为映射的键
verifiers map[reflect.Type]Verifier //鉴定者映射,Key实现的类型作为映射的键
}
//专用生成函数
func New(...) (bccsp.BCCSP, error) { ... }
//接口实现
func (csp *impl) KeyGen(opts bccsp.KeyGenOpts) (k bccsp.Key, ...) { ... }
...
粗线条上看,由于impl对象和各种操作选项的存在,绝大部分接口的实现都是以switch-case为主干,根据选项的类型或配置,分情况完成功能,且每个分支的操作基本都很类似,如KeyGen。接下来一一介绍:
utils.Clone
和utils.DERToPublicKey
,如AES256,ECDSAPrivateKey等类型的key;一部分直接用go语言的断言raw.(*Key类型)
,如ECDSAGoPublicKey,RSAGoPublicKey等类型的key。New
的signers赋值部分,可知用到了两种对应类型的Key和Signer:ecdsaPrivateKey - ecdsaSigner和rsaPrivateKey - rsaSigner。这里签名的实现是,从该bccsp实例的签名者集合成员signers获取类型为reflect.TypeOf(k)
的签名者signer,然后直接调用signer的接口Sign,追溯,ecdsaSigner使用ecdsa库的Sign函数,rsaSigner使用rsa库PrivateKey结构体的Sign函数。New
的verifiers赋值部分,可知所有鉴定者(与对应的Key接口实现)都有用到。这里鉴定的实现是,从该bccsp实例的鉴定者集合成员verifiers获取类型为reflect.TypeOf(k)
的鉴定者verifier,然后直接调用verifier的接口Verify,追溯,ecdsaXXXKeyVerifier使用ecdsa库的Verify函数,rsaXXXKeyVerifier使用rsa库的VerifyPSS函数(这里XXX表示PublicKey或Private)。New
的encryptors赋值部分,只有aesPrivateKey - aescbcpkcs7Encryptor被使用。这里加密的实现是,从该bccsp实例的加密者集合成员encryptors获取类型为reflect.TypeOf(k)
的加密者encryptor,然后直接调用encryptor的接口Encrypt,追溯,aescbcpkcs7Encryptor使用了aes库的加密流程进行加密。在fabirc源码解析12——peer的MSP服务文中背书检验部分的第3、4点,涉及到了包含在msp中的bccsp对象成员,使用了bccsp对象的GetHashOpt
、Hash
、KeyImport
、Verify
等接口用以生成identities对象或identities自身一些接口实现。在此可以对看。
BCCSP的pkcs11实现形式主要使用到的库与sw实现如出一辙,但外加一个github.com/miekg/pkcs11库,最好参看其文档以熟悉pkcs11的简要操作。pkcs11(PKCS,Public-Key Cryptography Standards)是一套非常通用的接口标准,可以说这里是用pkcs11实现了bccsp的功能,也为fabric支持热插拔和个人安全硬件模块提供了服务。这点可以从bccsp的pkcs11的实现实例的专用生成函数New
(参看下文)中所调用的loadLib
函数可以看出来:loadLib
加载了一个系统中的动态库,能加载系统的动态库,就可以和驱动、热插拔、连接电脑的字符设备联系在一起。比如将来,开发出了一款在区域链上类似于现在网上银行所用的U盾之类的个人身份或安全交易硬件模块或芯片,这些硬件模块或芯片只需要也遵循pkcs11,fabric即可对此进行支持和扩展。
对于pkcs11的所提供的接口,在此提供两个文档地址,读者可以稍作了解: https://www.ibm.com/developerworks/cn/security/s-pkcs/ , http://docs.oracle.com/cd/E19253-01/819-7056/6n91eac56/index.html#chapter2-9 。这些文档与fabric和区域链无关,但是因为pkcs11是通用的接口,所以有一定参考价值。pkcs11库中的解释相对过于简单。
目录结构:
signECDSA
和鉴定函数verifyECDSA
BCCSP接口实现:
type impl struct {
conf *config //配置
ks bccsp.KeyStore //key存储系统对象,存储和获取Key对象
ctx *pkcs11.Ctx //标准库的pkcs11上下文
sessions chan pkcs11.SessionHandle //实质是uint,会话标识符频道,默认10个缓存
slot uint //安全硬件外设连接插槽标识号
lib string //加载库所在路径
noPrivImport bool //禁止导入私匙标识
softVerify bool //使用软件方式鉴定签名标识
}
//专用生成函数
func New(opts PKCS11Opts, keyStore bccsp.KeyStore) (bccsp.BCCSP, error) { ... }
//接口实现
func (csp *impl) KeyGen(opts bccsp.KeyGenOpts) (k bccsp.Key, err error) { ... }
...
BCCSP的pkcs11实现的骨架在impl.go中与sw的实现基本一致,只是追溯到最终实现的语句时,sw实现是使用crypto库下的各个包进行签名,加密,密匙导入等,而pkcsll则用pkcs11包对数据进行了多一层的处理,使用pkcs11提供的上下文(pkcs11.Ctx)和会话(SessionHandle)之上对签名,密匙,加密等进行管理,这也是pkcs11.go文件的作用。两者最大的不同是一个面向软件,一个面向硬件,pkcs11自身又非常的冗杂,因此在此只讲pkcs11中与安全硬件模块建立连接的loadLib函数。
loadLib函数在pkcs11.go中定义,供专用生成函数New
使用。为建立与安全硬件模块的通信,进行了如下步骤:
pkcs11.New(lib)
建立pkcs11实例ctx。ctx相当于fabric与安全硬件模块通信的桥梁:bccsp<–>ctx<–>驱动lib<–>安全硬件模块,只要驱动lib是按照pkcs11标准开发。ctx.Initialize()
进行初始化。ctx.GetSlotList(true)
返回的列表中获取由label指定的插槽标识slot(这里的槽可以简单的理解为电脑主机上供安全硬件模块插入的槽,如USB插口,可能不止一个,每一个在系统内核中都有名字和标识号)。ctx.OpenSession
打开一个会话session(会话就是通过通信路径与安全硬件模块建立连接,可以简单的理解为pkcs11的chan)。ctx.Login
。关于pkcs11,还有一点可说的,就是SoftHSM库,它是一个模拟硬件实现的pkcsll,对应到的系统动态库可参看impl.go中FindPKCS11Lib测试函数中所涉及的,如Linux下的libsofthsm2.so。现阶段是没有安全硬件模块可以配合测试的,所以只有使用SoftHSM模拟测试,将libsofthsm2.so导入pkcs11对象。
对应两种bccsp实现,这里也有两种bccsp工厂:pkcs11factory.go和swfactory.go。fabric中某一模块一旦涉及工厂factory,则说明该模块基本就是由工厂提供“窗口函数”,供其他模块调用。这里以swfactory为例进行讲解。
目录结构:
swfactory接口和实现:
//在factory.go中定义
//接口
type BCCSPFactory interface {
//返回工厂的名字
Name() string
//返回符合工厂选项opts的bccsp实例
Get(opts *FactoryOpts) (bccsp.BCCSP, error)
}
//在swfactory.go中定义
//实现
type SWFactory struct{}
func (f *SWFactory) Name() string {
return SoftwareBasedFactoryName
}
func (f *SWFactory) Get(config *FactoryOpts) (bccsp.BCCSP, error) { ... }
实现的代码本身比较简单,Get
最终是调用的sw的专用生成函数New
来生成符合opts的bccsp实例。Name
则是直接返回一个”SW”常量。
在每个chaincode例子中,如/fabric/examples/e2e_cli/examples/chaincode/go/chaincode_example02/chaincode_example02.go,都使用了chaincode垫片shim中的Start
函数。不知道在fabirc源码解析7——peer的ChaincodeSupport服务文中是否说过,在此说一下,chaincode的“垫片”shim核心代码集中在/fabric/core/chaincode/shim中,该垫片所“承垫”的是与各个结点通信的任务,也即ChaincodeSupport服务。chaincode形成的通信的信息,通过shim分发到各个结点,然后shim负责从各个结点收集信息,汇总返回给chaincode,完成chaincode的功能。其中shim的Start
函数就是用来启动一个chaincode,定义在/fabric/core/chaincode/shim/chaincode.go中。在Start
的函数中,就调用了err := factory.InitFactories(&factory.DefaultOpts)
来初始化一个默认的bccsp工厂,在此可以知道,这里使用的默认工厂选项(参看opts.go),也就是使用的swfactory。