packages feed

crypto-rng 0.2.0.1 → 0.3.0.0

raw patch · 6 files changed

+162/−139 lines, 6 filesdep +entropydep −DRBGdep −crypto-apidep ~bytestringdep ~exceptionsdep ~monad-controlPVP ok

version bump matches the API change (PVP)

Dependencies added: entropy

Dependencies removed: DRBG, crypto-api

Dependency ranges changed: bytestring, exceptions, monad-control, mtl, random, transformers-base

API changes (from Hackage documentation)

- Crypto.RNG: instance System.Random.Internal.RandomGen Crypto.RNG.RNG
- Crypto.RNG: randomIO :: Uniform a => CryptoRNGState -> IO a
- Crypto.RNG: randomRIO :: UniformRange a => (a, a) -> CryptoRNGState -> IO a
- Crypto.RNG: unsafeCryptoRNGState :: MonadIO m => [ByteString] -> m CryptoRNGState
+ Crypto.RNG: instance System.Random.Internal.StatefulGen Crypto.RNG.CryptoRNGState GHC.Types.IO
+ Crypto.RNG.Unsafe: data RNGState
+ Crypto.RNG.Unsafe: data RNGT m a
+ Crypto.RNG.Unsafe: instance Control.Monad.Base.MonadBase b m => Control.Monad.Base.MonadBase b (Crypto.RNG.Unsafe.RNGT m)
+ Crypto.RNG.Unsafe: instance Control.Monad.Catch.MonadCatch m => Control.Monad.Catch.MonadCatch (Crypto.RNG.Unsafe.RNGT m)
+ Crypto.RNG.Unsafe: instance Control.Monad.Catch.MonadMask m => Control.Monad.Catch.MonadMask (Crypto.RNG.Unsafe.RNGT m)
+ Crypto.RNG.Unsafe: instance Control.Monad.Catch.MonadThrow m => Control.Monad.Catch.MonadThrow (Crypto.RNG.Unsafe.RNGT m)
+ Crypto.RNG.Unsafe: instance Control.Monad.Error.Class.MonadError e m => Control.Monad.Error.Class.MonadError e (Crypto.RNG.Unsafe.RNGT m)
+ Crypto.RNG.Unsafe: instance Control.Monad.Fail.MonadFail m => Control.Monad.Fail.MonadFail (Crypto.RNG.Unsafe.RNGT m)
+ Crypto.RNG.Unsafe: instance Control.Monad.IO.Class.MonadIO m => Control.Monad.IO.Class.MonadIO (Crypto.RNG.Unsafe.RNGT m)
+ Crypto.RNG.Unsafe: instance Control.Monad.IO.Class.MonadIO m => Crypto.RNG.Class.CryptoRNG (Crypto.RNG.Unsafe.RNGT m)
+ Crypto.RNG.Unsafe: instance Control.Monad.Trans.Class.MonadTrans Crypto.RNG.Unsafe.RNGT
+ Crypto.RNG.Unsafe: instance Control.Monad.Trans.Control.MonadBaseControl b m => Control.Monad.Trans.Control.MonadBaseControl b (Crypto.RNG.Unsafe.RNGT m)
+ Crypto.RNG.Unsafe: instance Control.Monad.Trans.Control.MonadTransControl Crypto.RNG.Unsafe.RNGT
+ Crypto.RNG.Unsafe: instance GHC.Base.Alternative m => GHC.Base.Alternative (Crypto.RNG.Unsafe.RNGT m)
+ Crypto.RNG.Unsafe: instance GHC.Base.Applicative m => GHC.Base.Applicative (Crypto.RNG.Unsafe.RNGT m)
+ Crypto.RNG.Unsafe: instance GHC.Base.Functor m => GHC.Base.Functor (Crypto.RNG.Unsafe.RNGT m)
+ Crypto.RNG.Unsafe: instance GHC.Base.Monad m => GHC.Base.Monad (Crypto.RNG.Unsafe.RNGT m)
+ Crypto.RNG.Unsafe: instance GHC.Base.MonadPlus m => GHC.Base.MonadPlus (Crypto.RNG.Unsafe.RNGT m)
+ Crypto.RNG.Unsafe: mapRNGT :: (m a -> n b) -> RNGT m a -> RNGT n b
+ Crypto.RNG.Unsafe: newRNGState :: MonadIO m => Int -> m RNGState
+ Crypto.RNG.Unsafe: runRNGT :: RNGState -> RNGT m a -> m a
+ Crypto.RNG.Unsafe: withRNG :: MonadIO m => RNGState -> (StdGen -> (a, StdGen)) -> m a
+ Crypto.RNG.Unsafe: withRNGState :: (RNGState -> m a) -> RNGT m a
- Crypto.RNG: randomBytesIO :: ByteLength -> CryptoRNGState -> IO ByteString
+ Crypto.RNG: randomBytesIO :: Int -> CryptoRNGState -> IO ByteString
- Crypto.RNG.Class: randomBytes :: CryptoRNG m => ByteLength -> m ByteString
+ Crypto.RNG.Class: randomBytes :: CryptoRNG m => Int -> m ByteString

