1.0 整体介绍
区块链密码服务提供商(BlockChain Cryptographic Service Provider)作为Fabric的核心组件之一,为其他组件提供了密码服务。其实现有三种,分别为SWBCCSP、PKCS11BCCSP、IDEMIXBCCSP。其中,SWBCCSP是基于软密码算法实现的CSP,其主要使用了Golang现有的密码库和哈希等算法编写而成。而PKCS11BCCSP则支持PKCS11标准,主要为基于硬件的密码组件而设计。至于IDEMIX,则是Fabric的另一大特色,支持使用基于零知识证明体系的密码服务,对隐私性的保护更强。软件实现的SW和硬件实现的PKCS11两种CSP,在Fabric程序中并不是同时支持的。在用于Fabric编译的makefile文件中,采用了tag的方式指定是否启用PKCS11。在BCCSP/factory/pkcs11.go
等文件中,也可以看到
// go:build pkcs11
// +build pkcs11
类似这样的条件编译语句。由于在实际应用中没有使用密码硬件支持,所以本次实践将全部关注SW实现的分析与改造,而忽略PKCS11实现与IDEMIX实现。
1.1 核心接口
对于Fabric源码中密码服务的提供者BCCSP而言,其核心接口BCCSP
的方法集,将是我们顺藤摸瓜,理解其整个结构的优质参考。
// BCCSP/bccsp.go line: 90
// BCCSP接口
type BCCSP interface {
// 密钥生成方法
KeyGen(opts KeyGenOpts) (k Key, err error)
// 密钥派生方法,根据已有密钥,通过密码学方法衍生得到新密钥
KeyDeriv(k Key, opts KeyDerivOpts) (dk Key, err error)
// 密钥导入方法,将原始二进制字节数据转换为指定密钥类型
KeyImport(raw interface{
}, opts KeyImportOpts) (k Key, err error)
// 获取密钥,根据密钥标识符SKI查找密钥
GetKey(ski []byte) (k Key, err error)
// 计算摘要,
Hash(msg []byte, opts HashOpts) (hash []byte, err error)
// 获取摘要方法,返回的是实现了标准库Hash接口的类型实例
GetHash(opts HashOpts) (h hash.Hash, err error)
// 签名(对大数据签名需要先做摘要)
Sign(k Key, digest []byte, opts SignerOpts) (signature []byte, err error)
// 验签
Verify(k Key, signature, digest []byte, opts SignerOpts) (valid bool, err error)
// 加密
Encrypt(k Key, plaintext []byte, opts EncrypterOpts) (ciphertext []byte, err error)
// 解密
Decrypt(k Key, ciphertext []byte, opts DecrypterOpts) (plaintext []byte, err error)
}
上面的接口被不同的工具集瓜分实现,BCCSP主要提供了以下工具集来对上述方法进行支持:
工具名称 | 介绍 |
---|---|
KeyStore | 对存储和加载密钥提供支持。主要分为dummyKeyStore和fileBasedKeyStore两种。 |
KeyGenerator | 对密钥生成提供支持。主要提供AES对称密钥、ECDSA密钥对的生成。 |
KeyImporter | 对密钥导入提供支持。支持对AES和ECDSA的密钥的不同编码格式进行导入。 |
KeyDerive | 对密钥派生提供支持。支持基于HMAC的密钥派生算法,以及ECDSA公私钥的分别派生。 |
Encryptor | 加密器。主要实现了AES算法CBC加密模式,支持PKCS7明文填充,以及初始向量明文扩充。 |
Decryptor | 解密器,用来解密(笑)。 |
Signer | 签名器,支持ECDSA算法签名,以及对S进行低位转化,以防止验签失败。 |
Verifier | 验签器,用来验签(笑)。 |
Hasher | 摘要计算器,支持SHA256摘要算法。 |
1.2 工厂方法
BCCSP的核心数据结构是BCCSP(但凡在结构或者接口名称中看到csp的心里有数就行),其在实现上采用了工厂模式,使用工厂方法控制CSP实例的生成。分析这块内容,我们需要重点关注3个文件(按顺序):① factory/factory.go
② factory/no_pkcs11.go
③ factory/swfactory.go
。
直观的来说,factory的作用就是生成并获取bccsp的实例。
// BCCSP/factory/factory.go line: 33
type BCCSPFactory interface {
// 工厂实例名,比如“sw”、"pkcs11"这样子
Name() string
// 关键方法,通过工厂实例获取BCCSP的实例。
Get(opts *FactoryOpts) (bccsp.BCCSP, error)
}
factory/factory.go
中定义了一些全局变量如下:
// BCCSP/factory/factory.go line: 33
var (
defaultBCCSP bccsp.BCCSP // default BCCSP
factoriesInitOnce sync.Once // factories' Sync on Initialization
factoriesInitError error // Factories' Initialization Error
// bootBCCSP是备用方案。在后面GetDefault()中我们可以看到,如果defaultBCCSP没有实例,则会启用bootBCCSP。
//(注释中表明,这玩意儿只会在测试中使用,正常情况下不可能使用到这个bootBCCSP)
bootBCCSP bccsp.BCCSP
bootBCCSPInitOnce sync.Once // bootBCCSP的实例化由该sync.Once的Do方法执行,即只会执行一遍
logger = flogging.MustGetLogger("bccsp")
)
从上面我们可以看到,工厂和defaultBCCSP
都是全局唯一的。bccsp实例的初始化通过调用下面的initBCCSP
方法来完成。这个方法本质上也是传入工厂实例,通过Get()方法生成CSP实例。
// BCCSP/factory/factory.go line: 58
func initBCCSP(f BCCSPFactory, config *FactoryOpts) (bccsp.BCCSP, error) {
csp, err := f.Get(config)
if err != nil {
return nil, errors.Errorf("Could not initialize BCCSP %s [%s]", f.Name(), err)
}
return csp, nil
}
在Fabric的其他组件中,若需要使用密码服务,则需要调用下面这一GetDefault()
方法来获取defaultBCCSP
,我们看到,如果defaultBCCSP为nil,它会启用备用方案,即使用SWFactory和默认配置生成bootBCCSP实现。当然,我们无须关注bootBCCSP的使用,因为正常情况下该CSP不会启用。
// BCCSP/factory/factory.go line: 42
func GetDefault() bccsp.BCCSP {
if defaultBCCSP == nil {
// 生成并启用bootBCCSP
logger.Debug("Before using BCCSP, please call InitFactories(). Falling back to bootBCCSP.")
bootBCCSPInitOnce.Do(func() {
var err error
bootBCCSP, err = (&SWFactory{
}).Get(GetDefaultOpts())
if err != nil {
panic("BCCSP Internal error, failed initialization with GetDefaultOpts!")
}
})
return bootBCCSP
}
return defaultBCCSP
}
好了,到这里我们通过factory.go
大致清楚了工厂用于实例化CSP的方法,以及其余组件通过获取默认CSP以使用密码服务这件事情。
接下来,我们关注CSP工厂自身方法以及如何实例化的具体实现。在Fabric源码的实现中,你会看到各种各样的Opts用于对实例的属性与行为进行控制,例如下面源码中的FactoryOpts
,就给出了BCCSP实例类型,以及使用的HASH算法与位数。
我们可以看到,在初始化工厂时,就会直接通过这一工厂实例调用initBCCSP
,一次性实例化defalutBCCSP。
// BCCSP/factory/nopkcs11.go line: 39
func initFactories(config *FactoryOpts) error {
// FactoryOpts内容
/* Default: "SW",
SW: &SwOpts{ Hash: "SHA2", Security: 256}
*/
// !!略去配置检查与加载代码!! 如果不传config,或者config配置不全,则启用默认配置
// Software-Based BCCSP
if config.Default == "SW" && config.SW != nil {
f := &SWFactory{
} // 先生成SWFactory,稍后会看到,该结构实现了Factory接口的Get()方法
var err error
defaultBCCSP, err = initBCCSP(f, config) // 在这里直接实例化defalutBCCSP
if err != nil {
return errors.Wrapf(err, "Failed initializing BCCSP")
}
}
if defaultBCCSP == nil {
return errors.Errorf("Could not find default `%s` BCCSP", config.Default)
}
return nil
}
最终,我们关注一下SWBCCSP工厂实例化SWBCCSP的过程。从上面我们知道,获取CSP实例的是工厂接口的Get()方法,下面我们来看一下SWBCCSP工厂的Get()方法。
// BCCSP/factory/swfactory.go line: 37
func (f *SWFactory) Get(config *FactoryOpts) (bccsp.BCCSP, error) {
// Validate arguments
if config == nil || config.SW == nil {
return nil, errors.New("Invalid config. It must not be nil.")
}
swOpts := config.SW
// step 1: 创建KeyStore
var ks bccsp.KeyStore
switch {
case swOpts.FileKeystore != nil:
fks, err := sw.NewFileBasedKeyStore(nil, swOpts.FileKeystore.KeyStorePath, false)
if err != nil {
return nil, errors.Wrapf(err, "Failed to initialize software key store")
}
ks = fks
default:
// Default to ephemeral key store
ks = sw.NewDummyKeyStore()
}
// step 2: 通过创建好的KeyStore,调用NewWithParams()方法获取swbccsp实例,该方法在BCCSP/sw/impl.go中
return sw.NewWithParams(swOpts.Security, swOpts.Hash, ks)
}
SWCSP工厂的Get()方法关键步骤就两步,其一创建KeyStore,其二是调用NewWithParams()
方法创建CSP实例。在之前核心数据部分的介绍中我们知道,Fabric BCCSP中的KeyStore结构用于存储和读取密钥数据,其有两种,一种为不支持读写的DummyKeyStore
、一种为基于文件系统加载密钥材料的FileBasedKeyStore
。上面的KeyStore在创建时,可以看到是根据swOpts选择KS的加载类型的。
小结:到目前为止,我们把BCCSP的工厂方法给串了一遍,知道了工厂如何创建,以及如何通过SW工厂获取SWcsp实例。下一节,我们将具体看看CSP的结构以及实例化过程。
1.3 CSP数据结构及其实例化过程
书接上节,在sw工厂的Get()
方法中,调用了NewWithParams()
方法创建swbccsp实例。现在我们来看一下这个NewWithParams()
方法里面都有啥。
// BCCSP/factory/new.go line: 39
func NewWithParams(securityLevel int, hashFamily string, keyStore bccsp.KeyStore) (bccsp.BCCSP, error) {
// Init config
conf := &config{
}
err := conf.setSecurityLevel(securityLevel, hashFamily)
if err != nil {
return nil, errors.Wrapf(err, "Failed initializing configuration at [%v,%v]", securityLevel, hashFamily)
}
swbccsp, err := New(keyStore) // ① 实际上swbccsp是通过New方法构建的
if err != nil {
return nil, err
}
// ② 这里面会通过AddWrapper方法将启用指定工具的类型,与相应工具的实例绑定到一起。这里不明白没关系,往后看。
// Set the Encryptors
swbccsp.AddWrapper(reflect.TypeOf(&aesPrivateKey{
}), &aescbcpkcs7Encryptor{
})
// Set the Decryptors
swbccsp.AddWrapper(reflect.TypeOf(&aesPrivateKey{
}), &aescbcpkcs7Decryptor{
})
// Set the Signers
swbccsp.AddWrapper(reflect.TypeOf(&ecdsaPrivateKey{
}), &ecdsaSigner{
})
// Set the Verifiers
swbccsp.AddWrapper(reflect.TypeOf(&ecdsaPrivateKey{
}), &ecdsaPrivateKeyVerifier{
})
swbccsp.AddWrapper(reflect.TypeOf(&ecdsaPublicKey{
}), &ecdsaPublicKeyKeyVerifier{
})
// Set the Hashers
swbccsp.AddWrapper(reflect.TypeOf(&bccsp.SHAOpts{
}), &hasher{
hash: conf.hashFunction})
// ... 省略一部分 ...
// Set the key generators
swbccsp.AddWrapper(reflect.TypeOf(&bccsp.ECDSAKeyGenOpts{
}), &ecdsaKeyGenerator{
curve: conf.ellipticCurve})
swbccsp.AddWrapper(reflect.TypeOf(&bccsp.ECDSAP256KeyGenOpts{
}), &ecdsaKeyGenerator{
curve: elliptic.P256()})
swbccsp.AddWrapper(reflect.TypeOf(&bccsp.ECDSAP384KeyGenOpts{
}), &ecdsaKeyGenerator{
curve: elliptic.P384()})
swbccsp.AddWrapper(reflect.TypeOf(&bccsp.AESKeyGenOpts{
}), &aesKeyGenerator{
length: conf.aesBitLength})
swbccsp.AddWrapper(reflect.TypeOf(&bccsp.AES256KeyGenOpts{
}), &aesKeyGenerator{
length: 32})
swbccsp.AddWrapper(reflect.TypeOf(&bccsp.AES192KeyGenOpts{
}), &aesKeyGenerator{
length: 24})
swbccsp.AddWrapper(reflect.TypeOf(&bccsp.AES128KeyGenOpts{
}), &aesKeyGenerator{
length: 16})
// Set the key deriver
swbccsp.AddWrapper(reflect.TypeOf(&ecdsaPrivateKey{
}), &ecdsaPrivateKeyKeyDeriver{
})
swbccsp.AddWrapper(reflect.TypeOf(&ecdsaPublicKey{
}), &ecdsaPublicKeyKeyDeriver{
})
swbccsp.AddWrapper(reflect.TypeOf(&aesPrivateKey{
}), &aesPrivateKeyKeyDeriver{
conf: conf})
// Set the key importers
swbccsp.AddWrapper(reflect.TypeOf(&bccsp.AES256ImportKeyOpts{
}), &aes256ImportKeyOptsKeyImporter{
})
// ... 省略一部分 ...
return swbccsp, nil
}
从上面我们可以看到,这个点有2个需要我们关注:① sw包内实现了bccsp接口的结构究竟是怎样的?以及这么实现意欲何为? ② Addwrapper()方法究竟是做什么的?顺着这三个点,我们基本上可以把SWBCCSP的实例化和使用方法给弄明白。
首先来看sw包内实现了bccsp接口的结构:CSP。
// BCCSP/factory/impl.go line: 34
type CSP struct {
ks bccsp.KeyStore // KeyStore是每一个CSP必须的,之前我们也看到了,得先构造KeyStore再传入New方法以获取CSP实例
KeyGenerators map[reflect.Type]KeyGenerator // reflect.Type作为Key类型,是Go语言反射的经典使用方式
KeyDerivers map[reflect.Type]KeyDeriver // 这样做的目的是,把一个类型和一个CSP工具给绑定在一起。
KeyImporters map[reflect.Type]KeyImporter // 打个比方,在上面的NewWithParams方法中,有这样一句AddWrapper调用
// swbccsp.AddWrapper(reflect.TypeOf(&bccsp.ECDSAKeyGenOpts{}), &ecdsaKeyGenerator{curve: conf.ellipticCurve})
Encryptors map[reflect.Type]Encryptor // 这个就是将ECCDSAKeyGenOpts的Type,与ecdsa的密钥生成器绑定在了一起
Decryptors map[reflect.Type]Decryptor // 这么多密码算法的密钥生成器工具,但是KeyGen方法只有这么一个,
Signers map[reflect.Type]Signer // 那么调用时具体该用哪个生成器,就靠传入的这个类型决定。
Verifiers map[reflect.Type]Verifier // 我们只需要一个传入bccsp.ECDSAKeyGenOpts类型
Hashers map[reflect.Type]Hasher // KeyGen方法里面会调用反射API获取reflect.Type接口类型,从而自CSP的KeyGeneratorsMap中找到ECDSA的生成器,其他的工具也是如此。
}
CSP的结构源码中,我们用注释解释了CSP的结构为什么这样写,以及AddWrapper方法作用。下面我们正式来看看AddWrapper方法。
// BCCSP/factory/impl.go line: 312
func (csp *CSP) AddWrapper(t reflect.Type, w interface{
}) error {
if t == nil {
return errors.Errorf("type cannot be nil")
}
if w == nil {
return errors.Errorf("wrapper cannot be nil")
}
switch dt := w.(type) {
// 可以看到, 工具结构实例被以空接口的形式传进来,并且使用switch搭配x.(type)用法获取工具类型
// 与下面相应的case进行匹配,从而将工具添加进正确的Map中。
case KeyGenerator:
csp.KeyGenerators[t] = dt
case KeyImporter:
csp.KeyImporters[t] = dt
case KeyDeriver:
csp.KeyDerivers[t] = dt
case Encryptor:
csp.Encryptors[t] = dt
case Decryptor:
csp.Decryptors[t] = dt
case Signer:
csp.Signers[t] = dt
case Verifier:
csp.Verifiers[t] = dt
case Hasher:
csp.Hashers[t] = dt
default:
return errors.Errorf("wrapper type not valid, must be on of: KeyGenerator, KeyDeriver, KeyImporter, Encryptor, Decryptor, Signer, Verifier, Hasher")
}
return nil
}
AddWrapper的绑定过程实际上就是如此直白。最后,我们再以加密为例,看看CSP的方法如何找到指定工具,并执行特定过程。
// BCCSP/factory/impl.go line: 274
func (csp *CSP) Encrypt(k bccsp.Key, plaintext []byte, opts bccsp.EncrypterOpts) ([]byte, error) {
// Validate arguments
if k == nil {
return nil, errors.New("Invalid Key. It must not be nil.")
}
// swbccsp.AddWrapper(reflect.TypeOf(&aesPrivateKey{}), &aescbcpkcs7Encryptor{})
// 看到了不,因为NewWithParams方法添加Wrapper时,给加密器绑定的是aesPrivateKey结构类型,该结构实现了bccsp.Key接口,因此可以被当做bccsp.Key传入。而在对bccsp.Key使用TypeOf获取Type接口类型,可以从反射包中的rtype结构中获知详细类型信息,从而自Map中取用到正确的加密器。
encryptor, found := csp.Encryptors[reflect.TypeOf(k)]
if !found {
return nil, errors.Errorf("Unsupported 'EncryptKey' provided [%v]", k)
}
// 然后,再通过该加密器执行加密方法即可
return encryptor.Encrypt(k, plaintext, opts)
}
CSP对BCCSP接口方法集中其他方法的实现大同小异,都是先利用反射获取指定工具,再调用工具接口方法执行对应操作即可。
到目前为止,我们已经看到了一个CSP实例化并装载完毕的全过程。下面的章节里,我们将把目光放在BCCSP的工具集中。因为国密算法的插入需要充分参考并实现工具集的接口,所以我们需要重点关注SW工具集的具体实现。SW工具集可以被划分两部分,一部分为密钥生命周期,包括密钥存储(KeyStore)、密钥生成(KeyGenerators)、密钥导入(KeyImporters)、密钥派生(KeyDerivers);另一部分为密码算法,包括加密(Encryptors)、解密(Decryptors)、签名(Signers)、验签(Verifiers)、摘要(Hashers)。
1.4 KeyStore实现
我们之前介绍过,这里复盘一波:KeyStore是一个密钥的存储系统,它可以被用来存储和加载密钥。fabric 2.4.7版本中的BCCSP有两种KeyStore,一种为dummyKeyStore,这种不能读写,正常情况下不使用;另一种为FileBasedKeyStore(后面缩写为fileks
),它基于文件系统来加载和存储密钥。也就是说,使用fileks
可以很便捷的将密钥实例序列化并存储在指定目录下,或者从该目录中加载密钥实例。这一节我们来看fileks
的实现。
在1.1 核心接口那一节,我们可以看到KeyStore接口的方法集只有三种方法,分别为ReadOnly、GetKey和StoreKey,readOnly是判断这个keyStore是不是只能读,先不管他。KeyStore的核心方法无非后面两种,一个存储密钥,一个加载密钥。
// BCCSP/factory/fileks.go line: 42
type fileBasedKeyStore struct {
path string // fileKS肯定要有自己的一个工作路径,所有密钥文件被管理在该路径下
readOnly bool //
isOpen bool //
pwd []byte // 这个口令用于对密钥文件进行加解密,也就是说通过fileKS存储的密钥可以不是明文PEM
m sync.Mutex // 同步互斥量
}
这个结构中规中矩好理解。下面我们看那两个核心方法,首先是存储密钥方法StoreKey
。
// BCCSP/factory/fileks.go line: 169
func (ks *fileBasedKeyStore) StoreKey(k bccsp.Key) (err error) {
if ks.readOnly {
return errors.New("read only KeyStore")
}
if k == nil {
return errors.New("invalid key. It must be different from nil")
}
switch kk := k.(type) {
// 首先,反射拉取密钥的类型
case *ecdsaPrivateKey: // ECDSA私钥: 调用 storePrivateKey 方法存储
err = ks.storePrivateKey(hex.EncodeToString(k.SKI()), kk.privKey)
if err != nil {
// 疑点请注意: SKI是个什么玩意儿下面会介绍
return fmt.Errorf("failed storing ECDSA private key [%s]", err)
}
case *ecdsaPublicKey: // ECDSA 公钥: 调用 storePublicKey 方法存储
err = ks.storePublicKey(hex.EncodeToString(k.SKI()), kk.pubKey)
if err != nil {
return fmt.Errorf("failed storing ECDSA public key [%s]", err)
}
case *aesPrivateKey: // AES对称密钥:调用 storeKey 方法存储
err = ks.storeKey(hex.EncodeToString(k.SKI()), kk.privKey)
if err != nil {
return fmt.Errorf("failed storing AES key [%s]", err)
}
default:
return fmt.Errorf("key type not reconigned [%s]", k)
}
return
}
可以看到,StoreKey方法会先判断密钥类型,并为每种类型选择对应的存储子方法。这些子方法只看函数签名的话,有个共同点,那就是两个参数都是SKI与密钥实例。那么SKI是个什么玩意儿,又怎么获取呢?我们先来康康。
SKI,subject key identify主题密钥标识,顾名思义是对一个密钥的标识。来看一个具体实现,
// BCCSP/factory/aeskey.go line: 169
func (k *aesPrivateKey) SKI() (ski []byte) {
hash := sha256.New()
// 这里的0x01是一个固定的前缀,目的是避免SKI与其他类型的密钥混淆。
hash.Write([]byte{
0x01})
hash.Write(k.privKey)
return hash.Sum(nil)
}
这是aeskey结构对SKI方法的具体实现,可以看到就是用SHA256方法对密钥计算了一个摘要。而ECDSA这类椭圆曲线密钥,就稍微复杂点就是了,但说白了也就是求摘要。
// BCCSP/factory/ecdsakey.go line: 39
func (k *ecdsaPrivateKey) SKI() []byte {
if k.privKey == nil {
return nil
}
// Marshall the public key, 看这里,要先序列化椭圆曲线、X、Y,再转哈希
raw := elliptic.Marshal(k.privKey.Curve, k.privKey.PublicKey.X, k.privKey.PublicKey.Y)
// Hash it
hash := sha256.New()
hash.Write(raw)
return hash.Sum(nil)
}
SKI这种密钥摘要主要用作后面保存密钥时文件名的一部分,而且加载密钥时也可以根据SKI从fileKS工作目录中快速检索。
说起存储密钥,可以看到根据密钥类型的不同,被细化成了3个子方法,但这些方法流程基本一致,可概括为:① 先转PEM ② 再用ioutils保存为文件。以ecdsa的私钥保存方法storePrivateKey
为例,
// BCCSP/factory/fileks.go line: 259
func (ks *fileBasedKeyStore) storePrivateKey(alias string, privateKey interface{
}) error {
rawKey, err := privateKeyToPEM(privateKey, ks.pwd) // 转PEM,注意每种密钥转PEM的方法不一样,是各自实现的
if err != nil {
// 而且这里传了KeyStore的pwd,说明密钥将以密文存储
logger.Errorf("Failed converting private key to PEM [%s]: [%s]", alias, err)
return err
}
// 写文件。在这之前给文件取个名,就是 SKI + "_" + 密钥类型(这里是"sk")
// 最后一个参数为file的mode,这里的0o600就是只能读,不能写,不能执行,这个大家都懂
err = ioutil.WriteFile(ks.getPathForAlias(alias, "sk"), rawKey, 0o600)
if err != nil {
logger.Errorf("Failed storing private key [%s]: [%s]", alias, err)
return err
}
return nil
}
了解了存储密钥,其实加载密钥就是一个反过来的过程,没啥意思。但咱们还是康康,
// BCCSP/factory/fileks.go line: 119
func (ks *fileBasedKeyStore) GetKey(ski []byte) (bccsp.Key, error) {
if len(ski) == 0 {
return nil, errors.New("invalid SKI. Cannot be of zero length")
} // 这里形参传的不是上面说的SKI,而是存储密钥时那个alias,即 SKI + "_" + 密钥类型
// 这里的getSuffix就是把那个后缀给取出来,也就是密钥的类型
suffix := ks.getSuffix(hex.EncodeToString(ski))
switch suffix {
case "key": // AES 对称密钥,用 loadKey 方法加载
key, err := ks.loadKey(hex.EncodeToString(ski))
if err != nil {
return nil, fmt.Errorf("failed loading key [%x] [%s]", ski, err)
}
return &aesPrivateKey{
key, false}, nil
case "sk": // ecdsa 私钥, 用 loadPrivateKey 方法加载
key, err := ks.loadPrivateKey(hex.EncodeToString(ski))
if err != nil {
return nil, fmt.Errorf("failed loading secret key [%x] [%s]", ski, err)
}
switch k := key.(type) {
case *ecdsa.PrivateKey:
return &ecdsaPrivateKey{
k}, nil
default:
return nil, errors.New("secret key type not recognized")
}
case "pk": // ecdsa 公钥, 用 loadPublicKey 方法加载
key, err := ks.loadPublicKey(hex.EncodeToString(ski))
if err != nil {
return nil, fmt.Errorf("failed loading public key [%x] [%s]", ski, err)
}
switch k := key.(type) {
case *ecdsa.PublicKey:
return &ecdsaPublicKey{
k}, nil
default:
return nil, errors.New("public key type not recognized")
}
default: // 注意这里如果没有匹配到指定类型的话,将使用searchKeystoreForSKI方法再搜一遍目录,以找到对应SKI的密钥
return ks.searchKeystoreForSKI(ski)
}
}
因为过程十分相似,就是先读文件,再从PEM解密并序列化为密钥实例这样一个过程,所以密钥的加载过程不再过多叙述。
最后我们思考下,什么情况下需要存储密钥?每个密钥都存储?找一下相关的场景。首先考虑密钥生成,生成完之后要存下来吗?
// BCCSP/factory/impl.go line: 119
// If the key is not Ephemeral, store it.
if !opts.Ephemeral() {
// Store the key 只有被标识为非临时的密钥才会在生成时存储下来
err = csp.ks.StoreKey(k)
if err != nil {
return nil, errors.Wrapf(err, "Failed storing key [%s]", opts.Algorithm())
}
}
由此可以看出来,BCCSP在使用过程中会产生很多没有使用KeyStore存储的临时密钥。实际上在2.x的早期版本中,有第三种KeyStore的存在,其被称为inMemoryKeyStore
,专门用于存储并管理这些临时密钥,但是2.4.7这个版本我没找到,估计是被去掉了。这些都是题外话,大家知道不是所有密钥都要以文件形式存储下来就行。
以上就是KeyStore的实现内容介绍。
1.5 KeyGenerators — 密钥生成器实现
密钥生成是密钥生命周期的一个重要环节,在BCCSP中,我们可以使用KeyGen方法根据生成配置快速生成各类所需的密钥实例。BCCSP中的密钥生成主要包括两类,其一为AES对称密钥;其二为ECDSA非对称公私钥。
这里是KeyGenerator结构以及KeyGen方法的实现,两类密钥的KeyGen方法结构都比较相似:
// BCCSP/sw/keygen.go line: 28
type ecdsaKeyGenerator struct {
curve elliptic.Curve
}
// ECDSA非对称公私钥
func (kg *ecdsaKeyGenerator) KeyGen(opts bccsp.KeyGenOpts) (bccsp.Key, error) {
privKey, err := ecdsa.GenerateKey(kg.curve, rand.Reader)
if err != nil {
return nil, fmt.Errorf("Failed generating ECDSA key for [%v]: [%s]", kg.curve, err)
}
return &ecdsaPrivateKey{
privKey}, nil
}
type aesKeyGenerator struct {
length int
}
// AES对称密钥
func (kg *aesKeyGenerator) KeyGen(opts bccsp.KeyGenOpts) (bccsp.Key, error) {
lowLevelKey, err := GetRandomBytes(int(kg.length))
if err != nil {
return nil, fmt.Errorf("Failed generating AES %d key [%s]", kg.length, err)
}
return &aesPrivateKey{
lowLevelKey, false}, nil
}
可以看到,ECDSA的密钥生成直接通过crypto/ecdsa库的GenerateKey方法一步到位。因为ECDSA的公钥是由私钥生成的,ecdsaPrivateKey中已经包含了公钥信息,所以ECDSA的KeyGen的方法只返回了一个*ecdsaPrivateKey类型实例。至于AES的密钥,则是由随机比特串组成,具体来说就是在上面的GetRandomBytes()
方法里,调用rand.Read方法获取随机串而来。
以上就是KeyGenerators的实现内容介绍。
1.6 KeyImporters — 密钥导入器实现
密钥导入器(KeyImporter)是密码服务提供程序(CSP)的一部分,用于将原始密钥材料转换为CSP可用的密钥对象。在密码学中,密钥是一段特定格式的二进制数据,它包含了加密和解密、签名和验证等操作的关键参数。密钥导入器的作用就是将这些关键参数提取出来,并将它们填充到CSP中的密钥对象中,使其可以进行各种加密和解密、签名和验证等操作。
BCCSP中的密钥导入器包括多个实现,每个实现用于处理一种特定类型的密钥。包括:
- aes256ImportKeyOptsKeyImporter
- hmacImportKeyOptsKeyImporter
- ecdsaPKIXPublicKeyImportOptsKeyImporter
- ecdsaPrivateKeyImportOptsKeyImporter
- ecdsaGoPublicKeyImportOptsKeyImporter
- x509PublicKeyImportOptsKeyImporter
例如,对于ECDSA密钥,可以使用密钥导入器ecdsaPKIXPublicKeyImportOptsKeyImporter
实现将PKIX格式的ECDSA公钥转换为CSP可用的公钥对象, Go 标准库中的 *ecdsa.PublicKey
类型,这个类型在 Go 语言中是表示 ECDSA 公钥的标准类型。也可以使用ecdsaGoPublicKeyImportOptsKeyImporter
。这些实现被称为密钥导入选项(KeyImportOpts),并作为参数传递给密钥导入器的KeyImport方法中。这些方法在keyimport.go
中可以找到,因为内容比较容易,这里不放代码。
以上就是KeyGenerators的实现内容介绍。
1.7 KeyDerivers — 密钥派生器实现
密钥派生是指根据已有密钥派生出新的密钥。在BCCSP中有三种实现,分别为:AES对称密钥派生、ECDSA公钥派生、ECDSA私钥派生。
AES密钥的派生算法借助于HMAC完成。
// BCCSP/sw/keyderiv.go line: 128
func (kd *aesPrivateKeyKeyDeriver) KeyDeriv(k bccsp.Key, opts bccsp.KeyDerivOpts) (bccsp.Key, error) {
// Validate opts
if opts == nil {
return nil, errors.New("Invalid opts parameter. It must not be nil.")
}
// 转换密钥类型
aesK := k.(*aesPrivateKey)
switch hmacOpts := opts.(type) {
// 判断密钥派生选项类型
case *bccsp.HMACTruncated256AESDeriveKeyOpts:
// Truncated256类型需要限制生成的密钥长度固定为256bit
mac := hmac.New(kd.conf.hashFunction, aesK.privKey)
mac.Write(hmacOpts.Argument())
// 对生成的摘要进行截断,只取256bit
return &aesPrivateKey{
mac.Sum(nil)[:kd.conf.aesBitLength], false}, nil
case *bccsp.HMACDeriveKeyOpts:
// 这种类型则直接生成摘要返回,生成摘要的长度是可以指定的
// 这种情况下一般是使用多出来的额外数据做一些其他事,并不是说一个任意长度的串都可以做AES密钥
mac := hmac.New(kd.conf.hashFunction, aesK.privKey)
mac.Write(hmacOpts.Argument())
return &aesPrivateKey{
mac.Sum(nil), true}, nil
default:
return nil, fmt.Errorf("Unsupported 'KeyDerivOpts' provided [%v]", opts)
}
}
ECDSA公钥和私钥的派生过程则比较相似。
ECDSA公钥是由私钥得到的,即: P = d ⋅ G P=d·G P=d⋅G,G为椭圆曲线的生成元,或者叫基点。对ECDSA私钥进行派生实际上是根据现有的随机数 d d d得到新的随机数d,并生成相应公钥的过程。具体算法如下:
k ′ = k % ( n − 1 ) + 1 k'=k \% (n-1)+1 k′=k%(n−1)+1
d ′ = ( d + k ′ ) % n d'=(d+k')\%n d′=(d+k′)%n
P = ( k ′ + 1 ) ⋅ P P=(k'+1)·P P=(k′+1)⋅P
而对公钥做密钥派生的过程与这是一样的,只是没有对新私钥进行计算,且只返回了新公钥。
确实,只保存新公钥而不保存对应的新私钥是没有意义的,因为如果需要使用这个新公钥进行解密操作,就必须要有对应的私钥。在ECDSA算法中,只对公钥做密钥派生主要是为了方便某些场景下的密钥管理,比如密钥轮换、密钥备份等,同时保证新生成的密钥与原始密钥有一定的关联性,以确保密钥的连续性和可追溯性。但是需要注意的是,在使用新公钥进行加密操作时,必须要同时保证对应的新私钥也能够及时生成和保存,以便在需要时进行解密操作。----我问chatGPT:“公钥由私钥d唯一确定,那就意味着公钥变,私钥也要变,所以只对公钥做密钥派生没有任何意义”
TODO: 所以我感觉这里很奇怪啊,为什么只对公钥做派生?等着以后回来填坑。
因为私钥派生的方法包含了公钥派生的方法,所以我们拿私钥派生算法来举个例子:
// BCCSP/sw/keyderiv.go line: 79
func (kd *ecdsaPrivateKeyKeyDeriver) KeyDeriv(key bccsp.Key, opts bccsp.KeyDerivOpts) (bccsp.Key, error) {
// Validate opts
if opts == nil {
return nil, errors.New("Invalid opts parameter. It must not be nil.")
}
ecdsaK := key.(*ecdsaPrivateKey)
reRandOpts, ok := opts.(*bccsp.ECDSAReRandKeyOpts)
if !ok {
return nil, fmt.Errorf("Unsupported 'KeyDerivOpts' provided [%v]", opts)
}
// 创建私钥结构,曲线保持不变,私钥d和公钥P需要重新生成
tempSK := &ecdsa.PrivateKey{
PublicKey: ecdsa.PublicKey{
Curve: ecdsaK.privKey.Curve,
X: new(big.Int),
Y: new(big.Int),
},
D: new(big.Int),
}
// k' = k \% (n-1)+1 生成变换参数K
k := new(big.Int).SetBytes(reRandOpts.ExpansionValue())
one := new(big.Int).SetInt64(1)
n := new(big.Int).Sub(ecdsaK.privKey.Params().N, one)
k.Mod(k, n)
k.Add(k, one)
// d'=(d+k')\%n 重新计算私钥
tempSK.D.Add(ecdsaK.privKey.D, k)
tempSK.D.Mod(tempSK.D, ecdsaK.privKey.PublicKey.Params().N)
tempX, tempY := ecdsaK.privKey.PublicKey.ScalarBaseMult(k.Bytes())
tempSK.PublicKey.X, tempSK.PublicKey.Y =
// P=(k'+1)·P 重新生成公钥
tempSK.PublicKey.Add(
ecdsaK.privKey.PublicKey.X, ecdsaK.privKey.PublicKey.Y,
tempX, tempY,
)
// 验证公钥是否在椭圆曲线上
isOn := tempSK.Curve.IsOnCurve(tempSK.PublicKey.X, tempSK.PublicKey.Y)
if !isOn {
return nil, errors.New("Failed temporary public key IsOnCurve check.")
}
return &ecdsaPrivateKey{
tempSK}, nil
}
以上就是密钥派生器的实现内容介绍。
1.8 Encryptors & Decryptors实现 — AES
在上面的几节里,我们主要阅读了工具集中与密钥生命周期相关的部分,包括密钥存储、密钥生成、密钥导入以及密钥派生。结合密钥生成、密钥导入以及密钥派生的工具,我们可以先看一下CSP加解密的调用方法。
- 新生成一个密钥,再对其进行派生,使用派生后的密钥进行加解密
// 密钥生成
k, err := csp.KeyGen(&bccsp.AESKeyGenOpts{
Temporary: false})
// HMAC密钥派生
hmcaedKey, err := provider.KeyDeriv(k, &bccsp.HMACTruncated256AESDeriveKeyOpts{
Temporary: false, Arg: []byte{
1}})
// 加密
ptext := []byte("Hello World")
ct, err := csp.Encrypt(hmcaedKey, msg, &bccsp.AESCBCPKCS7ModeOpts{
})
// 解密
pt, err := csp.Decrypt(hmcaedKey, ct, bccsp.AESCBCPKCS7ModeOpts{
})
- 随机生成32字节数据,使用密钥导入器生成密钥,再进行加解密。
// 生成随机字节
raw := make([]byte, 32)
rand.Reader.Read(raw)
// 导入密钥
k, err := provider.KeyImport(raw, &bccsp.AES256ImportKeyOpts{
Temporary: false})
// 加密
ptext := []byte("Hello World")
ct, err := csp.Encrypt(k, msg, &bccsp.AESCBCPKCS7ModeOpts{
})
// 解密
pt, err := csp.Decrypt(k, ct, bccsp.AESCBCPKCS7ModeOpts{
})
其实到这里为止,使用BCCSP的加解密器已经没有问题了,但咱们的最终目标是对Fabric进行国密改造,所以这里还需要看一下其AES加解密方法的具体实现。
BCCSP中,只提供了一个AES加解密器,即aescbcpkcs7Encryptor
。来看一下该结构的方法集中,对Encrypt方法的实现。
// BCCSP/sw/aes.go line: 188
func (e *aescbcpkcs7Encryptor) Encrypt(k bccsp.Key, plaintext []byte, opts bccsp.EncrypterOpts) ([]byte, error) {
switch o := opts.(type) {
case *bccsp.AESCBCPKCS7ModeOpts:
// 所有分支方法,都采用了PKCS Padding,也就是说不满足块大小整数倍的明文,最后未满的块会被填充满。
if len(o.IV) != 0 && o.PRNG != nil {
return nil, errors.New("Invalid options. Either IV or PRNG should be different from nil, or both nil.")
}
if len(o.IV) != 0 {
// 若初始向量长度不为0,则启用 AESCBCPKCS7EncryptWithIV
return AESCBCPKCS7EncryptWithIV(o.IV, k.(*aesPrivateKey).privKey, plaintext)
} else if o.PRNG != nil {
// 如果伪随机数选项不为nil,则启用 AESCBCPKCS7EncryptWithRand
return AESCBCPKCS7EncryptWithRand(o.PRNG, k.(*aesPrivateKey).privKey, plaintext)
}
// 如果没有上面两个幺蛾子,则直接填充加密即可。
return AESCBCPKCS7Encrypt(k.(*aesPrivateKey).privKey, plaintext)
case bccsp.AESCBCPKCS7ModeOpts:
// 如果 opts bccsp.EncrypterOpts 参数传的并非指针,则递归一层,将opts的指针传进去
return e.Encrypt(k, plaintext, &o)
default:
return nil, fmt.Errorf("Mode not recognized [%s]", opts)
}
}
在上面的代码中可以看,主要有三种方法,分别是基于初始向量IV的、基于伪随机数的,以及ECB AES。而且点开任意一种方法,其结构都是这样的:
// BCCSP/sw/aes.go line: 168
func AESCBCPKCS7EncryptWithIV(IV []byte, key, src []byte) ([]byte, error) {
// 先填充
tmp := pkcs7Padding(src)
// 再加密(用子方法)
return aesCBCEncryptWithIV(IV, key, tmp)
}
因此加密这里,我们的关注点实际上分为四块,分别为PKCS填充方法,以及三种加密子算法。先来看PKCS填充,
// BCCSP/sw/aes.go line: 50
func pkcs7Padding(src []byte) []byte {
// 看看有多少block, 最后一个不够一blcoksize的block会用数据填满
// 填充的数据的每一个字节都为差的字节数(这样做可以在解填充时,读取当初填了多少,以便取出原文)
padding := aes.BlockSize - len(src)%aes.BlockSize
padtext := bytes.Repeat([]byte{
byte(padding)}, padding)
return append(src, padtext...)
}
注释中也说了,在对密文进行解密后,需要解填充以获取原始明文,其实就是填充逆过程,先从填充数据里读取填充字节数,然后再切割即可。
// BCCSP/sw/aes.go line: 58
func pkcs7UnPadding(src []byte) ([]byte, error) {
length := len(src)
unpadding := int(src[length-1]) // 读取填充数据,获取填了多少字节
if unpadding > aes.BlockSize || unpadding == 0 {
return nil, errors.New("Invalid pkcs7 padding (unpadding > aes.BlockSize || unpadding == 0)")
}
// 按照填充数据的大小把padding字节拿掉便是
pad := src[len(src)-unpadding:]
for i := 0; i < unpadding; i++ {
if pad[i] != byte(unpadding) {
return nil, errors.New("Invalid pkcs7 padding (pad[i] != unpadding)")
}
}
return src[:(length - unpadding)], nil
}
下面我们来看原始AES,就是没有IV也没有伪随机数的那种实现。(前方难受)
// BCCSP/sw/aes.go line: 77
func aesCBCEncrypt(key, s []byte) ([]byte, error) {
return aesCBCEncryptWithRand(rand.Reader, key, s)
}
淦,这特喵的。所以压根没有原始AES,因为原始AES方法直接导向了使用伪随机数的AES加密。也就是说,不是初始向量,就是伪随机。浪费感情。
来,直接来看伪随机数的。
// BCCSP/sw/aes.go line: 81
func aesCBCEncryptWithRand(prng io.Reader, key, s []byte) ([]byte, error) {
if len(s)%aes.BlockSize != 0 {
return nil, errors.New("Invalid plaintext. It must be a multiple of the block size")
}
// 返回AES加密实例,即cipher.Block接口,该接口提供了encrypt和decrypt方法,所以不要被block这个名称给迷惑
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// 创建空字节切片,大小为数据长度 + 一个aes块的大小,
ciphertext := make([]byte, aes.BlockSize+len(s))
// 单独操作上述切片最前面的一个aes块大小的空间,将伪随机数填充进去
// 其实方法上讲就是初始向量CBC,但是这个初始向量直接由本方法随机取,没法从外面传就是了
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(prng, iv); err != nil {
return nil, err
}
// 将加密实例和初始向量传入,构建CBC加密器
mode := cipher.NewCBCEncrypter(block, iv)
// 加密消息,将密文存入切片的后面部分,前面的空间被用于存储初始向量密文
mode.CryptBlocks(ciphertext[aes.BlockSize:], s)
return ciphertext, nil
}
至于初始向量的方法,只是在上面这个方法的基础上,把取伪随机值填充切片头的部分,给换成了用传入的指定初始向量填充,其余部分是一样的。如下:
// BCCSP/sw/aes.go line: 103
copy(ciphertext[:aes.BlockSize], IV)
加密部分到此完毕。来看解密,BCCSP同样只提供了一个AES解密器,aescbcpkcs7Decryptor
,其实现的Decrypt方法如下:
// BCCSP/sw/aes.go line: 215
func (*aescbcpkcs7Decryptor) Decrypt(k bccsp.Key, ciphertext []byte, opts bccsp.DecrypterOpts) ([]byte, error) {
switch opts.(type) {
case *bccsp.AESCBCPKCS7ModeOpts, bccsp.AESCBCPKCS7ModeOpts:
// 可以看到,只有这一个可用解密选项
return AESCBCPKCS7Decrypt(k.(*aesPrivateKey).privKey, ciphertext)
default:
return nil, fmt.Errorf("Mode not recognized [%s]", opts)
}
}
往里点,来看AESCBCPKCS7Decrypt
方法:
// BCCSP/sw/aes.go line: 177
func AESCBCPKCS7Decrypt(key, src []byte) ([]byte, error) {
// 先解密
pt, err := aesCBCDecrypt(key, src)
if err == nil {
// 再解填充
return pkcs7UnPadding(pt)
}
return nil, err
}
这个就比较明朗了,解填充我们前面看过了,直接来看aesCBCDecrypt
方法:
// BCCSP/sw/aes.go line: 126
func aesCBCDecrypt(key, src []byte) ([]byte, error) {
// 和加密一样,通过NewCipher方法创建AES加解密实例
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
// 检查密文长度
if len(src) < aes.BlockSize {
return nil, errors.New("Invalid ciphertext. It must be a multiple of the block size")
}
// 将初始向量的密文和有效载荷密文分开
iv := src[:aes.BlockSize]
src = src[aes.BlockSize:]
// 检查有效载荷密文长度是否为blocksize整数倍
if len(src) % aes.BlockSize != 0 {
return nil, errors.New("Invalid ciphertext. It must be a multiple of the block size")
}
// 同样的方法,传入初始向量密文,创建CBC解密器
mode := cipher.NewCBCDecrypter(block, iv)
// 解密即可
mode.CryptBlocks(src, src)
return src, nil
}
以上就是加解密器的实现内容介绍。
1.9 Signers & Verifiers & Hashers实现 — ECDSA、Hash
ECDSA算法和Hash算法在BCCSP的主要应用是进行签名和验签。对一段消息进行数字签名时,需要先用Hash算法取得这段消息的摘要,然后再用ECDSA对这段摘要做签名。验签时同样也是解签后与原文的摘要进行比较。这属于密码学基础了,不多讲。
BCCSP中的Hasher实现较为简单,就是标准库里的sha256实现
// BCCSP/sw/hash.go line: 25
type hasher struct {
hash func() hash.Hash // Go/src/hash/hash.go interface Hash
}
func (c *hasher) Hash(msg []byte, opts bccsp.HashOpts) ([]byte, error) {
h := c.hash() // 使用结构中的属性,该属性类型为一函数,使用的是go官方hash库,也就是sha256算法
h.Write(msg)
return h.Sum(nil), nil
}
来看Signers和Verifiers。BCCSP提供了一种签名器ecdsaSigner
,和两种验签器ecdsaPrivateKeyVerifier
、ecdsaPublicKeyKeyVerifier
。如名称所见,这两种验签器区别在于,一个传的是公钥实例,一个传的是私钥实例。因为Go中的私钥类型
type PrivateKey struct {
PublicKey // 匿名元素,Go伪继承
D *big.Int
}
里面包含了公钥数据,所以用私钥实例验签也可,两者没啥本质区别,我们后面以公钥验签举例讲解。
先来看ecdsaSigner
的Sign方法,
// BCCSP/sw/ecdsa.go line: 27
func signECDSA(k *ecdsa.PrivateKey, digest []byte, opts bccsp.SignerOpts) ([]byte, error) {
// 返回大整数 R 和 S 。
// R表示在椭圆曲线上的一个点的横坐标,用于证明签名的合法性。在ECDSA算法中,
// R 是通过对消息的哈希值应用椭圆曲线上的点乘运算得到的。
// S 用于表示对消息的签名。
// S 是使用私钥对消息的哈希值和 R 进行计算得到的。
r, s, err := ecdsa.Sign(rand.Reader, k, digest)
if err != nil {
return nil, err
}
// 对S值进行处理
// S 是一个大整数,其取值范围是 1 到椭圆曲线的阶 N-1。
// 在一些实现中,s 取值可能会超过 N/2,这会导致签名不可验证。
// 因此大于 N/2 的 s 在 ToLowS方法 中会被转化为 N - s
s, err = utils.ToLowS(&k.PublicKey, s)
if err != nil {
return nil, err
}
// 将r和s按照ASN.1编码标准进行序列化,返回字节切片
return utils.MarshalECDSASignature(r, s)
}
接下来看验签,刚才提到,两种验签器没有本质区别,他们的Verify方法其实都是调用了verifyECDSA
这个子方法来完成的验签操作,唯一区别在于调用时传参那里,私钥版本的多了一步提取公钥如下:
// BCCSP/sw/ecdsa.go line: 67
func (v *ecdsaPrivateKeyVerifier) Verify(k bccsp.Key, signature, digest []byte, opts bccsp.SignerOpts) (bool, error) {
return verifyECDSA(&(k.(*ecdsaPrivateKey).privKey.PublicKey), signature, digest, opts)
}
既然如此,我们果断来看verifyECDSA
方法。
// BCCSP/sw/ecdsa.go line: 41
func verifyECDSA(k *ecdsa.PublicKey, signature, digest []byte, opts bccsp.SignerOpts) (bool, error) {
// 反序列化得到 r 和 s
r, s, err := utils.UnmarshalECDSASignature(signature)
if err != nil {
return false, fmt.Errorf("Failed unmashalling signature [%s]", err)
}
// 判断是否为lowS,若非lowS则无法正常验签
lowS, err := utils.IsLowS(k, s)
if err != nil {
return false, err
}
if !lowS {
return false, fmt.Errorf("Invalid S. Must be smaller than half the order [%s][%s].", s, utils.GetCurveHalfOrdersAt(k.Curve))
}
// 然后剩下的交给包即可
return ecdsa.Verify(k, digest, r, s), nil
}
其实大家也发现了,加解密器以及签名验签器这里,真正核心的内容调包实现的,因此这一部分读起来没有什么迷惑的地方,反而不如上面的密钥生命周期部分第一次看时让人有点迷糊。
以上就是签名验签器的实现内容介绍。
1.10 小节
BCCSP的源码阅读到此为止告一段落。分享下收获,① 建立起了我对CSP组件整体结构的认证,如果让我自己设计并实现一个CSP组件,至少我知道有哪些部分,以及每一部分要考虑哪些内容,这是最大的收获。② 再就是源码看着一坨挺麻烦,其实拆分开来,捋一下其实没有那么难懂。③ 最后就是Golang断断续续写过一些皮毛,看到这种Golang工程项目里的很多写法,受益匪浅。小小期待一下后面对一版本Fabric的国密改造工作。