cprng-aes 0.2.5 → 0.6.1
raw patch · 4 files changed
Files
- Benchmarks/Benchmarks.hs +3/−2
- Crypto/Random/AESCtr.hs +67/−136
- Crypto/Random/AESCtr/Internal.hs +42/−0
- cprng-aes.cabal +7/−20
Benchmarks/Benchmarks.hs view
@@ -4,14 +4,15 @@ import qualified Data.ByteString as B import Crypto.Random.AESCtr+import Crypto.Random import System.IO.Unsafe (unsafePerformIO) import Data.IORef -gen rng n = fst (genRandomBytes rng n)+gen rng n = fst (cprgGenerate n rng) gen2 rngref n = unsafePerformIO $ do rng <- readIORef rngref- let (b, rng2) = genRandomBytes rng n+ let (b, rng2) = cprgGenerate n rng writeIORef rngref rng2 return b
Crypto/Random/AESCtr.hs view
@@ -12,142 +12,62 @@ -- each block are generated the following way: -- aes (IV `xor` counter) -> 16 bytes output ---{-# LANGUAGE CPP, PackageImports #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE BangPatterns #-} module Crypto.Random.AESCtr ( AESRNG , make , makeSystem- , genRandomBytes ) where -import Control.Applicative ((<$>))- import Crypto.Random-import System.Random (RandomGen(..))-import System.Entropy (getEntropy)-#ifdef CIPHER_AES-import qualified "cipher-aes" Crypto.Cipher.AES as AES-#else-import qualified "cryptocipher" Crypto.Cipher.AES as AES-#endif+import Crypto.Random.AESCtr.Internal+import Control.Arrow (second) import Data.ByteString (ByteString) import qualified Data.ByteString as B -import Data.Word+import Data.Byteable import Data.Bits (xor, (.&.)) -#ifdef USE_CEREAL-import Data.Serialize-#else-import Foreign.Ptr-import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)-import Foreign.Storable-import qualified Data.ByteString.Internal as B-#endif--data Word128 = Word128 {-# UNPACK #-} !Word64 {-# UNPACK #-} !Word64--{-| An opaque object containing an AES CPRNG -}-data RNG = RNG- {-# UNPACK #-} !Word128- {-# UNPACK #-} !Word128- {-# UNPACK #-} !AES.Key--data AESRNG = AESRNG { aesrngState :: RNG- , aesrngCache :: ByteString }+-- | AES Counter mode Pseudo random generator.+--+-- Provide a very good Cryptographic pseudo random generator+-- that create pseudo random output based an AES cipher+-- used in counter mode, initialized from random key, random IV+-- and random nonce.+--+-- This CPRG uses 64 bytes of pure entropy to create its random state.+--+-- By default, this generator will automatically reseed after generating+-- 1 megabyte of data.+data AESRNG = AESRNG { aesrngState :: !RNG+ , aesrngEntropy :: EntropyPool+ , aesrngThreshold :: !Int -- ^ in number of generated block+ , aesrngCache :: !ByteString } instance Show AESRNG where show _ = "aesrng[..]" --- using serialize to grab a w128 as a non-negligeable cost,--- the Bytestring pointer manipulation are much faster.-#if USE_CEREAL--put128 :: Word128 -> ByteString-put128 (Word128 a b) = runPut (putWord64host a >> putWord64host b)--get128 :: ByteString -> Word128-get128 = either (\_ -> Word128 0 0) id . runGet (getWord64host >>= \a -> (getWord64host >>= \b -> return $ Word128 a b))--#else--put128 :: Word128 -> ByteString-put128 (Word128 a b) = B.unsafeCreate 16 (write64 . castPtr)- where write64 :: Ptr Word64 -> IO ()- write64 ptr = poke ptr a >> poke (ptr `plusPtr` 8) b--get128 :: ByteString -> Word128-get128 (B.PS ps s _) = B.inlinePerformIO $ do- let ptr = castPtr (unsafeForeignPtrToPtr ps `plusPtr` s) :: Ptr Word64- a <- peek ptr- b <- peek (ptr `plusPtr` 8)- return $ Word128 a b-#endif--xor128 :: Word128 -> Word128 -> Word128-xor128 (Word128 a1 b1) (Word128 a2 b2) = Word128 (a1 `xor` a2) (b1 `xor` b2)--#ifdef CIPHER_AES-add64 :: Word128 -> Word128-add64 (Word128 a b) = if b >= (0xffffffffffffffff-63) then Word128 (a+1) (b+64) else Word128 a (b+64)-#else-add1 :: Word128 -> Word128-add1 (Word128 a b) = if b == 0xffffffffffffffff then Word128 (a+1) 0 else Word128 a (b+1)-#endif--makeParams :: ByteString -> (AES.Key, ByteString, ByteString)-makeParams b = (key, cnt, iv)- where-#ifdef CIPHER_AES- key = AES.initKey $ B.take 32 left2-#else- (Right key) = AES.initKey256 $ B.take 32 left2-#endif- (cnt, left2) = B.splitAt 16 left1- (iv, left1) = B.splitAt 16 b+makeFrom :: EntropyPool -> B.ByteString -> AESRNG+makeFrom entPool b = AESRNG+ { aesrngState = rng+ , aesrngEntropy = entPool+ , aesrngThreshold = 1024 -- in blocks generated, so 1mb+ , aesrngCache = B.empty }+ where rng = makeRNG b --- | 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.+-- | make an AES RNG from an EntropyPool. ----- use `makeSystem` to not have to deal with the generator seed.-make :: B.ByteString -> Either GenError AESRNG-make b- | B.length b < 64 = Left NotEnoughEntropy- | otherwise = Right $ AESRNG { aesrngState = rng, aesrngCache = B.empty }- where- rng = RNG (get128 iv) (get128 cnt) key- (key, cnt, iv) = makeParams b--#ifdef CIPHER_AES-chunkSize :: Int-chunkSize = 1024--genNextChunk :: RNG -> (ByteString, RNG)-genNextChunk (RNG iv counter key) = (chunk, newrng)- where- newrng = RNG (get128 chunk) (add64 counter) key- chunk = AES.genCTR key (AES.IV bytes) 1024- bytes = put128 (iv `xor128` counter)-#else-chunkSize :: Int-chunkSize = 16--genNextChunk :: RNG -> (ByteString, RNG)-genNextChunk (RNG iv counter key) = (chunk, newrng)- where- newrng = RNG (get128 chunk) (add1 counter) key- chunk = AES.encrypt key bytes- bytes = put128 (iv `xor128` counter)-#endif+-- use `makeSystem` to not have to deal with the entropy pool.+make :: EntropyPool -> AESRNG+make entPool = makeFrom entPool b+ where !b = toBytes $ grabEntropy 64 entPool -- | Initialize a new AES RNG using the system entropy.+-- {-# DEPRECATED makeSystem "use cprgCreate with an entropy pool" #-} makeSystem :: IO AESRNG-makeSystem = ofRight . make <$> getEntropy 64- where- ofRight (Left _) = error "ofRight on a Left value"- ofRight (Right x) = x+makeSystem = make `fmap` createEntropyPool -- | get a Random number of bytes from the RNG. -- it generate randomness by block of chunkSize bytes and will returns@@ -157,42 +77,53 @@ | n <= chunkSize = genNextChunk rng | otherwise = let (bs, rng') = acc 0 [] rng in (B.concat bs, rng')- where- acc l bs g+ where acc l bs g | l * chunkSize >= n = (bs, g) | otherwise = let (b, g') = genNextChunk g in acc (l+1) (b:bs) g' -genRandomBytes :: AESRNG -> Int -> (ByteString, AESRNG)-genRandomBytes rng n+genRanBytesNoCheck :: AESRNG -> Int -> (ByteString, AESRNG)+genRanBytesNoCheck rng n | B.length (aesrngCache rng) >= n = let (b1,b2) = B.splitAt n (aesrngCache rng) in (b1, rng { aesrngCache = b2 }) | otherwise =- let (b, rng') = genRandomBytesState (aesrngState rng) n- (b1, b2) = B.splitAt n b- in (b1, rng { aesrngState = rng', aesrngCache = b2 })+ let (b, rng') = genRandomBytesState (aesrngState rng) n+ (b1, b2) = B.splitAt n b+ in (b1, rng { aesrngState = rng', aesrngCache = b2 }) -reseedState b rng@(RNG _ cnt1 _) = RNG (get128 r16 `xor128` get128 iv2) (cnt1 `xor128` get128 cnt2) key2- where (r16, _) = genNextChunk rng- (key2, cnt2, iv2) = makeParams b+-- | generate a random set of bytes+genRanBytes :: AESRNG -> Int -> (ByteString, AESRNG)+genRanBytes rng n = second reseedThreshold $ genRanBytesNoCheck rng n -instance CryptoRandomGen AESRNG where- newGen = make- genSeedLength = 64- genBytes len rng = Right $ genRandomBytes rng len- reseed b rng- | B.length b < 64 = Left NotEnoughEntropy- | otherwise = Right $ rng { aesrngState = reseedState b (aesrngState rng) }+reseedThreshold :: AESRNG -> AESRNG+reseedThreshold rng+ | getNbChunksGenerated (aesrngState rng) >= lvl =+ let newRngState = makeRNG $ toBytes $ grabEntropy 64 (aesrngEntropy rng)+ in rng { aesrngState = newRngState }+ | otherwise = rng+ where lvl = aesrngThreshold rng +instance CPRG AESRNG where+ cprgCreate = make+ cprgSetReseedThreshold lvl rng = reseedThreshold (rng { aesrngThreshold = if nbChunks > 0 then nbChunks else 1 })+ where nbChunks = lvl `div` chunkSize+ cprgGenerate len rng = genRanBytes rng len+ cprgGenerateWithEntropy len rng =+ let ent = toBytes $ grabEntropy len (aesrngEntropy rng)+ (bs, rng') = genRanBytes rng len+ in (B.pack $ B.zipWith xor ent bs, rng')+ cprgFork rng = let (b,rng') = genRanBytes rng 64+ in (rng', makeFrom (aesrngEntropy rng) b)++{- instance RandomGen AESRNG where next rng =- let (bs, rng') = genRandomBytes rng 16 in+ let (bs, rng') = genRanBytes rng 16 in let (Word128 a _) = get128 bs in let n = fromIntegral (a .&. 0x7fffffff) in (n, rng') split rng =- let (bs, rng') = genRandomBytes rng 64 in- case make bs of- Left _ -> error "assert"- Right rng'' -> (rng', rng'')+ let rng' = make (aesrngEntropy rng)+ in (rng, rng') genRange _ = (0, 0x7fffffff)+-}
+ Crypto/Random/AESCtr/Internal.hs view
@@ -0,0 +1,42 @@+-- |+-- Module : Crypto.Random.AESCtr.Internal+-- License : BSD-style+-- Maintainer : Vincent Hanquez <vincent@snarc.org>+-- Stability : stable+-- Portability : unknown+--+{-# LANGUAGE CPP #-}+{-# LANGUAGE BangPatterns #-}+module Crypto.Random.AESCtr.Internal where++import qualified Crypto.Cipher.AES as AES++import Data.ByteString (ByteString)+import qualified Data.ByteString as B++{-| An opaque object containing an AES CPRNG -}+data RNG = RNG !AES.AESIV !Int !AES.AES++getNbChunksGenerated :: RNG -> Int+getNbChunksGenerated (RNG _ c _) = c++makeParams :: ByteString -> (AES.AES, AES.AESIV)+makeParams b = key `seq` iv `seq` (key, iv)+ where (keyBS, r1) = B.splitAt 32 b+ (cnt, _) = B.splitAt 16 r1+ !key = AES.initAES keyBS+ !iv = AES.aesIV_ $ B.copy cnt++makeRNG :: ByteString -> RNG+makeRNG b = RNG iv 0 key+ where (key,iv) = makeParams b++chunkSize :: Int+chunkSize = 1024++genNextChunk :: RNG -> (ByteString, RNG)+genNextChunk (RNG counter nbChunks key) =+ chunk `seq` newrng `seq` (chunk, newrng)+ where+ newrng = RNG newCounter (nbChunks+1) key+ (chunk,newCounter) = AES.genCounter key counter chunkSize
cprng-aes.cabal view
@@ -1,5 +1,5 @@ Name: cprng-aes-Version: 0.2.5+Version: 0.6.1 Description: Simple crypto pseudo-random-number-generator with really good randomness property. .@@ -30,37 +30,24 @@ Homepage: http://github.com/vincenthz/hs-cprng-aes data-files: README.md -Flag fastaes- Description: Use fast AES if available- Default: True--Flag cereal- Description: Use cereal- Default: False- Library Build-Depends: base >= 3 && < 5 , bytestring- , random- , crypto-api >= 0.8- , entropy >= 0.2+ , byteable+ , crypto-random >= 0.0.7 && < 0.1+ , cipher-aes >= 0.2.9 && < 0.3+ Exposed-modules: Crypto.Random.AESCtr+ Other-modules: Crypto.Random.AESCtr.Internal ghc-options: -Wall - if flag(fastaes) && (arch(i386) || arch(x86_64))- cpp-options: -DCIPHER_AES- Build-Depends: cipher-aes >= 0.1 && < 0.2- else- Build-Depends: cryptocipher- if flag(cereal)- Build-Depends: cereal >= 0.3.0 && < 0.4.0- Benchmark bench-cprng-aes hs-source-dirs: Benchmarks Main-Is: Benchmarks.hs type: exitcode-stdio-1.0 Build-depends: base >= 4 && < 5 , bytestring+ , crypto-random , cprng-aes , criterion , mtl