-- |-- Module : Crypto.Random.AESCtr-- License : BSD-style-- Maintainer : Vincent Hanquez <vincent@snarc.org>-- Stability : stable-- Portability : unknown---- this CPRNG is an AES based counter system.---- the internal size of fields are: 16 bytes IV, 16 bytes counter, 32 bytes key---- each block are generated the following way:-- aes (IV `xor` counter) -> 16 bytes output--moduleCrypto.Random.AESCtr(AESRNG,make,makeSystem,genRandomBytes)whereimportControl.Applicative((<$>))importCrypto.RandomimportSystem.Random(RandomGen(..))importSystem.Entropy(getEntropy)importqualifiedCrypto.Cipher.AESasAESimportData.ByteString(ByteString)importqualifiedData.ByteStringasBimportData.WordimportData.Bits(xor,(.&.))importData.SerializedataWord128=Word128!Word64!Word64{-| An opaque object containing an AES CPRNG -}dataAESRNG=RNG!ByteString!Word128!AES.KeyinstanceShowAESRNGwhereshow_="aesrng[..]"put128::Word128->ByteStringput128(Word128ab)=runPut(putWord64hosta>>putWord64hostb)get128::ByteString->Word128get128=either(\_->Word12800)id.runGet(getWord64host>>=\a->(getWord64host>>=\b->return$Word128ab))add1::Word128->Word128add1(Word128ab)=ifb==0xffffffffffffffffthenWord128(a+1)0elseWord128a(b+1)makeParams::ByteString->(AES.Key,ByteString,ByteString)makeParamsb=(key,cnt,iv)where(Rightkey)=AES.initKey256$B.take32left2(cnt,left2)=B.splitAt16left1(iv,left1)=B.splitAt16b-- | make an AES RNG from a bytestring seed. the bytestring need to be at least 64 bytes.-- if the bytestring is longer, the extra bytes will be ignored and will not take part in-- the initialization.---- use `makeSystem` to not have to deal with the generator seed.make::B.ByteString->EitherGenErrorAESRNGmakeb|B.lengthb<64=LeftNotEnoughEntropy|otherwise=Right$RNGiv(get128cnt)keywhere(key,cnt,iv)=makeParamsbchunkSize::IntchunkSize=16bxor::ByteString->ByteString->ByteStringbxorab=B.pack$B.zipWithxorabnextChunk::AESRNG->(ByteString,AESRNG)nextChunk(RNGivcounterkey)=(chunk,newrng)wherenewrng=RNGchunk(add1counter)keychunk=AES.encryptkeybytesbytes=iv`bxor`(put128counter)-- | Initialize a new AES RNG using the system entropy.makeSystem::IOAESRNGmakeSystem=ofRight.make<$>getEntropy64whereofRight(Left_)=error"ofRight on a Left value"ofRight(Rightx)=x-- | get a Random number of bytes from the RNG.-- it generate randomness by block of 16 bytes, but will truncate-- to the number of bytes required, and lose the truncated bytes.genRandomBytes::AESRNG->Int->(ByteString,AESRNG)genRandomBytesrngn=letlist=helperrngnin(B.concat$mapfstlist,snd$lastlist)wherehelper_0=[]helpergi=let(b,g')=nextChunkginifchunkSize>=ithen[(B.takeib,g')]else(b,g'):helperg'(i-chunkSize)instanceCryptoRandomGenAESRNGwherenewGen=makegenSeedLength=64genByteslenrng=Right$genRandomBytesrnglenreseedbrng@(RNG_cnt1_)|B.lengthb<64=LeftNotEnoughEntropy|otherwise=Right$RNG(r16`bxor`iv2)(get128(put128cnt1`bxor`cnt2))key2where(r16,_)=nextChunkrng(key2,cnt2,iv2)=makeParamsbinstanceRandomGenAESRNGwherenextrng=let(bs,rng')=nextChunkrnginlet(Word128a_)=get128bsinletn=fromIntegral(a.&.0x7fffffff)in(n,rng')splitrng=let(bs,rng')=genRandomBytesrng64incasemakebsofLeft_->error"assert"Rightrng''->(rng',rng'')genRange_=(0,0x7fffffff)