{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-|
Module      : Data.Password.PBKDF2
Copyright   : (c) Felix Paulusma, 2020
License     : BSD-style (see LICENSE file)
Maintainer  : cdep.illabout@gmail.com
Stability   : experimental
Portability : POSIX

= PBKDF2

The PBKDF2 algorithm is one of the oldest and most solid password
algorithms out there. It has also, however, been shown to be
the least secure out of all major password algorithms. The main
reason for this is that it doesn't make use of any memory cost
or other method of making it difficult for specialized hardware
attacks, like GPU cracking attacks.

It is still, however, used all over the world, since it has been
shown to be a very reliable way to encrypt passwords. And it is
most definitely better than trying to develop a password algorithm
on your own, or god-forbid, not using /any/ encryption on your stored
passwords.

== Other algorithms

Seeing as PBKDF2 is shown to be very weak in terms of protection
against GPU cracking attacks, it is generally advised to go with
@'Data.Password.Bcrypt.Bcrypt'@, if not @'Data.Password.Scrypt.Scrypt'@
or @'Data.Password.Argon2.Argon2'@.
When unsure, @'Data.Password.Bcrypt.Bcrypt'@
would probably be the safest option, as it has no memory cost which
could become a problem if not properly calibrated to the machine
doing the password verifications.
-}

module Data.Password.PBKDF2 (
  -- Algorithm
  PBKDF2
  -- * Plain-text Password
  , Password
  , mkPassword
  -- * Hash Passwords (PBKDF2)
  , hashPassword
  , PasswordHash(..)
  -- * Verify Passwords (PBKDF2)
  , checkPassword
  , PasswordCheck(..)
  -- * Hashing Manually (PBKDF2)
  , hashPasswordWithParams
  , defaultParams
  , PBKDF2Params(..)
  , PBKDF2Algorithm(..)
  -- ** Hashing with salt (DISADVISED)
  --
  -- | Hashing with a set 'Salt' is almost never what you want
  -- to do. Use 'hashPassword' or 'hashPasswordWithParams' to have
  -- automatic generation of randomized salts.
  , hashPasswordWithSalt
  , newSalt
  , Salt(..)
  -- * Unsafe debugging function to show a Password
  , unsafeShowPassword
  , -- * Setup for doctests.
    -- $setup
  ) where

import Control.Monad (guard)
import Control.Monad.IO.Class (MonadIO(liftIO))
import Crypto.Hash.Algorithms as Crypto (MD5(..))
import Crypto.KDF.PBKDF2 as PBKDF2
import Data.ByteArray (ByteArray, ByteArrayAccess, Bytes, constEq, convert)
import Data.ByteString (ByteString)
import qualified Data.ByteString.Base64 as Base64
import qualified Data.ByteString.Char8 as C8 (length)
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import qualified Data.Text as T (intercalate, pack, split, stripPrefix)
import Data.Word (Word32)

import Data.Password (
         PasswordCheck(..)
       , PasswordHash(..)
       , Salt(..)
       , mkPassword
       , unsafeShowPassword
       )
import Data.Password.Internal (Password(..), from64, readT, toBytes)
import qualified Data.Password.Internal (newSalt)


-- | Phantom type for __PBKDF2__
--
-- @since 2.0.0.0
data PBKDF2

-- $setup
-- >>> :set -XFlexibleInstances
-- >>> :set -XOverloadedStrings
--
-- Import needed libraries.
--
-- >>> import Data.Password
-- >>> import Data.ByteString (pack)
-- >>> import Test.QuickCheck (Arbitrary(arbitrary), Blind(Blind), vector)
-- >>> import Test.QuickCheck.Instances.Text ()
--
-- >>> instance Arbitrary (Salt a) where arbitrary = Salt . pack <$> vector 16
-- >>> instance Arbitrary Password where arbitrary = fmap Password arbitrary
-- >>> let testParams = defaultParams{ pbkdf2Iterations = 5000 }
-- >>> let salt = Salt "abcdefghijklmnop"

-- -- >>> instance Arbitrary (PasswordHash PBKDF2) where arbitrary = hashPasswordWithSalt defaultParams <$> arbitrary <*> arbitrary

-- | Hash the 'Password' using the 'PBKDF2' hash algorithm
--
-- >>> hashPassword $ mkPassword "foobar"
-- PasswordHash {unPasswordHash = "sha512:25000:...:..."}
hashPassword :: MonadIO m => Password -> m (PasswordHash PBKDF2)
hashPassword :: Password -> m (PasswordHash PBKDF2)
hashPassword = PBKDF2Params -> Password -> m (PasswordHash PBKDF2)
forall (m :: * -> *).
MonadIO m =>
PBKDF2Params -> Password -> m (PasswordHash PBKDF2)
hashPasswordWithParams PBKDF2Params
defaultParams

