gmsm/drbg/ctr_drbg.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
}