-- | -- Module : Crypto.Random.API -- License : BSD-style -- Maintainer : Vincent Hanquez -- Stability : experimental -- Portability : Good module Crypto.Random.API ( CPRG(..) , ReseedPolicy(..) , genRandomBytes , genRandomBytes' , withRandomBytes , getSystemEntropy -- * System Random generator , SystemRandom , getSystemRandomGen ) where import Control.Applicative import qualified Data.ByteString as B import Data.ByteString (ByteString) import qualified System.Entropy as SE import System.IO.Unsafe (unsafeInterleaveIO) import Data.Word -- | This is the reseed policy requested by the CPRG data ReseedPolicy = NeverReseed -- ^ there is no need to reseed as either -- the RG doesn't supports it, it's done automatically -- or pratically the reseeding period exceed a Word64 type. | ReseedInBytes Word64 -- ^ the RG need to be reseed in the number -- of bytes joined to the type. it should be done before -- the number reached 0, otherwise an user of the RG -- might request too many bytes and get repeated random bytes. deriving (Show,Eq) -- | A class of Cryptographic Secure Random generator. -- -- The main difference with the generic haskell RNG is that -- it return bytes instead of integer. -- -- It is quite similar to the CryptoRandomGen class in crypto-api -- except that error are not returned to the user. Instead -- the user is suppose to handle reseeding by using the NeedReseed -- and SupplyEntropy methods. For other type of errors, the user -- is expected to generate bytes with the parameters bounds explicity -- defined here. -- -- The CPRG need to be able to generate up to 2^20 bytes in one call, -- class CPRG g where -- | Provide a way to query the CPRG to calculate when new entropy -- is required to be supplied so the CPRG doesn't repeat output, and -- break assumptions. This returns the number of bytes before -- which supply entropy should have been called. cprgNeedReseed :: g -> ReseedPolicy -- | Supply entropy to the CPRG, that can be used now or later -- to reseed the CPRG. This should be used in conjunction to -- NeedReseed to know when to supply entropy. cprgSupplyEntropy :: ByteString -> g -> g -- | Generate bytes using the CPRG and the number specified. -- -- For user of the API, it's recommended to use genRandomBytes -- instead of this method directly. the CPRG need to be able -- to supply at minimum 2^20 bytes at a time. cprgGenBytes :: Int -> g -> (ByteString, g) -- | Generate bytes using the cprg in parameter. -- -- If the number of bytes requested is really high, -- it's preferable to use 'genRandomBytes' for better memory efficiency. genRandomBytes :: CPRG g => Int -- ^ number of bytes to return -> g -- ^ CPRG to use -> (ByteString, g) genRandomBytes len rng = (\(lbs,g) -> (B.concat lbs, g)) $ genRandomBytes' len rng -- | Generate bytes using the cprg in parameter. -- -- This is not tail recursive and an excessive len (>= 2^29) parameter would -- result in stack overflow. genRandomBytes' :: CPRG g => Int -- ^ number of bytes to return -> g -- ^ CPRG to use -> ([ByteString], g) genRandomBytes' len rng | len < 0 = error "genBytes: cannot request negative amount of bytes." | otherwise = loop rng len where loop g len | len == 0 = ([], g) | otherwise = let itBytes = min (2^20) len (bs, g') = cprgGenBytes itBytes g (l, g'') = genRandomBytes' (len-itBytes) g' in (bs:l, g'') -- | this is equivalent to using Control.Arrow 'first' with 'genRandomBytes'. -- -- namely it generate @len bytes and map the bytes to the function @f withRandomBytes :: CPRG g => g -> Int -> (ByteString -> a) -> (a, g) withRandomBytes rng len f = (f bs, rng') where (bs, rng') = genRandomBytes len rng -- | Return system entropy using the entropy package 'getEntropy' getSystemEntropy :: Int -> IO ByteString getSystemEntropy = SE.getEntropy -- | This is a simple generator that pull bytes from the system entropy -- directly. Its randomness and security properties are absolutely -- depends on the underlaying system implementation. data SystemRandom = SystemRandom [B.ByteString] -- | Get a random number generator based on the standard system entropy source getSystemRandomGen :: IO SystemRandom getSystemRandomGen = do ch <- SE.openHandle let getBS = unsafeInterleaveIO $ do bs <- SE.hGetEntropy ch 8192 more <- getBS return (bs:more) SystemRandom <$> getBS instance CPRG SystemRandom where cprgNeedReseed _ = NeverReseed cprgSupplyEntropy _ g = g cprgGenBytes n (SystemRandom l) = (B.concat l1, SystemRandom l2) where (l1, l2) = lbsSplitAt n l lbsSplitAt rBytes (x:xs) | xLen >= rBytes = let (b1,b2) = B.splitAt rBytes x in ([b1], b2:xs) | otherwise = let (l1,l2) = lbsSplitAt (rBytes-xLen) xs in (x:l1,l2) where xLen = B.length x