-- TODO: Add way to parse the following:
-- $pbkdf2-md5$29000$...$...
-- $pbkdf2$25000$...$... (SHA1)
-- $pbkdf2-sha256$29000$x9h7j/Ge8x6DMEao1VqrdQ$kra3R1wEnY8mPdDWOpTqOTINaAmZvRMcYd8u5OBQP9A
-- $pbkdf2-sha512$25000$LyWE0HrP2RsjZCxlDGFMKQ$1vC5Ohk2mCS9b6akqsEfgeb4l74SF8XjH.SljXf3dMLHdlY1GK9ojcCKts6/asR4aPqBmk74nCDddU3tvSCJvw

-- | Parameters used in the 'PBKDF2' hashing algorithm.
--
-- @since 2.0.0.0
data PBKDF2Params = PBKDF2Params {
  PBKDF2Params -> Word32
pbkdf2Salt :: Word32,
  -- ^ Bytes to randomly generate as a unique salt, default is __16__
  PBKDF2Params -> PBKDF2Algorithm
pbkdf2Algorithm :: PBKDF2Algorithm,
  -- ^ Which algorithm to use for hashing, default is __'PBKDF2_SHA512'__
  PBKDF2Params -> Word32
pbkdf2Iterations :: Word32,
  -- ^ Rounds to hash, default is __25,000__
  PBKDF2Params -> Word32
pbkdf2OutputLength :: Word32
  -- ^ Output key length in bytes, default is __64__
  --
  -- Limits are min: 1, max: /the amount of entropy of the hashing algorithm/.
  -- This is limited automatically to __16, 20, 32, 64__
  -- for __MD5, SHA1, SHA256, SHA512__, respectively.
} deriving (PBKDF2Params -> PBKDF2Params -> Bool
(PBKDF2Params -> PBKDF2Params -> Bool)
-> (PBKDF2Params -> PBKDF2Params -> Bool) -> Eq PBKDF2Params
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: PBKDF2Params -> PBKDF2Params -> Bool
$c/= :: PBKDF2Params -> PBKDF2Params -> Bool
== :: PBKDF2Params -> PBKDF2Params -> Bool
$c== :: PBKDF2Params -> PBKDF2Params -> Bool
Eq, Int -> PBKDF2Params -> ShowS
[PBKDF2Params] -> ShowS
PBKDF2Params -> String
(Int -> PBKDF2Params -> ShowS)
-> (PBKDF2Params -> String)
-> ([PBKDF2Params] -> ShowS)
-> Show PBKDF2Params
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [PBKDF2Params] -> ShowS
$cshowList :: [PBKDF2Params] -> ShowS
show :: PBKDF2Params -> String
$cshow :: PBKDF2Params -> String
showsPrec :: Int -> PBKDF2Params -> ShowS
$cshowsPrec :: Int -> PBKDF2Params -> ShowS
Show)

-- | Default parameters for the 'PBKDF2' algorithm.
--
-- >>> defaultParams
-- PBKDF2Params {pbkdf2Salt = 16, pbkdf2Algorithm = PBKDF2_SHA512, pbkdf2Iterations = 25000, pbkdf2OutputLength = 64}
--
-- @since 2.0.0.0
defaultParams :: PBKDF2Params
defaultParams :: PBKDF2Params
defaultParams = PBKDF2Params :: Word32 -> PBKDF2Algorithm -> Word32 -> Word32 -> PBKDF2Params
PBKDF2Params {
  pbkdf2Salt :: Word32
pbkdf2Salt = Word32
16,
  pbkdf2Algorithm :: PBKDF2Algorithm
pbkdf2Algorithm = PBKDF2Algorithm
PBKDF2_SHA512,
  pbkdf2Iterations :: Word32
pbkdf2Iterations = Word32
25 Word32 -> Word32 -> Word32
forall a. Num a => a -> a -> a
* Word32
1000,
  pbkdf2OutputLength :: Word32
pbkdf2OutputLength = Word32
64
}

