Fabric 2.4.7 -- BCCSP源码分析

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.gofactory/no_pkcs11.gofactory/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=dG,G为椭圆曲线的生成元,或者叫基点。对ECDSA私钥进行派生实际上是根据现有的随机数 d d d得到新的随机数d,并生成相应公钥的过程。具体算法如下:
k ′ = k % ( n − 1 ) + 1 k'=k \% (n-1)+1 k=k%(n1)+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加解密的调用方法。

  1. 新生成一个密钥,再对其进行派生,使用派生后的密钥进行加解密
// 密钥生成
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{
    
    })
  1. 随机生成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,和两种验签器ecdsaPrivateKeyVerifierecdsaPublicKeyKeyVerifier。如名称所见,这两种验签器区别在于,一个传的是公钥实例,一个传的是私钥实例。因为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的国密改造工作。

猜你喜欢

转载自blog.csdn.net/qq_38236620/article/details/129530054