// This is initial implementation of CTR_DRBG with AES-256. Code is tested // and functionaly correct. Nevertheless it will be changed // // TODO: Following things still need to be done // * Add other AES key lengts // * Validate sizes from table 3 of SP800-90A // * Improve reseeding so that code returns an error when reseed is needed // * Add case with derivation function (maybe) // * Code cleanup // * Implement benchmark // * Add rest of the test vectors from CAVP package drbg import ( "crypto/aes" ) // Constants below correspond to AES-256, which is currently // the only block cipher supported. const ( BlockLen = 16 KeyLen = 32 SeedLen = BlockLen + KeyLen ) type CtrDrbg struct { v []byte key []byte counter uint strength uint resistance bool } func NewCtrDrbg() *CtrDrbg { var c = new(CtrDrbg) return c } func (c *CtrDrbg) inc() { for i := BlockLen - 1; i >= 0; i-- { if c.v[i] == 0xff { c.v[i] = 0x00 } else { c.v[i]++ break } } } func (c *CtrDrbg) Init(entropy, personalization []byte) bool { var lsz int var seedBuf [SeedLen]byte // Minimum entropy input (SP800-90A, 10.2.1) if len(entropy) < int(c.strength/8) { return false } // Security strength for AES-256 as per SP800-57, 5.6.1 c.strength = 256 lsz = len(entropy) if lsz > SeedLen { lsz = SeedLen } copy(seedBuf[:], entropy[:lsz]) lsz = len(personalization) if lsz > SeedLen { lsz = SeedLen } for i := 0; i < lsz; i++ { seedBuf[i] ^= personalization[i] } c.key = make([]byte, KeyLen) c.v = make([]byte, BlockLen) c.update(seedBuf[:]) c.counter = 1 return true } func (c *CtrDrbg) update(data []byte) { var buf [3 * BlockLen]byte if len(data) != SeedLen { // OZAPTF: panic? panic("Provided data is not equal to strength/8") } for i := 0; i < 3*BlockLen; i += BlockLen { c.inc() // Ignore error => NewCipher returns error when c.key has unexpected size encBlock, _ := aes.NewCipher(c.key) encBlock.Encrypt(buf[i:], c.v) } for i := 0; i < len(buf); i++ { buf[i] ^= data[i] } copy(c.key, buf[:KeyLen]) copy(c.v, buf[KeyLen:]) } func (c *CtrDrbg) Reseed(entropy, data []byte) { var seedBuf [SeedLen]byte var lsz int lsz = len(entropy) if lsz > SeedLen { lsz = SeedLen } copy(seedBuf[:], entropy[:lsz]) lsz = len(data) if lsz > SeedLen { lsz = SeedLen } for i := 0; i < lsz; i++ { seedBuf[i] ^= data[i] } c.update(seedBuf[:]) c.counter = 1 } func (c *CtrDrbg) Read(b, ad []byte) (n int, err error) { var seedBuf [SeedLen]byte // TODO: check reseed_counter > reseed_interval if len(ad) > 0 { // pad additional data with zeros if needed copy(seedBuf[:], ad) c.update(seedBuf[:]) } // OZAPTF: would be better not need to allocate that buf := make([]byte, ((len(b)+BlockLen)/BlockLen)*BlockLen) for i := 0; i < len(b); i += BlockLen { c.inc() // Ignore error => NewCipher returns error when c.key has unexpected size encBlock, _ := aes.NewCipher(c.key) encBlock.Encrypt(buf[i:], c.v) } copy(b, buf[:len(b)]) c.update(seedBuf[:]) c.counter += 1 return len(b), nil }