-- | Hash a password with the given 'PBKDF2Params' and also with the given 'Salt'
-- instead of a randomly generated salt using 'pbkdf2Salt' from 'PBKDF2Params'. (cf. 'hashPasswordWithParams')
-- Using 'hashPasswordWithSalt' is strongly __disadvised__ and 'hashPasswordWithParams' should be used instead.
-- /Never use a static salt in production applications!/
--
-- >>> let salt = Salt "abcdefghijklmnop"
-- >>> hashPasswordWithSalt defaultParams salt (mkPassword "foobar")
-- PasswordHash {unPasswordHash = "sha512:25000:YWJjZGVmZ2hpamtsbW5vcA==:JRElYYrOMe9OIV4LDxaLTgO9ho8fFBVofXoQcdngi7AcuH6Amvmlj2B0y6y1UtQciXXBepSCS+rpy8/vDDQvoA=="}
--
-- (Note that we use an explicit 'Salt' in the example above.  This is so that the
-- example is reproducible, but in general you should use 'hashPassword'. 'hashPassword'
-- (and 'hashPasswordWithParams') generates a new 'Salt' everytime it is called.)
hashPasswordWithSalt :: PBKDF2Params -> Salt PBKDF2 -> Password -> PasswordHash PBKDF2
hashPasswordWithSalt :: PBKDF2Params -> Salt PBKDF2 -> Password -> PasswordHash PBKDF2
hashPasswordWithSalt params :: PBKDF2Params
params@PBKDF2Params{Word32
PBKDF2Algorithm
pbkdf2OutputLength :: Word32
pbkdf2Iterations :: Word32
pbkdf2Algorithm :: PBKDF2Algorithm
pbkdf2Salt :: Word32
pbkdf2OutputLength :: PBKDF2Params -> Word32
pbkdf2Iterations :: PBKDF2Params -> Word32
pbkdf2Algorithm :: PBKDF2Params -> PBKDF2Algorithm
pbkdf2Salt :: PBKDF2Params -> Word32
..} s :: Salt PBKDF2
s@(Salt ByteString
salt) Password
pass =
  Text -> PasswordHash PBKDF2
forall a. Text -> PasswordHash a
PasswordHash (Text -> PasswordHash PBKDF2) -> Text -> PasswordHash PBKDF2
forall a b. (a -> b) -> a -> b
$ Text -> [Text] -> Text
T.intercalate Text
":"
    [ PBKDF2Algorithm -> Text
algToText PBKDF2Algorithm
pbkdf2Algorithm
    , String -> Text
T.pack (String -> Text) -> String -> Text
forall a b. (a -> b) -> a -> b
$ Word32 -> String
forall a. Show a => a -> String
show Word32
pbkdf2Iterations
    , ByteString -> Text
b64 ByteString
salt
    , ByteString -> Text
b64 ByteString
key
    ]
  where
    b64 :: ByteString -> Text
b64 = ByteString -> Text
Base64.encodeBase64
    key :: ByteString
key = PBKDF2Params -> Salt PBKDF2 -> Password -> ByteString
hashPasswordWithSalt' PBKDF2Params
params Salt PBKDF2
s Password
pass

-- | Only for internal use
hashPasswordWithSalt' :: PBKDF2Params -> Salt PBKDF2 -> Password -> ByteString
hashPasswordWithSalt' :: PBKDF2Params -> Salt PBKDF2 -> Password -> ByteString
hashPasswordWithSalt' PBKDF2Params{Word32
PBKDF2Algorithm
pbkdf2OutputLength :: Word32
pbkdf2Iterations :: Word32
pbkdf2Algorithm :: PBKDF2Algorithm
pbkdf2Salt :: Word32
pbkdf2OutputLength :: PBKDF2Params -> Word32
pbkdf2Iterations :: PBKDF2Params -> Word32
pbkdf2Algorithm :: PBKDF2Params -> PBKDF2Algorithm
pbkdf2Salt :: PBKDF2Params -> Word32
..} (Salt ByteString
salt) (Password Text
pass) =
    Bytes -> ByteString
forall bin bout.
(ByteArrayAccess bin, ByteArray bout) =>
bin -> bout
convert (Bytes
pbkdf2Hash :: Bytes)
  where
    pbkdf2Hash :: Bytes
pbkdf2Hash = PBKDF2Algorithm -> Parameters -> Bytes -> Bytes -> Bytes
forall password salt hash.
(ByteArrayAccess password, ByteArrayAccess salt, ByteArray hash) =>
PBKDF2Algorithm -> Parameters -> password -> salt -> hash
algToFunc PBKDF2Algorithm
pbkdf2Algorithm Parameters
params (Text -> Bytes
toBytes Text
pass) (ByteString -> Bytes
forall bin bout.
(ByteArrayAccess bin, ByteArray bout) =>
bin -> bout
convert ByteString
salt :: Bytes)
    params :: Parameters
