加密涉及到了内容挺复杂的,是一门专业性很强的学科。笔者没有专门学过,在此只是略讲一些bccsp服务所涉及到的皮毛:
- RSA - 一种非对称的加密算法,用于加密。有几种族簇,如RSA1024,RSA2048等。
- AES - 一种块加密算法,用于加密成块的大量数据。有几种族簇,如AES128,AES192等。
- ECDSA - 一种椭圆曲线签名,用于签名。有几种族簇,如ECDSAP256,ECDSAP384等。
- Hash - 哈希,有几种族簇,如SHA256,SHA3_256等。
- HMAC - 密匙相关的哈希运算消息认证码。
- x509 - 证书的一种,可参看文章12中对证书的解释。
- PKCS#11 - 一套标准安全接口,可与安全硬件相关,以上的这些东西,可以找它来建立,读取,写入,修改,删除等操作进行管理。
fabric所用到的这些技术的常量名称在/fabric/bccsp/opts.Go中开始的部分定义,如ECDSA支持ECDSAP256,ECDSAP384等几种类型。
BCCSP服务结构
BCCSP,是blockchain cryptographic service provider的缩写,个人译作区域链加密服务提供者,为fabric项目提供各种加密技术,签名技术,工具的性质很强,MSP服务模块中就使用到了BCCSP。这里需要说明的一点是,工具性强,也就说明了,如果不是想专门学这一领域,其实不用太在乎其实现的细节,只要用就行了。BCCSP服务的代码集中在/fabric/bccsp中,目录结构如下:
- mocks - 模拟代码文件夹,可以参看之帮助理解bccsp服务
- signer - 实现的是crypto标准库的Signer接口,可参看文章12中MSP服务实现中带“专用签名笔”的身份signingidentity,该目录的签名接口是专用于向外界提供签名对象的功能的。
- factory - bccsp服务工厂
- pkcs11 - bccsp服务实现之一:HSM基础的bccsp(the hsm-based BCCSP implementation)
- sw - bccsp服务实现之二:软件基础的bccsp(the software-based implementation of the BCCSP)
- utils - bccsp服务工具函数
- bccsp.go - 定义了BCCSP,Key接口,众多BCCSP接口所使用到的选项接口,如Key,KeyGenOpts,KeyDerivOpts等
- keystore.go - 定义了key的管理存储接口,如果生成的key不是暂时的,则存储在该接口的实现对象中,如果是暂时性的,则不存储
- XXXopts.go - XXX表示该目录下的各种值,bccsp服务实现所使用到的各种技术的选项实现
从以上可以看出bccsp服务有两种实现:pkcs11和sw。简单明了的解释的话(虽不太精准),就是pckcs11是硬件基础的加密服务实现,sw是软件基础的加密服务实现。这个硬件基础的实现以 https://github.com/miekg/pkcs11 这个库为基础,而HSM是Hardware Security Modules,即硬件安全模块的缩写。相对应的两种bccsp服务实现,这里有两种工厂,两种工厂为其他使用bccsp服务的模块提供了窗口函数(就是给其他模块提供窗口的函数,这些函数一般统一管理自己服务的功能模块,供外界调用,如后文所讲的InitFactories函数)。所有产生的bccsp实例存储在factory/factory.go中所定义的全局变量中。
bccsp中的接口和选项
接口
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
选项
bccsp文件夹中任何带opt字眼的文件,都是和选项有关的源码。关于对象的配套选项,我们在讲MSP服务的时候就见识过。根据一个选项的不同配置,对象主体可以得到不同的数据或进行不同的操作,这也是一种比较值得学习的语言上的组织技巧。尤其是在bccsp这种涉及的技术比较多,而每个对象自身又分为好多类的情况。在此以哈希选项HashOpts和key导入选项KeyImportOpts作为例子进行说明:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
SW实现方式
BCCSP的SoftWare(SW)实现形式是默认的形式,这点仅从/fabric/bccsp/factory/opts.go中工厂的默认选项DefaultOpts和核心配置文档中关于bccsp的配置就可以看出来。主要使用的包是标准库hash和crypto(包括其中的各种包,如aes,rsa,ecdsa,sha256,elliptic,x509等)。
目录结构:
- /fabric/bccsp/sw
- impl.go - bccsp的sw实现impl
- internals.go - 签名者、鉴定者、加密者、解密者接口定义
- conf.go - bccsp的sw实现的配置定义
- ———————————————————————–
- aes.go - aes类型的生成key函数、加密者/解密者实现
- ecdsa.go - ecdsa类型的签名者、公匙/私匙鉴定者实现
- rsa.go - rsa类型的签名者、公匙/私匙鉴定者实现
- ———————————————————————–
- aeskey.go - aes类型的Key接口实现
- ecdsakey.go - ecdsa类型的Key接口实现
- rsakey.go - rsa类型的Key接口实现
- ———————————————————————–
- dummyks.go - dummy类型的KeyStore接口实现dummyKeyStore,当生成的key是短暂的,则说明这些key不会保存到文件中,而是保存到内存中,系统一关闭,这些key就消失了
- fileks.go - file类型的KeyStore接口实现fileBasedKeyStore,当生成的key不是短暂的,则说明这些key在导入时,会存储在文件中,即便系统关闭,这些key也不会消失
BCCSP接口实现:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
粗线条上看,由于impl对象和各种操作选项的存在,绝大部分接口的实现都是以switch-case为主干,根据选项的类型或配置,分情况完成功能,且每个分支的操作基本都很类似,如KeyGen。接下来一一介绍:
- KeyGen - 根据key生成选项不同,生成三种系列的key:ECDSA,AES,RSA。ECDSA使用库ecdsa的GenerateKey函数,AES使用自定义的GetRandomBytes函数(在aes.go中实现,包装了rand.Read),RSA使用库rsa的GenerateKey函数。每个系列又根据具体参数的不同生成不同“尺寸”的key,具体的细节略过。最后返回不同的Key实现对象,如ecdsaPrivateKey,aesPrivateKey等(分别定义在同目录中的XXXkey.go中)。这里要注意的是,当前版本中用于签名的key,只支持ECDSA系列的key,这是官方文档中所说的,但是从实现上看签名的支持不止一种,又但是,bccsp本质上无论实现多上中key,也只有被调用者决定使用哪一种。而MSP模块是bccsp的使用者之一,应该是在它这个地方只认ECDSA的key。
- KeyDeriv - 根据key获取选项opts从k中重新获取一个key,这里的重新获取可以理解为把k中的内容重新打乱再生成一个key。处理两种key类型:ecdsaXXXKey(XXX代表Public和Private),aesPrivateKey。最后返回打乱后重新生成的两种类型的key:reRandomizedKey和hmacedKey。对于重新生成的key,如果选项中指定的不是暂时性的key,则会在ks中存储。
- KeyImport - 从原始的数据raw中取出选项opts指定的key,如果不是临时性的,则在ks中存储,最后返回key。这里的原始,指的是[]byte或者含有key的数据(如证书数据里的key)。raw是一个空接口,也就是说可以接收任何形式的原始数据。为了得到key,对原始数据raw的转化或抽取,一部分使用了utils下的工具函数
utils.Clone
和utils.DERToPublicKey
,如AES256,ECDSAPrivateKey等类型的key;一部分直接用go语言的断言raw.(*Key类型)
,如ECDSAGoPublicKey,RSAGoPublicKey等类型的key。 - Hash - 根据哈希选项opts对一个消息msg进行哈希,返回该msg的哈希值。如果opts为空,则使用默认选项。这个比较好理解,种类支持SHA2,SHA3哈希家族。
- Sign - 根据签名者选项,使用k对digest进行签名,这里的签名者选项在当前版本里没什么用处,调用者都给的是nil。签名涉及到了Key接口和签名者Signer在SW bccsp中的实现。Key接口有三种实现:ecdsakey.go中的ecdsaPublicKey/ecdsaPrivateKey,rsakey.go中的rsaPublicKey/rsaPrivateKey,aeskey.go中的aesPrivateKey,这里签名(自然)使用的都是私匙。签名者接口Signer定义在同目录下的internals.go中,有两种类型的实现:ecdsa.go中的ecdsaSigner和rsa.go中的rsaSigner。参看SW bccsp专用生成函数
New
的signers赋值部分,可知用到了两种对应类型的Key和Signer:ecdsaPrivateKey - ecdsaSigner和rsaPrivateKey - rsaSigner。这里签名的实现是,从该bccsp实例的签名者集合成员signers获取类型为reflect.TypeOf(k)
的签名者signer,然后直接调用signer的接口Sign,追溯,ecdsaSigner使用ecdsa库的Sign函数,rsaSigner使用rsa库PrivateKey结构体的Sign函数。 - Verify - 根据鉴定者选项opts,通过对比k和digest,鉴定签名。鉴定涉及到了Key接口和鉴定者接口Verifier在SW bccsp中的实现。Key接口实现如上描述。鉴定者接口Verifier定义在同目录下的internals.go中,有两种类型的实现:ecdsa.go中的ecdsaPublicKeyKeyVerifier/ecdsaPrivateKeyVerifier和rsa.go中的rsaPublicKeyKeyVerifier/rsaPrivateKeyVerifier。参看SW bccsp专用生成函数
New
的verifiers赋值部分,可知所有鉴定者(与对应的Key接口实现)都有用到。这里鉴定的实现是,从该bccsp实例的鉴定者集合成员verifiers获取类型为reflect.TypeOf(k)
的鉴定者verifier,然后直接调用verifier的接口Verify,追溯,ecdsaXXXKeyVerifier使用ecdsa库的Verify函数,rsaXXXKeyVerifier使用rsa库的VerifyPSS函数(这里XXX表示PublicKey或Private)。 - Encrypt - 根据加密者选项opts,使用k**加密plaintext。加密涉及到了**Key接口和加密者接口Encryptor在SW bccsp中的实现。Key接口实现如上描述。加密者接口Encryptor定义在同目录下的internals.go中,在aes.go中实现(只能是aes,因为只有aes是用来加密的):aescbcpkcs7Encryptor。参看SW bccsp专用生成函数
New
的encryptors赋值部分,只有aesPrivateKey - aescbcpkcs7Encryptor被使用。这里加密的实现是,从该bccsp实例的加密者集合成员encryptors获取类型为reflect.TypeOf(k)
的加密者encryptor,然后直接调用encryptor的接口Encrypt,追溯,aescbcpkcs7Encryptor使用了aes库的加密流程进行加密。 - Decrypt - 解密与加密类似,过程类似,也是只有aes配套实现,最终使用aes库解密流程进行解密。
- GetXXX - GetXXX系列接口,获取实例对象中的数据,略。
在fabirc源码解析12——peer的MSP服务文中背书检验部分的第3、4点,涉及到了包含在msp中的bccsp对象成员,使用了bccsp对象的GetHashOpt
、Hash
、KeyImport
、Verify
等接口用以生成identities对象或identities自身一些接口实现。在此可以对看。
pkcs11实现方式
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库中的解释相对过于简单。
目录结构:
- /fabric/bccsp/pkcs11
- impl.go - bccsp的pkcs11实现impl
- conf.go - 定义了bccsp服务的配置和PKCS11Opts、FileKeystoreOpts、DummyKeystoreOpts选项
- pkcs11.go - 以pkcs11库为基础,包装各种pkcs11功能,实现了impl基于pkcs11的内调函数,和一些bccsp服务使用到的独立的内调函数
- ———————————————————————–
- aes.go - 实现aes类型的生成key、加密、解密函数
- ecdsa.go - 实现impl在ecdsa技术下的签名函数
signECDSA
和鉴定函数verifyECDSA
- ———————————————————————–
- aeskey.go - 实现aes类型的Key接口,只实现私匙aesPrivateKey
- ecdsakey.go - 实现ecdsa类型的Key接口,实现了公匙ecdsaPublicKey,私匙ecdsaPrivateKey
- rsakey.go - 实现了rsa类型的Key接口,实现了公匙rsaPublicKey,私匙rsaPrivateKey
- ———————————————————————–
- dummyks.go - dummy类型的KeyStore接口实现DummyKeyStore
- fileks.go - file类型的KeyStore接口实现FileBasedKeyStore
BCCSP接口实现:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
BCCSP的pkcs11实现的骨架在impl.go中与sw的实现基本一致,只是追溯到最终实现的语句时,sw实现是使用crypto库下的各个包进行签名,加密,密匙导入等,而pkcsll则用pkcs11包对数据进行了多一层的处理,使用pkcs11提供的上下文(pkcs11.Ctx)和会话(SessionHandle)之上对签名,密匙,加密等进行管理,这也是pkcs11.go文件的作用。两者最大的不同是一个面向软件,一个面向硬件,pkcs11自身又非常的冗杂,因此在此只讲pkcs11中与安全硬件模块建立连接的loadLib函数。
loadLib函数在pkcs11.go中定义,供专用生成函数New
使用。为建立与安全硬件模块的通信,进行了如下步骤:
- 根据所给的系统动态库路径lib加载动态库(如openCryptoki的动态库),调用
pkcs11.New(lib)
建立pkcs11实例ctx。ctx相当于fabric与安全硬件模块通信的桥梁:bccsp<–>ctx<–>驱动lib<–>安全硬件模块,只要驱动lib是按照pkcs11标准开发。 ctx.Initialize()
进行初始化。- 从
ctx.GetSlotList(true)
返回的列表中获取由label指定的插槽标识slot(这里的槽可以简单的理解为电脑主机上供安全硬件模块插入的槽,如USB插口,可能不止一个,每一个在系统内核中都有名字和标识号)。 - 尝试10次调用
ctx.OpenSession
打开一个会话session(会话就是通过通信路径与安全硬件模块建立连接,可以简单的理解为pkcs11的chan)。 - 登陆会话
ctx.Login
。 - 返回ctx,slot,会话对象session,用于赋值给impl实例成员ctx,slot,把session发送到sessions里。
关于pkcs11,还有一点可说的,就是SoftHSM库,它是一个模拟硬件实现的pkcsll,对应到的系统动态库可参看impl.go中FindPKCS11Lib测试函数中所涉及的,如Linux下的libsofthsm2.so。现阶段是没有安全硬件模块可以配合测试的,所以只有使用SoftHSM模拟测试,将libsofthsm2.so导入pkcs11对象。
BCCSP工厂
对应两种bccsp实现,这里也有两种bccsp工厂:pkcs11factory.go和swfactory.go。fabric中某一模块一旦涉及工厂factory,则说明该模块基本就是由工厂提供“窗口函数”,供其他模块调用。这里以swfactory为例进行讲解。
目录结构:
- /fabric/bccsp/factory/
- factory.go - 声明了默认bccsp实例defaultBCCSP,bccsp实例存储映射bccspMap等全局变量和这些变量的获取函数GetXXX,定义bccsp工厂接口。
- nopkcs11.go/pkcs11.go - 定义了两种版本的工厂选项FactoryOpts,初始化工厂函数InitFactories和获取指定bccsp实例函数GetBCCSPFromOpts。nopkcs11是默认版本,可条件编译指定使用哪种版本(编译时加入nopkcs11或!nopkcs11选项)。两种版本的差异集中在是否使用pkcs11上。
- opts.go - 定义了默认的工厂选项DefaultOpts。
- pkcs11factory.go - pkcs11类型的bccsp工厂实现PKCS11Factory。
- swfactory.go - sw类型的bccsp工厂实现SWFactory。还定义了sw版本的bccsp选项。
swfactory接口和实现:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
实现的代码本身比较简单,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。