{-# LANGUAGE OverloadedStrings #-} {- Copyright 2016 Joey Hess - - Licensed under the GNU AGPL version 3 or higher. -} module ExpensiveHash where import Types import Tunables import Cost import Serialization () import qualified Data.Text as T import qualified Data.ByteString as B import qualified Crypto.Argon2 as Argon2 import Raaz.Core.Encode import Data.Time.Clock import Control.DeepSeq import Control.Monad import Data.Monoid -- | A hash that is expensive to calculate. -- -- This is a lynchpin of keysafe's security, because using this hash -- as an encryption key forces brute force attackers to generate -- hashes over and over again, taking a very long time. data ExpensiveHash = ExpensiveHash (Cost CreationOp) T.Text deriving (Show) instance HasCreationCost ExpensiveHash where getCreationCost (ExpensiveHash c _) = c data Salt t = Salt t expensiveHash :: Encodable t => ExpensiveHashTunable -> Salt t -> B.ByteString -> ExpensiveHash expensiveHash (UseArgon2 cost opts) (Salt s) b = ExpensiveHash cost $ -- Using hashEncoded here and not hash, -- because of this bug: -- https://github.com/ocharles/argon2/issues/3 Argon2.hashEncoded opts b argonsalt where -- argon salt cannot be shorter than 8 bytes, so pad with spaces. argonsalt = let sb = toByteString s in sb <> B.replicate (8 - B.length sb ) 32 benchmarkExpensiveHash :: Int -> ExpensiveHashTunable -> Cost op -> IO (BenchmarkResult (Cost op)) benchmarkExpensiveHash rounds tunables expected = do start <- getCurrentTime forM_ [1..rounds] $ \_ -> do let ExpensiveHash _ t = expensiveHash tunables (Salt (GpgKey (KeyId ("dummy" :: B.ByteString)))) ("himom" :: B.ByteString) t `deepseq` return () end <- getCurrentTime let diff = floor $ end `diffUTCTime` start let actual = CPUCost $ Seconds diff return $ BenchmarkResult { expectedBenchmark = expected , actualBenchmark = actual } benchmarkTunables :: Tunables -> IO () benchmarkTunables tunables = do putStrLn "Note that expected times are for 1 core machine." putStrLn "If this machine has 2 cores, the actual times should be half the expected times." putStrLn "Benchmarking 16 rounds of key generation hash..." print =<< benchmarkExpensiveHash 16 (keyEncryptionKeyHash $ keyEncryptionKeyTunable tunables) (mapCost (`div` 16) $ randomSaltBytesBruteForceCost $ keyEncryptionKeyTunable tunables) putStrLn "Benchmarking 1 round of name generation hash..." print =<< benchmarkExpensiveHash 1 (nameGenerationHash $ nameGenerationTunable tunables) (getexpected $ nameGenerationHash $ nameGenerationTunable tunables) where getexpected (UseArgon2 cost _) = cost