cprng-aes 0.2.4 → 0.2.5
raw patch · 3 files changed
+175/−67 lines, 3 filesdep +cprng-aesdep +criteriondep +mtldep ~basedep ~bytestringdep ~cryptocipherPVP ok
version bump matches the API change (PVP)
Dependencies added: cprng-aes, criterion, mtl
Dependency ranges changed: base, bytestring, cryptocipher
API changes (from Hackage documentation)
Files
- Benchmarks/Benchmarks.hs +39/−0
- Crypto/Random/AESCtr.hs +115/−62
- cprng-aes.cabal +21/−5
+ Benchmarks/Benchmarks.hs view
@@ -0,0 +1,39 @@+module Main where++import Criterion.Main++import qualified Data.ByteString as B+import Crypto.Random.AESCtr+import System.IO.Unsafe (unsafePerformIO)+import Data.IORef++gen rng n = fst (genRandomBytes rng n)++gen2 rngref n = unsafePerformIO $ do+ rng <- readIORef rngref+ let (b, rng2) = genRandomBytes rng n+ writeIORef rngref rng2+ return b++main = do+ rng <- makeSystem+ rngref <- newIORef rng++ defaultMain+ [ bgroup "generate random bytes (init)"+ [ bench "1" $ nf (gen rng) 1+ , bench "8" $ nf (gen rng) 8+ , bench "16" $ nf (gen rng) 16+ , bench "256" $ nf (gen rng) 256+ , bench "1024" $ nf (gen rng) 1024+ , bench "4096" $ nf (gen rng) 4096+ ]+ , bgroup "generate random bytes (continous)"+ [ bench "1" $ nf (gen2 rngref) 1+ , bench "8" $ nf (gen2 rngref) 8+ , bench "16" $ nf (gen2 rngref) 16+ , bench "256" $ nf (gen2 rngref) 256+ , bench "1024" $ nf (gen2 rngref) 1024+ , bench "4096" $ nf (gen2 rngref) 4096+ ]+ ]
Crypto/Random/AESCtr.hs view
@@ -14,11 +14,11 @@ -- {-# LANGUAGE CPP, PackageImports #-} module Crypto.Random.AESCtr- ( AESRNG- , make- , makeSystem- , genRandomBytes- ) where+ ( AESRNG+ , make+ , makeSystem+ , genRandomBytes+ ) where import Control.Applicative ((<$>)) @@ -36,41 +36,76 @@ import Data.Word 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 AESRNG = RNG- {-# UNPACK #-} !Word128- {-# UNPACK #-} !Word128- {-# UNPACK #-} !AES.Key+data RNG = RNG+ {-# UNPACK #-} !Word128+ {-# UNPACK #-} !Word128+ {-# UNPACK #-} !AES.Key +data AESRNG = AESRNG { aesrngState :: RNG+ , aesrngCache :: ByteString }+ instance Show AESRNG where- show _ = "aesrng[..]"+ 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+ where #ifdef CIPHER_AES- key = AES.initKey $ B.take 32 left2+ key = AES.initKey $ B.take 32 left2 #else- (Right key) = AES.initKey256 $ B.take 32 left2+ (Right key) = AES.initKey256 $ B.take 32 left2 #endif- (cnt, left2) = B.splitAt 16 left1- (iv, left1) = B.splitAt 16 b+ (cnt, left2) = B.splitAt 16 left1+ (iv, left1) = B.splitAt 16 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@@ -79,67 +114,85 @@ -- 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 $ RNG (get128 iv) (get128 cnt) key- where- (key, cnt, iv) = makeParams 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 = 16+chunkSize = 1024 -genNextChunk :: AESRNG -> (ByteString, AESRNG)+genNextChunk :: RNG -> (ByteString, RNG) genNextChunk (RNG iv counter key) = (chunk, newrng)- where- newrng = RNG (get128 chunk) (add1 counter) key-#ifdef CIPHER_AES- chunk = AES.encryptECB key bytes+ where+ newrng = RNG (get128 chunk) (add64 counter) key+ chunk = AES.genCTR key (AES.IV bytes) 1024+ bytes = put128 (iv `xor128` counter) #else- chunk = AES.encrypt key bytes+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- bytes = put128 (iv `xor128` counter) -- | Initialize a new AES RNG using the system entropy. makeSystem :: IO AESRNG makeSystem = ofRight . make <$> getEntropy 64- where- ofRight (Left _) = error "ofRight on a Left value"- ofRight (Right x) = x+ where+ ofRight (Left _) = error "ofRight on a Left value"+ ofRight (Right x) = 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.+-- it generate randomness by block of chunkSize bytes and will returns+-- a block bigger or equal to the size requested.+genRandomBytesState :: RNG -> Int -> (ByteString, RNG)+genRandomBytesState rng n+ | n <= chunkSize = genNextChunk rng+ | otherwise = let (bs, rng') = acc 0 [] rng+ in (B.concat bs, rng')+ 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 16 = genNextChunk rng-genRandomBytes rng n = (B.concat $ map fst list, snd $ last list)- where- list = helper rng n- helper _ 0 = []- helper g i =- let (b, g') = genNextChunk g in- if chunkSize >= i- then [ (B.take i b, g') ]- else (b, g') : helper g' (i-chunkSize)+genRandomBytes 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 }) +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+ instance CryptoRandomGen AESRNG where- newGen = make- genSeedLength = 64- genBytes len rng = Right $ genRandomBytes rng len- reseed b rng@(RNG _ cnt1 _)- | B.length b < 64 = Left NotEnoughEntropy- | otherwise = Right $ RNG (get128 r16 `xor128` get128 iv2) (cnt1 `xor128` get128 cnt2) key2- where- (r16, _) = genNextChunk rng- (key2, cnt2, iv2) = makeParams b+ 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) } instance RandomGen AESRNG where- next rng =- let (bs, rng') = genNextChunk rng 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'')- genRange _ = (0, 0x7fffffff)+ next rng =+ let (bs, rng') = genRandomBytes 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'')+ genRange _ = (0, 0x7fffffff)
cprng-aes.cabal view
@@ -1,5 +1,5 @@ Name: cprng-aes-Version: 0.2.4+Version: 0.2.5 Description: Simple crypto pseudo-random-number-generator with really good randomness property. .@@ -26,7 +26,7 @@ Build-Type: Simple Category: Cryptography stability: experimental-Cabal-Version: >=1.6+Cabal-Version: >=1.8 Homepage: http://github.com/vincenthz/hs-cprng-aes data-files: README.md @@ -34,20 +34,36 @@ 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- , cryptocipher- , cereal >= 0.3.0 && < 0.4.0 Exposed-modules: Crypto.Random.AESCtr ghc-options: -Wall - if os(linux) && flag(fastaes) && (arch(i386) || arch(x86_64))+ 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+ , cprng-aes+ , criterion+ , mtl source-repository head type: git