params = Parameters :: Int -> Int -> Parameters
PBKDF2.Parameters {
        iterCounts :: Int
PBKDF2.iterCounts = Word32 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral Word32
pbkdf2Iterations,
        outputLength :: Int
PBKDF2.outputLength = Word32 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word32 -> Int) -> Word32 -> Int
forall a b. (a -> b) -> a -> b
$ PBKDF2Algorithm -> Word32 -> Word32
maxOutputLength PBKDF2Algorithm
pbkdf2Algorithm Word32
pbkdf2OutputLength
      }

-- | Hash a password using the 'PBKDF2' algorithm with the given 'PBKDF2Params'.
--
-- __N.B.__: If you have any doubt in your knowledge of cryptography and/or the
-- 'PBKDF2' algorithm, please just use 'hashPassword'.
--
-- @since 2.0.0.0
hashPasswordWithParams :: MonadIO m => PBKDF2Params -> Password -> m (PasswordHash PBKDF2)
hashPasswordWithParams :: PBKDF2Params -> Password -> m (PasswordHash PBKDF2)
hashPasswordWithParams PBKDF2Params
params Password
pass = IO (PasswordHash PBKDF2) -> m (PasswordHash PBKDF2)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO (PasswordHash PBKDF2) -> m (PasswordHash PBKDF2))
-> IO (PasswordHash PBKDF2) -> m (PasswordHash PBKDF2)
forall a b. (a -> b) -> a -> b
$ do
    Salt PBKDF2
salt <- Int -> IO (Salt PBKDF2)
forall (m :: * -> *) a. MonadIO m => Int -> m (Salt a)
Data.Password.Internal.newSalt (Int -> IO (Salt PBKDF2))
-> (Word32 -> Int) -> Word32 -> IO (Salt PBKDF2)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word32 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Word32 -> IO (Salt PBKDF2)) -> Word32 -> IO (Salt PBKDF2)
forall a b. (a -> b) -> a -> b
$ PBKDF2Params -> Word32
pbkdf2Salt PBKDF2Params
params
    PasswordHash PBKDF2 -> IO (PasswordHash PBKDF2)
forall (m :: * -> *) a. Monad m => a -> m a
return (PasswordHash PBKDF2 -> IO (PasswordHash PBKDF2))
-> PasswordHash PBKDF2 -> IO (PasswordHash PBKDF2)
forall a b. (a -> b) -> a -> b
$ PBKDF2Params -> Salt PBKDF2 -> Password -> PasswordHash PBKDF2
hashPasswordWithSalt PBKDF2Params
params Salt PBKDF2
salt Password
pass

-- | Check a 'Password' against a 'PasswordHash' 'PBKDF2'.
--
-- Returns 'PasswordCheckSuccess' on success.
--
-- >>> let pass = mkPassword "foobar"
-- >>> passHash <- hashPassword pass
-- >>> checkPassword pass passHash
-- PasswordCheckSuccess
--
-- Returns 'PasswordCheckFail' if an incorrect 'Password' or 'PasswordHash' 'PBKDF2' is used.
--
-- >>> let badpass = mkPassword "incorrect-password"
-- >>> checkPassword badpass passHash
-- PasswordCheckFail
--
-- This should always fail if an incorrect password is given.
--
-- prop> \(Blind badpass) -> let correctPasswordHash = hashPasswordWithSalt testParams salt "foobar" in checkPassword badpass correctPasswordHash == PasswordCheckFail
checkPassword :: Password -> PasswordHash PBKDF2 -> PasswordCheck
checkPassword :: Password -> PasswordHash PBKDF2 -> PasswordCheck
checkPassword Password
pass (PasswordHash Text
passHash) =
  PasswordCheck -> Maybe PasswordCheck -> PasswordCheck
forall a. a -> Maybe a -> a
fromMaybe PasswordCheck
PasswordCheckFail (Maybe PasswordCheck -> PasswordCheck)
-> Maybe PasswordCheck -> PasswordCheck
forall a b. (a -> b) -> a -> b
$ do
    -- This step makes it possible to also check the following format:
    -- "pbkdf2:sha256:150000:etc.etc."
    let passHash' :: Text
