From 4d0f3e5293ae1af856bc9bb790d6758e44a310c7 Mon Sep 17 00:00:00 2001 From: "Henry D. Case" Date: Sun, 24 Jun 2018 00:08:53 +0100 Subject: [PATCH] AES-256 CTR_DRBG --- .travis.yml | 2 +- README.md | 2 + drbg/ctr_drbg.go | 153 ++++++++++++++++++++++++++++++++++++++++++ drbg/ctr_drbg_test.go | 86 ++++++++++++++++++++++++ rand/ctr_drbg.go | 53 --------------- rand/ctr_drbg_test.go | 21 ------ 6 files changed, 242 insertions(+), 75 deletions(-) create mode 100644 drbg/ctr_drbg.go create mode 100644 drbg/ctr_drbg_test.go delete mode 100644 rand/ctr_drbg.go delete mode 100644 rand/ctr_drbg_test.go diff --git a/.travis.yml b/.travis.yml index e00eca2..b5213d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,4 +2,4 @@ sudo: required language: go script: - - make test \ No newline at end of file + - go test -v ./... diff --git a/README.md b/README.md index 99508df..0f0e98e 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ Crypto primitives implementation in Go. * hash/ - cSHAKE (sha3 coppied from "golang.org/x/crypto") - SM3 +* rand/ + - CTR_DRBG with AES256 (NIST SP800-90A) ## Testing ``` diff --git a/drbg/ctr_drbg.go b/drbg/ctr_drbg.go new file mode 100644 index 0000000..5f0d23c --- /dev/null +++ b/drbg/ctr_drbg.go @@ -0,0 +1,153 @@ +// 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) + // Security strength for AES-256 as per SP800-57, 5.6.1 + c.strength = 256 + 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 + } + + 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 +} diff --git a/drbg/ctr_drbg_test.go b/drbg/ctr_drbg_test.go new file mode 100644 index 0000000..6e181d1 --- /dev/null +++ b/drbg/ctr_drbg_test.go @@ -0,0 +1,86 @@ +package drbg + +import ( + "bytes" + "encoding/hex" + "testing" +) + +func S2H(s string) []byte { + hex, e := hex.DecodeString(s) + if e != nil { + panic("Can't import private key") + } + return hex +} + +func TestNominal(t *testing.T) { + var entropy [16]byte + var data [48]byte + + c := NewCtrDrbg() + if !c.Init(entropy[:], nil) { + t.FailNow() + } + + c.Read(entropy[0:16], data[:]) + + exp := S2H("16BA361FA14563FB1E8BCF88932F9FA7") + if !bytes.Equal(exp, entropy[:]) { + t.FailNow() + } +} + +// TODO: should parse *.req file from here: https://raw.githubusercontent.com/coruus/nist-testvectors/master/csrc.nist.gov/groups/STM/cavp/documents/drbg/drbgtestvectors/drbgvectors_pr_false/CTR_DRBG.rsp +var vectors = []struct { + EntropyInput []byte + PersonalizationString []byte + EntropyInputReseed []byte + AdditionalInputReseed []byte + AdditionalInput1 []byte + AdditionalInput2 []byte + ReturnedBits []byte +}{ + // With Reseeding + { + S2H("99903165903fea49c2db26ed675e44cc14cb2c1f28b836b203240b02771e831146ffc4335373bb344688c5c950670291"), + []byte{}, + S2H("b4ee99fa9e0eddaf4a3612013cd636c4af69177b43eebb3c58a305b9979b68b5cc820504f6c029aad78a5d29c66e84a0"), + S2H("2d8c5c28b05696e74774eb69a10f01c5fabc62691ddf7848a8004bb5eeb4d2c5febe1aa01f4d557b23d7e9a0e4e90655"), + S2H("0dc9cde42ac6e856f01a55f219c614de90c659260948db5053d414bab0ec2e13e995120c3eb5aafc25dc4bdcef8ace24"), + S2H("711be6c035013189f362211889248ca8a3268e63a7eb26836d915810a680ac4a33cd1180811a31a0f44f08db3dd64f91"), + S2H("11c7a0326ea737baa7a993d510fafee5374e7bbe17ef0e3e29f50fa68aac2124b017d449768491cac06d136d691a4e80785739f9aaedf311bba752a3268cc531"), + }, + + { + S2H("ffad10100025a879672ff50374b286712f457dd01441d76ac1a1cd15c7390dd93179a2f5920d198bf34a1b76fbc21289"), + S2H("1d2be6f25e88fa30c4ef42e4d54efd957dec231fa00143ca47580be666a8c143a916c90b3819a0a7ea914e3c9a2e7a3f"), + S2H("6c1a089cae313363bc76a780139eb4f2f2048b1f6b07896c5c412bff0385440fc43b73facbb79e3a252fa01fe17ab391"), + []byte{}, + []byte{}, + []byte{}, + S2H("e053c7d4bd9099ef6a99f190a5fd80219437d642006672338da6e0fe73ca4d24ffa51151bfbdac78d8a2f6255046edf57a04626e9977139c6933274299f3bdff"), + }, +} + +func TestVector(t *testing.T) { + + for i := range vectors { + result := make([]byte, len(vectors[i].ReturnedBits)) + c := NewCtrDrbg() + if !c.Init(vectors[i].EntropyInput[:], vectors[i].PersonalizationString) { + t.FailNow() + } + + if len(vectors[i].EntropyInputReseed) > 0 { + c.Reseed(vectors[i].EntropyInputReseed[:], vectors[i].AdditionalInputReseed[:]) + } + c.Read(result[:], vectors[i].AdditionalInput1) + c.Read(result[:], vectors[i].AdditionalInput2) + + if !bytes.Equal(vectors[i].ReturnedBits[:], result[:]) { + t.FailNow() + } + + } +} diff --git a/rand/ctr_drbg.go b/rand/ctr_drbg.go deleted file mode 100644 index 38951de..0000000 --- a/rand/ctr_drbg.go +++ /dev/null @@ -1,53 +0,0 @@ -import rand - -import ( - "crypto/aes" - "crypto/cipher" -) - -// Constants below correspond to AES-256, which is currently -// the only block cipher supported. -const { - Blocklen = 16 - Keylen = 32 -} - -type CtrDrbg struct { - v uint - keylen uint // OZAPTF: is it needed? - counter uint - strength uint - resistance bool -} - -func (c *CtrDrbg) update(data []byte) { - -} - -func New() *CtrDrbg { - c = new(CtrDrbg) - c.key = make([]byte, 0, Keylen) - c.v = make([]byte, 0, Blocklen) - // Security strength for AES-256 as per SP800-57, 5.6.1 - c.strength = 256 - return c -} - -func (c *CtrDrbg) Init(entropy []byte, personalization []byte, strength uint) bool { - - if len(entropy) < (c.strength/8) { - return nil - } - - // does enropyt needs to have some minimal length? - seed := make([]byte, 0, c.strength / 8) - - c.update(seed) - c.counter = 1 - return c - -} -func (c *CtrDrbg) Update() {} -func (c *CtrDrbg) Read(b []byte) (n int, err error) { - -} \ No newline at end of file diff --git a/rand/ctr_drbg_test.go b/rand/ctr_drbg_test.go deleted file mode 100644 index 0eabcdb..0000000 --- a/rand/ctr_drbg_test.go +++ /dev/null @@ -1,21 +0,0 @@ -import rand - -import ( - "testing" - "fmt" - "io" - "os" - - "crypto/aes" - "crypto/cipher" -) - -func TestNominal(t* testing.T) { - block, err := aes.NewCipher(key) - if err != nil { - panic(err) - } - - stream := cipher.NewCTR(block, iv) - stream.XORKeyStream(pt, ct) -} \ No newline at end of file