Files

ChangeLog.md view
@@ -1,5 +1,9 @@ # Revision history for crypto-rng +## 0.3.0.0  -- 2022-02-21++* Use the entropy package instead of DRBG.+ ## 0.2.0.1  -- 2022-02-16  * Better selection strategy for picking generators from the pool.
crypto-rng.cabal view
@@ -1,9 +1,9 @@ name:                crypto-rng-version:             0.2.0.1+version:             0.3.0.0 synopsis:            Cryptographic random number generator. -description:         Convenient wrapper for the cryptographic random generator-                     provided by the DRBG package.+description:         Convenient wrapper for the source of random bytes+                     provided by the @entropy@ package.  homepage:            https://github.com/scrive/crypto-rng license:             BSD3@@ -14,7 +14,7 @@ copyright:           Scrive AB category:            Crypto build-type:          Simple-tested-with:         GHC ==8.8.4 || ==8.10.7 || ==9.0.2+tested-with:         GHC ==8.8.4 || ==8.10.7 || ==9.0.2 || ==9.2.1 extra-source-files:  ChangeLog.md cabal-version:       >=1.10 @@ -28,17 +28,17 @@   exposed-modules:     Crypto.RNG                        Crypto.RNG.Class                        Crypto.RNG.Utils+                       Crypto.RNG.Unsafe -  build-depends:       base              >= 4.13    && < 5,-                       DRBG              >= 0.5.5  && < 0.6,-                       bytestring        >= 0.10.8 && < 0.12,-                       crypto-api        >= 0.13.2 && < 0.14,-                       mtl               >= 2.2.1  && < 2.3,-                       exceptions        >= 0.8.3  && < 0.11,-                       monad-control     >= 1.0.1  && < 1.1,-                       primitive         >= 0.7,-                       random            >= 1.2,-                       transformers-base >= 0.4.4  && < 0.5+  build-depends:       base              >= 4.13    && < 5+                     , bytestring        >= 0.10.8+                     , entropy           >= 0.4+                     , exceptions        >= 0.8.3+                     , monad-control     >= 1.0.1+                     , mtl               >= 2.2+                     , primitive         >= 0.7+                     , random            >= 1.2     && <1.3+                     , transformers-base >= 0.4.4    hs-source-dirs:      src 
src/Crypto/RNG.hs view
@@ -1,169 +1,125 @@-{-# LANGUAGE BangPatterns               #-}-{-# LANGUAGE FlexibleInstances          #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}-{-# LANGUAGE LambdaCase                 #-}-{-# LANGUAGE MultiParamTypeClasses      #-}-{-# LANGUAGE ScopedTypeVariables        #-}-{-# LANGUAGE TypeFamilies               #-}-{-# LANGUAGE UndecidableInstances       #-}---- | Support for generation of cryptographically secure random--- numbers, based on the DRBG package.------ This is a convenience layer on top of DRBG, which allows you to--- pull random values by means of the method 'random', while keeping--- the state of the random number generator (RNG) inside a monad.  The--- state is protected by an MVar, which means that concurrent--- generation of random values from several threads works straight out--- of the box.+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE UndecidableInstances #-}+-- | Support for generation of cryptographically secure random numbers. ----- The access to the RNG state is captured by a class.  By making--- instances of this class, client code can enjoy RNG generation from--- their own monads.+-- This is a convenience layer on top of "System.Entropy", which allows you to+-- pull random values by means of the class 'CryptoRNG', while keeping the state+-- of the random number generator (RNG) inside a monad. The state is protected+-- by an MVar, which means that concurrent generation of random values from+-- several threads works straight out of the box. module Crypto.RNG   ( -- * CryptoRNG class     module Crypto.RNG.Class-    -- * Generation of strings and numbers-  , CryptoRNGState-  , newCryptoRNGState-  , newCryptoRNGStateSized-  , unsafeCryptoRNGState-  , randomBytesIO-  , randomIO-  , randomRIO     -- * Monad transformer for carrying rng state   , CryptoRNGT   , mapCryptoRNGT   , runCryptoRNGT   , withCryptoRNGState+    -- * Instantiation of the initial RNG state+  , CryptoRNGState+  , newCryptoRNGState+  , newCryptoRNGStateSized+    -- ** Low-level utils+  , randomBytesIO   ) where  import Control.Applicative import Control.Concurrent+import Control.Monad import Control.Monad.Base import Control.Monad.Catch-import Control.Monad.Cont import Control.Monad.Except import Control.Monad.Reader import Control.Monad.Trans.Control-import Crypto.Random-import Crypto.Random.DRBG import Data.Bits import Data.ByteString (ByteString)-import Data.Either-import Data.Primitive.SmallArray+import System.Entropy import qualified Data.ByteString as BS-import qualified System.Random as R+import qualified Data.ByteString.Short as SBS+import qualified System.Random.Stateful as R  import Crypto.RNG.Class  -- | The random number generator state.-newtype CryptoRNGState = CryptoRNGState (SmallArray (MVar RNG))+newtype CryptoRNGState = CryptoRNGState (MVar Buffer) --- | The random number generator.-newtype RNG = RNG (GenBuffered (GenAutoReseed HashDRBG HashDRBG))+-- | A buffer of random bytes for immediate consumption.+data Buffer = Buffer+  { maxSize :: !Int+  , bytes   :: !BS.ByteString+  } -instance R.RandomGen RNG where-  split = error "split"-  genWord32 (RNG g) = case genBytes 4 g of-    Left err       -> error $ "genBytes failed: " ++ show err-    Right (bs, g') -> (mkWord bs, RNG g')-  genWord64 (RNG g) = case genBytes 8 g of-    Left err       -> error $ "genBytes failed: " ++ show err-    Right (bs, g') -> (mkWord bs, RNG g')+instance R.StatefulGen CryptoRNGState IO where+  uniformWord8  st = mkWord <$> randomBytesIO 1 st+  uniformWord16 st = mkWord <$> randomBytesIO 2 st+  uniformWord32 st = mkWord <$> randomBytesIO 4 st+  uniformWord64 st = mkWord <$> randomBytesIO 8 st+  uniformShortByteString n st = SBS.toShort <$> randomBytesIO n st  mkWord :: (Bits a, Integral a) => ByteString -> a mkWord bs = BS.foldl' (\acc w -> shiftL acc 8 .|. fromIntegral w) 0 bs --- | Work with one of the RNGs from the pool.-withRNG :: CryptoRNGState -> (RNG -> (a, RNG)) -> IO a-withRNG (CryptoRNGState pool) f = do-  -- Selection strategy is based on the id of a capability instead of an id of a-  -- thread as that offers much better performance in a typical scenario when-  -- the size of the pool is equal to the number of capabilities and there are-  -- more threads than capabilities.-  (cid, _) <- threadCapability =<< myThreadId-  let mrng = pool `indexSmallArray` (cid `rem` sizeofSmallArray pool)-  modifyMVar mrng $ \rng -> do-    (a, newRng) <- pure $ f rng-    newRng `seq` pure (newRng, a)- ---------------------------------------- --- | Create a new 'CryptoRNGState', based on system entropy.+-- | Create a new 'CryptoRNGState' based on system entropy with a buffer size of+-- 32KB. newCryptoRNGState :: MonadIO m => m CryptoRNGState-newCryptoRNGState = newCryptoRNGStateSized =<< liftIO getNumCapabilities+newCryptoRNGState = newCryptoRNGStateSized $ 32 * 1024 --- | Create a new 'CryptoRNGState', based on system entropy with the pool of a--- specific size.------ /Note:/ making the pool bigger than the number of capabilities will not--- affect anything.+-- | Create a new 'CryptoRNGState' based on system entropy with a buffer of+-- specified size. newCryptoRNGStateSized   :: MonadIO m-  => Int -- ^ Pool size.-  -> m CryptoRNGState-newCryptoRNGStateSized n = liftIO $ do-  pool <- replicateM n $ newMVar . RNG =<< newGenIO-  pure . CryptoRNGState $ smallArrayFromListN n pool---- | Create a new 'CryptoRNGState', based on a bytestring seed.--- Should only be used for testing.-unsafeCryptoRNGState-  :: MonadIO m-  => [ByteString]-  -- ^ Seeds for each generator from the pool.+  => Int -- ^ Buffer size.   -> m CryptoRNGState-unsafeCryptoRNGState ss = liftIO $ do-  case partitionEithers $ map newGen ss of-    ([], gens) -> do-      pool <- mapM (newMVar . RNG) gens-      pure . CryptoRNGState $ smallArrayFromList pool-    (errs, _)  -> error $ show errs---- | Generate given number of cryptographically secure random bytes.-randomBytesIO :: ByteLength -- ^ number of bytes to generate-              -> CryptoRNGState-              -> IO ByteString-randomBytesIO n pool = withRNG pool $ \(RNG g) ->-  case genBytes n g of-    Left err       -> error $ "genBytes failed: " ++ show err-    Right (bs, g') -> (bs, RNG g')+newCryptoRNGStateSized bufferSize = liftIO $ do+  when (bufferSize <= 0) $ do+    error "Buffer size must be larger than 0"+  CryptoRNGState <$> newMVar (Buffer bufferSize BS.empty) -randomIO :: R.Uniform a => CryptoRNGState -> IO a-randomIO pool = withRNG pool $ \g -> R.uniform g+-- | Generate a number of cryptographically secure random bytes.+randomBytesIO :: Int -> CryptoRNGState -> IO ByteString+randomBytesIO n (CryptoRNGState rng) = modifyMVar rng $ \buf -> do+  (rs, newBuf) <- generateBytes buf n []+  pure (newBuf, BS.concat rs) -randomRIO :: R.UniformRange a => (a, a) -> CryptoRNGState -> IO a-randomRIO bounds pool = withRNG pool $ \g -> R.uniformR bounds g+generateBytes+  :: Buffer+  -> Int+  -> [BS.ByteString]+  -> IO ([BS.ByteString], Buffer)+generateBytes buf n acc = do+  (r, newBytes) <- BS.splitAt n <$> if BS.null (bytes buf)+                                    then getEntropy (maxSize buf)+                                    else pure (bytes buf)+  let newBuf = buf { bytes = newBytes }+      k = n - BS.length r+  newBuf `seq` if k <= 0+    then pure (r : acc, newBuf)+    else generateBytes newBuf k (r : acc) -type InnerCryptoRNGT = ReaderT CryptoRNGState+----------------------------------------  -- | Monad transformer with RNG state.-newtype CryptoRNGT m a = CryptoRNGT { unCryptoRNGT :: InnerCryptoRNGT m a }-  deriving ( Alternative, Applicative, Functor, Monad-           , MonadBase b, MonadCatch, MonadError e, MonadIO, MonadMask, MonadPlus-           , MonadThrow, MonadTrans, MonadFail )+newtype CryptoRNGT m a = CryptoRNGT { unCryptoRNGT :: ReaderT CryptoRNGState m a }+  deriving ( Alternative, Applicative, Functor, Monad, MonadFail, MonadPlus+           , MonadError e, MonadIO,  MonadBase b, MonadBaseControl b+           , MonadThrow, MonadCatch, MonadMask+           , MonadTrans, MonadTransControl+           )  mapCryptoRNGT :: (m a -> n b) -> CryptoRNGT m a -> CryptoRNGT n b-mapCryptoRNGT f m = withCryptoRNGState $ \s -> f (runCryptoRNGT s m)+mapCryptoRNGT f m = withCryptoRNGState $ \rng -> f (runCryptoRNGT rng m)  runCryptoRNGT :: CryptoRNGState -> CryptoRNGT m a -> m a-runCryptoRNGT pool m = runReaderT (unCryptoRNGT m) pool+runCryptoRNGT rng m = runReaderT (unCryptoRNGT m) rng  withCryptoRNGState :: (CryptoRNGState -> m a) -> CryptoRNGT m a withCryptoRNGState = CryptoRNGT . ReaderT -instance MonadTransControl CryptoRNGT where-  type StT CryptoRNGT a = StT InnerCryptoRNGT a-  liftWith = defaultLiftWith CryptoRNGT unCryptoRNGT-  restoreT = defaultRestoreT CryptoRNGT--instance MonadBaseControl b m => MonadBaseControl b (CryptoRNGT m) where-  type StM (CryptoRNGT m) a = ComposeSt CryptoRNGT m a-  liftBaseWith = defaultLiftBaseWith-  restoreM     = defaultRestoreM--instance {-# OVERLAPPABLE #-} MonadIO m => CryptoRNG (CryptoRNGT m) where+instance MonadIO m => CryptoRNG (CryptoRNGT m) where   randomBytes n  = CryptoRNGT ask >>= liftIO . randomBytesIO n-  random         = CryptoRNGT ask >>= liftIO . randomIO-  randomR bounds = CryptoRNGT ask >>= liftIO . randomRIO bounds+  random         = CryptoRNGT ask >>= liftIO . R.uniformM+  randomR bounds = CryptoRNGT ask >>= liftIO . R.uniformRM bounds
src/Crypto/RNG/Class.hs view
@@ -1,18 +1,15 @@-{-# LANGUAGE FlexibleInstances    #-}+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE UndecidableInstances #-} module Crypto.RNG.Class where  import Control.Monad.Trans-import Crypto.Random.DRBG import Data.ByteString (ByteString) import System.Random (Uniform, UniformRange)  -- | Monads carrying around the RNG state. class Monad m => CryptoRNG m where   -- | Generate a given number of cryptographically secure random bytes.-  randomBytes-    :: ByteLength -- ^ A number of bytes to generate.-    -> m ByteString+  randomBytes :: Int -> m ByteString    -- | Generate a cryptographically secure value uniformly distributed over all   -- possible values of that type.@@ -22,8 +19,8 @@   randomR :: UniformRange a => (a, a) -> m a  -- | Generic, overlapping instance.-instance {-# OVERLAPPABLE #-} (-    Monad (t m)+instance {-# OVERLAPPABLE #-}+  ( Monad (t m)   , MonadTrans t   , CryptoRNG m   ) => CryptoRNG (t m) where
+ src/Crypto/RNG/Unsafe.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE UndecidableInstances #-}+-- | Support for generation of __non cryptographically secure__ random numbers+-- for testing purposes.+module Crypto.RNG.Unsafe+  ( -- * CryptoRNG class+    module Crypto.RNG.Class+    -- * Monad transformer for carrying rng state+  , RNGT+  , mapRNGT+  , runRNGT+  , withRNGState+    -- * Instantiation of the initial RNG state+  , RNGState+  , newRNGState+    -- ** Low-level utils+  , withRNG+  ) where++import Control.Applicative+import Control.Concurrent+import Control.Monad+import Control.Monad.Base+import Control.Monad.Catch+import Control.Monad.Except+import Control.Monad.Reader+import Control.Monad.Trans.Control+import qualified System.Random as R++import Crypto.RNG.Class++-- | The random number generator state.+newtype RNGState = RNGState (MVar R.StdGen)++-- | Create a new 'RNGState' with a given seed.+newRNGState :: MonadIO m => Int -> m RNGState+newRNGState seed = liftIO $ do+  RNGState <$> newMVar (R.mkStdGen seed)++----------------------------------------++-- | Monad transformer with RNG state.+newtype RNGT m a = RNGT { unRNGT :: ReaderT RNGState m a }+  deriving ( Alternative, Applicative, Functor, Monad, MonadFail, MonadPlus+           , MonadError e, MonadIO, MonadBase b, MonadBaseControl b+           , MonadThrow, MonadCatch, MonadMask+           , MonadTrans, MonadTransControl+           )++mapRNGT :: (m a -> n b) -> RNGT m a -> RNGT n b+mapRNGT f m = withRNGState $ \rng -> f (runRNGT rng m)++runRNGT :: RNGState -> RNGT m a -> m a+runRNGT rng m = runReaderT (unRNGT m) rng++withRNGState :: (RNGState -> m a) -> RNGT m a+withRNGState = RNGT . ReaderT++instance MonadIO m => CryptoRNG (RNGT m) where+  randomBytes n  = RNGT ask >>= (`withRNG` \g -> R.genByteString n g)+  random         = RNGT ask >>= (`withRNG` \g -> R.uniform g)+  randomR bounds = RNGT ask >>= (`withRNG` \g -> R.uniformR bounds g)++withRNG :: MonadIO m => RNGState -> (R.StdGen -> (a, R.StdGen)) -> m a+withRNG (RNGState rng) f = liftIO . modifyMVar rng $ \g -> do+  (a, newG) <- pure $ f g+  newG `seq` pure (newG, a)
src/Crypto/RNG/Utils.hs view
@@ -5,8 +5,7 @@  import Crypto.RNG --- | Generate random string of specified length that contains allowed--- chars.+-- | Generate random string of specified length that contains allowed chars. randomString :: CryptoRNG m => Int -> [Char] -> m String randomString n allowedList = map (indexSmallArray allowed)   <$> replicateM n (randomR (0, sizeofSmallArray allowed - 1))