passHash' = Text -> Maybe Text -> Text
forall a. a -> Maybe a -> a
fromMaybe Text
passHash (Maybe Text -> Text) -> Maybe Text -> Text
forall a b. (a -> b) -> a -> b
$ Text
"pbkdf2:" Text -> Text -> Maybe Text
`T.stripPrefix` Text
passHash
        paramList :: [Text]
paramList = (Char -> Bool) -> Text -> [Text]
T.split (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
':') Text
passHash'
    Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ [Text] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Text]
paramList Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
4
    let [ Text
algT,
          Text
iterationsT,
          Text
salt64,
          Text
hashedKey64 ] = [Text]
paramList
    PBKDF2Algorithm
pbkdf2Algorithm <- Text -> Maybe PBKDF2Algorithm
textToAlg Text
algT
    Word32
pbkdf2Iterations <- Text -> Maybe Word32
forall a. Read a => Text -> Maybe a
readT Text
iterationsT
    ByteString
salt <- Text -> Maybe ByteString
from64 Text
salt64
    ByteString
hashedKey <- Text -> Maybe ByteString
from64 Text
hashedKey64
    let pbkdf2OutputLength :: Word32
pbkdf2OutputLength = Int -> Word32
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int -> Word32) -> Int -> Word32
forall a b. (a -> b) -> a -> b
$ ByteString -> Int
C8.length ByteString
hashedKey
        producedKey :: ByteString
producedKey = PBKDF2Params -> Salt PBKDF2 -> Password -> ByteString
hashPasswordWithSalt' PBKDF2Params :: Word32 -> PBKDF2Algorithm -> Word32 -> Word32 -> PBKDF2Params
PBKDF2Params{Word32
PBKDF2Algorithm
pbkdf2Salt :: Word32
pbkdf2OutputLength :: Word32
pbkdf2Iterations :: Word32
pbkdf2Algorithm :: PBKDF2Algorithm
pbkdf2OutputLength :: Word32
pbkdf2Iterations :: Word32
pbkdf2Algorithm :: PBKDF2Algorithm
pbkdf2Salt :: Word32
..} (ByteString -> Salt PBKDF2
forall a. ByteString -> Salt a
Salt ByteString
salt) Password
pass
    Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Bool -> Maybe ()) -> Bool -> Maybe ()
forall a b. (a -> b) -> a -> b
$ ByteString
hashedKey ByteString -> ByteString -> Bool
forall bs1 bs2.
(ByteArrayAccess bs1, ByteArrayAccess bs2) =>
bs1 -> bs2 -> Bool
`constEq` ByteString
producedKey
    PasswordCheck -> Maybe PasswordCheck
forall (m :: * -> *) a. Monad m => a -> m a
return PasswordCheck
PasswordCheckSuccess
  where
    pbkdf2Salt :: Word32
pbkdf2Salt = Word32
16


-- | Type of algorithm to use for hashing PBKDF2 passwords.
--
-- N.B.: 'PBKDF2_MD5' and 'PBKDF2_SHA1' are not considered very secure.
data PBKDF2Algorithm =
    PBKDF2_MD5
  | PBKDF2_SHA1
  | PBKDF2_SHA256
  | PBKDF2_SHA512
  deriving (PBKDF2Algorithm -> PBKDF2Algorithm -> Bool
(PBKDF2Algorithm -> PBKDF2Algorithm -> Bool)
-> (PBKDF2Algorithm -> PBKDF2Algorithm -> Bool)
-> Eq PBKDF2Algorithm
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: PBKDF2Algorithm -> PBKDF2Algorithm -> Bool
$c/= :: PBKDF2Algorithm -> PBKDF2Algorithm -> Bool
== :: PBKDF2Algorithm -> PBKDF2Algorithm -> Bool
$c== :: PBKDF2Algorithm -> PBKDF2Algorithm -> Bool
Eq, Int -> PBKDF2Algorithm -> ShowS
[PBKDF2Algorithm] -> ShowS
PBKDF2Algorithm -> String
(Int -> PBKDF2Algorithm -> ShowS)
-> (PBKDF2Algorithm -> String)
-> ([PBKDF2Algorithm] -> ShowS)
-> Show PBKDF2Algorithm
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [PBKDF2Algorithm] -> ShowS
$cshowList :: [PBKDF2Algorithm] -> ShowS
show :: PBKDF2Algorithm -> String
$cshow :: PBKDF2Algorithm -> String
showsPrec :: Int -> PBKDF2Algorithm -> ShowS
$cshowsPrec :: Int -> PBKDF2Algorithm -> ShowS
Show)

