mirror of
https://github.com/emmansun/gmsm.git
synced 2025-04-22 02:06:18 +08:00
225 lines
6.7 KiB
Go
225 lines
6.7 KiB
Go
package drbg
|
|
|
|
import (
|
|
"crypto/cipher"
|
|
"encoding/binary"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/emmansun/gmsm/internal/subtle"
|
|
"github.com/emmansun/gmsm/sm4"
|
|
)
|
|
|
|
// CtrDrbg CTR DRBG structure, its instance is NOT goroutine safe!!!
|
|
type CtrDrbg struct {
|
|
BaseDrbg
|
|
cipherProvider func(key []byte) (cipher.Block, error)
|
|
key []byte
|
|
keyLen int
|
|
}
|
|
|
|
// NewCtrDrbg create one CTR DRBG instance
|
|
func NewCtrDrbg(cipherProvider func(key []byte) (cipher.Block, error), keyLen int, securityLevel SecurityLevel, gm bool, entropy, nonce, personalization []byte) (*CtrDrbg, error) {
|
|
hd := &CtrDrbg{}
|
|
|
|
hd.gm = gm
|
|
hd.setSecurityLevel(securityLevel)
|
|
|
|
// here for the min length, we just check <=0 now
|
|
if len(entropy) == 0 || (hd.gm && len(entropy) < 32) || len(entropy) >= MAX_BYTES {
|
|
return nil, errors.New("drbg: invalid entropy length")
|
|
}
|
|
|
|
// here for the min length, we just check <=0 now
|
|
if len(nonce) == 0 || (hd.gm && len(nonce) < 16) || len(nonce) >= MAX_BYTES>>1 {
|
|
return nil, errors.New("drbg: invalid nonce length")
|
|
}
|
|
|
|
if len(personalization) >= MAX_BYTES {
|
|
return nil, errors.New("drbg: personalization is too long")
|
|
}
|
|
|
|
hd.cipherProvider = cipherProvider
|
|
hd.keyLen = keyLen
|
|
temp := make([]byte, hd.keyLen)
|
|
block, err := cipherProvider(temp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
hd.seedLength = block.BlockSize() + keyLen
|
|
hd.v = make([]byte, block.BlockSize())
|
|
hd.key = make([]byte, hd.keyLen)
|
|
|
|
// seed_material = entropy_input || instantiation_nonce || personalization_string
|
|
seedMaterial := make([]byte, len(entropy)+len(nonce)+len(personalization))
|
|
copy(seedMaterial, entropy)
|
|
copy(seedMaterial[len(entropy):], nonce)
|
|
copy(seedMaterial[len(entropy)+len(nonce):], personalization)
|
|
// seed_material = Block_Cipher_df(seed_material, seed_length)
|
|
seedMaterial = hd.derive(seedMaterial, hd.seedLength)
|
|
// CTR_DRBG_Updae(seed_material, Key, V)
|
|
hd.update(seedMaterial)
|
|
|
|
hd.reseedCounter = 1
|
|
hd.reseedTime = time.Now()
|
|
return hd, nil
|
|
}
|
|
|
|
// NewNISTCtrDrbg create one CTR DRBG implementation which follows NIST standard
|
|
func NewNISTCtrDrbg(cipherProvider func(key []byte) (cipher.Block, error), keyLen int, securityLevel SecurityLevel, entropy, nonce, personalization []byte) (*CtrDrbg, error) {
|
|
return NewCtrDrbg(cipherProvider, keyLen, securityLevel, false, entropy, nonce, personalization)
|
|
}
|
|
|
|
// NewGMCtrDrbg create one CTR DRBG implementation which follows GM/T 0105-2021 standard
|
|
func NewGMCtrDrbg(securityLevel SecurityLevel, entropy, nonce, personalization []byte) (*CtrDrbg, error) {
|
|
return NewCtrDrbg(sm4.NewCipher, 16, securityLevel, true, entropy, nonce, personalization)
|
|
}
|
|
|
|
func (hd *CtrDrbg) Reseed(entropy, additional []byte) error {
|
|
// here for the min length, we just check <=0 now
|
|
if len(entropy) <= 0 || (hd.gm && len(entropy) < 32) || len(entropy) >= MAX_BYTES {
|
|
return errors.New("drbg: invalid entropy length")
|
|
}
|
|
|
|
if len(additional) >= MAX_BYTES {
|
|
return errors.New("drbg: additional input too long")
|
|
}
|
|
|
|
// seed_material = entropy_input || additional_input
|
|
var seedMaterial []byte
|
|
if len(additional) == 0 {
|
|
seedMaterial = entropy
|
|
} else {
|
|
seedMaterial = make([]byte, len(entropy)+len(additional))
|
|
copy(seedMaterial, entropy)
|
|
copy(seedMaterial[len(entropy):], additional)
|
|
}
|
|
// seed_material = Block_Cipher_df(seed_material, seed_length)
|
|
seedMaterial = hd.derive(seedMaterial, hd.seedLength)
|
|
// CTR_DRBG_Updae(seed_material, Key, V)
|
|
hd.update(seedMaterial)
|
|
|
|
hd.reseedCounter = 1
|
|
hd.reseedTime = time.Now()
|
|
return nil
|
|
}
|
|
|
|
func (hd *CtrDrbg) newBlockCipher(key []byte) cipher.Block {
|
|
block, err := hd.cipherProvider(key)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return block
|
|
}
|
|
|
|
func (hd *CtrDrbg) MaxBytesPerRequest() int {
|
|
if hd.gm {
|
|
return len(hd.v)
|
|
}
|
|
return MAX_BYTES_PER_GENERATE
|
|
}
|
|
|
|
// Generate CTR DRBG pseudorandom bits generate process.
|
|
func (hd *CtrDrbg) Generate(b, additional []byte) error {
|
|
if hd.NeedReseed() {
|
|
return ErrReseedRequired
|
|
}
|
|
outlen := len(hd.v)
|
|
if (hd.gm && len(b) > outlen) || (!hd.gm && len(b) > MAX_BYTES_PER_GENERATE) {
|
|
return errors.New("drbg: too many bytes requested")
|
|
}
|
|
|
|
// If len(additional_input) > 0, then
|
|
// additional_input = Block_Cipher_df(additional_input, seed_length)
|
|
// CTR_DRBG_Update(additional_input, Key, V)
|
|
if len(additional) > 0 {
|
|
additional = hd.derive(additional, hd.seedLength)
|
|
hd.update(additional)
|
|
}
|
|
|
|
block := hd.newBlockCipher(hd.key)
|
|
temp := make([]byte, outlen)
|
|
|
|
m := len(b)
|
|
limit := uint64(m+outlen-1) / uint64(outlen)
|
|
for i := 0; i < int(limit); i++ {
|
|
// V = (V + 1) mod 2^outlen)
|
|
addOne(hd.v, outlen)
|
|
// output_block = Encrypt(Key, V)
|
|
block.Encrypt(temp, hd.v)
|
|
copy(b[i*outlen:], temp)
|
|
}
|
|
hd.update(additional)
|
|
hd.reseedCounter++
|
|
return nil
|
|
}
|
|
|
|
func (cd *CtrDrbg) update(seedMaterial []byte) {
|
|
temp := make([]byte, cd.seedLength)
|
|
block := cd.newBlockCipher(cd.key)
|
|
|
|
outlen := block.BlockSize()
|
|
v := make([]byte, outlen)
|
|
output := make([]byte, outlen)
|
|
copy(v, cd.v)
|
|
for i := 0; i < (cd.seedLength+outlen-1)/outlen; i++ {
|
|
// V = (V + 1) mod 2^outlen
|
|
addOne(v, outlen)
|
|
// output_block = Encrypt(Key, V)
|
|
block.Encrypt(output, v)
|
|
copy(temp[i*outlen:], output)
|
|
}
|
|
// temp = temp XOR seed_material
|
|
subtle.XORBytes(temp, temp, seedMaterial)
|
|
// Key = leftmost(temp, key_length)
|
|
copy(cd.key, temp)
|
|
// V = rightmost(temp, outlen)
|
|
copy(cd.v, temp[cd.keyLen:])
|
|
}
|
|
|
|
// derive Block_Cipher_df
|
|
func (cd *CtrDrbg) derive(seedMaterial []byte, returnBytes int) []byte {
|
|
outlen := cd.seedLength - cd.keyLen
|
|
lenS := ((4 + 4 + len(seedMaterial) + outlen) / outlen) * outlen
|
|
S := make([]byte, lenS+outlen)
|
|
|
|
// S = counter || len(seed_material) || len(return_bytes) || seed_material || 0x80
|
|
// len(S) = ((outlen + 4 + 4 + len(seed_material) + 1 + outlen - 1) / outlen) * outlen
|
|
binary.BigEndian.PutUint32(S[outlen:], uint32(len(seedMaterial)))
|
|
binary.BigEndian.PutUint32(S[outlen+4:], uint32(returnBytes))
|
|
copy(S[outlen+8:], seedMaterial)
|
|
S[outlen+8+len(seedMaterial)] = 0x80
|
|
|
|
key := make([]byte, cd.keyLen)
|
|
for i := 0; i < cd.keyLen; i++ {
|
|
key[i] = byte(i)
|
|
}
|
|
blocks := (cd.seedLength + outlen - 1) / outlen
|
|
temp := make([]byte, blocks*outlen)
|
|
block := cd.newBlockCipher(key)
|
|
|
|
for i := 0; i < blocks; i++ {
|
|
binary.BigEndian.PutUint32(S, uint32(i))
|
|
copy(temp[i*outlen:], cd.bcc(block, S))
|
|
}
|
|
|
|
key = temp[:cd.keyLen]
|
|
X := temp[cd.keyLen:cd.seedLength]
|
|
temp = make([]byte, returnBytes)
|
|
block = cd.newBlockCipher(key)
|
|
for i := 0; i < (returnBytes+outlen-1)/outlen; i++ {
|
|
block.Encrypt(X, X)
|
|
copy(temp[i*outlen:], X)
|
|
}
|
|
return temp
|
|
}
|
|
|
|
func (cd *CtrDrbg) bcc(block cipher.Block, data []byte) []byte {
|
|
chainingValue := make([]byte, block.BlockSize())
|
|
for i := 0; i < len(data)/block.BlockSize(); i++ {
|
|
subtle.XORBytes(chainingValue, chainingValue, data[i*block.BlockSize():])
|
|
block.Encrypt(chainingValue, chainingValue)
|
|
}
|
|
return chainingValue
|
|
}
|