-- | Depending on the given algorithm limits the output length.
maxOutputLength :: PBKDF2Algorithm -> Word32 -> Word32
maxOutputLength :: PBKDF2Algorithm -> Word32 -> Word32
maxOutputLength = Word32 -> Word32 -> Word32
forall a. Ord a => a -> a -> a
min (Word32 -> Word32 -> Word32)
-> (PBKDF2Algorithm -> Word32)
-> PBKDF2Algorithm
-> Word32
-> Word32
forall b c a. (b -> c) -> (a -> b) -> a -> c
. \case
  PBKDF2Algorithm
PBKDF2_MD5 -> Word32
16
  PBKDF2Algorithm
PBKDF2_SHA1 -> Word32
20
  PBKDF2Algorithm
PBKDF2_SHA256 -> Word32
32
  PBKDF2Algorithm
PBKDF2_SHA512 -> Word32
64

algToText :: PBKDF2Algorithm -> Text
algToText :: PBKDF2Algorithm -> Text
algToText = \case
  PBKDF2Algorithm
PBKDF2_MD5 -> Text
"md5"
  PBKDF2Algorithm
PBKDF2_SHA1 -> Text
"sha1"
  PBKDF2Algorithm
PBKDF2_SHA256 -> Text
"sha256"
  PBKDF2Algorithm
PBKDF2_SHA512 -> Text
"sha512"

textToAlg :: Text -> Maybe PBKDF2Algorithm
textToAlg :: Text -> Maybe PBKDF2Algorithm
textToAlg = \case
  Text
"md5" -> PBKDF2Algorithm -> Maybe PBKDF2Algorithm
forall a. a -> Maybe a
Just PBKDF2Algorithm
PBKDF2_MD5
  Text
"sha1" -> PBKDF2Algorithm -> Maybe PBKDF2Algorithm
forall a. a -> Maybe a
Just PBKDF2Algorithm
PBKDF2_SHA1
  Text
"sha256" -> PBKDF2Algorithm -> Maybe PBKDF2Algorithm
forall a. a -> Maybe a
Just PBKDF2Algorithm
PBKDF2_SHA256
  Text
"sha512" -> PBKDF2Algorithm -> Maybe PBKDF2Algorithm
forall a. a -> Maybe a
Just PBKDF2Algorithm
PBKDF2_SHA512
  Text
_ -> Maybe PBKDF2Algorithm
forall a. Maybe a
Nothing

-- Which function to use, based on the given algorithm
algToFunc :: (ByteArrayAccess password, ByteArrayAccess salt, ByteArray hash)
          => PBKDF2Algorithm -> PBKDF2.Parameters -> password -> salt -> hash
algToFunc :: PBKDF2Algorithm -> Parameters -> password -> salt -> hash
algToFunc = \case
  PBKDF2Algorithm
PBKDF2_MD5 -> PRF password -> Parameters -> password -> salt -> hash
forall password salt ba.
(ByteArrayAccess password, ByteArrayAccess salt, ByteArray ba) =>
PRF password -> Parameters -> password -> salt -> ba
PBKDF2.generate (MD5 -> PRF password
forall a password.
(HashAlgorithm a, ByteArrayAccess password) =>
a -> PRF password
PBKDF2.prfHMAC MD5
Crypto.MD5)
  PBKDF2Algorithm
PBKDF2_SHA1 -> Parameters -> password -> salt -> hash
forall password salt out.
(ByteArrayAccess password, ByteArrayAccess salt, ByteArray out) =>
Parameters -> password -> salt -> out
PBKDF2.fastPBKDF2_SHA1
  PBKDF2Algorithm
PBKDF2_SHA256 -> Parameters -> password -> salt -> hash
forall password salt out.
(ByteArrayAccess password, ByteArrayAccess salt, ByteArray out) =>
Parameters -> password -> salt -> out
PBKDF2.fastPBKDF2_SHA256
  PBKDF2Algorithm
PBKDF2_SHA512 -> Parameters -> password -> salt -> hash
forall password salt out.
(ByteArrayAccess password, ByteArrayAccess salt, ByteArray out) =>
Parameters -> password -> salt -> out
PBKDF2.fastPBKDF2_SHA512

-- | Generate a random 16-byte @PBKDF2@ salt
--
-- @since 2.0.0.0
newSalt :: MonadIO m => m (Salt PBKDF2)
newSalt :: m (Salt PBKDF2)
newSalt = Int -> m (Salt PBKDF2)
forall (m :: * -> *) a. MonadIO m => Int -> m (Salt a)
Data.Password.Internal.newSalt Int
16