botan-low-0.1.0.0: src/Botan/Low/PwdHash.hs
{-|
Module : Botan.Low.PwdHash
Description : Password hashing
Copyright : (c) 2023-2024, Apotheca Labs
(c) 2024-2025, Haskell Foundation
License : BSD-3-Clause
Maintainer : joris@well-typed.com, leo@apotheca.io
Stability : experimental
Portability : POSIX
This module is based on the [Password Based Key
Deriviation](https://botan.randombit.net/handbook/api_ref/pbkdf.html) section of
the C++ API reference.
-}
module Botan.Low.PwdHash (
pwdhash
, pwdhashTimed
-- * Available schemes
-- $available-schemes
, PBKDFName
, pattern PBKDF2
, pbkdf2
, pattern Scrypt
, pattern Argon2d
, pattern Argon2i
, pattern Argon2id
, pattern Bcrypt_PBKDF
, pattern OpenPGP_S2K
, openPGP_S2K
) where
import Botan.Bindings.ConstPtr (ConstPtr (..))
import Botan.Bindings.PwdHash
import Botan.Low.Error.Internal
import Botan.Low.Hash
import Botan.Low.Internal.ByteString
import Botan.Low.Internal.String
import Data.ByteString (ByteString)
import Foreign.Marshal.Alloc
import Foreign.Storable
{- $setup
>>> import Botan.Low.Hash
-}
{-------------------------------------------------------------------------------
Password hashing
-------------------------------------------------------------------------------}
-- | Derive a cryptographic key from a passphrase using algorithm-specific parameters
--
-- NOTE: the interpretation of parameters @param1@, @param2@, and @param3@ are
-- different depending on the PBKDF algorithm that is picked. See the
-- documentation of the
-- [@from_params@](https://botan.randombit.net/handbook/api_ref/pbkdf.html#passwordhash)
-- C++ function for more information about the meaning of the parameters.
--
pwdhash ::
PBKDFName -- ^ __algo__: PBKDF algorithm, e.g., "Scrypt" or "PBKDF2(SHA-256)"
-> Int -- ^ __param1__: the first PBKDF algorithm parameter
-> Int -- ^ __param2__: the second PBKDF algorithm parameter (may be zero if unneeded)
-> Int -- ^ __param3__: the third PBKDF algorithm parameter (may be zero if unneeded)
-> Int -- ^ __out_len__: the desired length of the key to produce
-> ByteString -- ^ __passphrase__: the password to derive the key from
-> ByteString -- ^ __salt[]__: a randomly chosen salt
-> IO ByteString -- ^ __out[]__: buffer to store the derived key, must be of out_len bytes
pwdhash algo p1 p2 p3 outLen passphrase salt =
allocBytes outLen $ \ outPtr ->
asCString algo $ \ algoPtr ->
asCStringLen passphrase $ \ passphrasePtr passphraseLen ->
asBytesLen salt $ \ saltPtr saltLen ->
throwBotanIfNegative_ $ botan_pwdhash
(ConstPtr algoPtr)
(fromIntegral p1)
(fromIntegral p2)
(fromIntegral p3)
outPtr
(fromIntegral outLen)
(ConstPtr passphrasePtr)
passphraseLen
(ConstPtr saltPtr)
saltLen
-- | Derive a cryptographic key from a passphrase using algorithm-specific
-- parameters that are tuned automatically for a desired running time of the
-- algorithm.
--
-- NOTE: for the @Argon2@ and @Scrypt@ PBKDF algorithms, 'pwdhashTimed' returns
-- parameters in a different order than the order in which they should be passed
-- to 'pwdhash'. This is a known issue with the Botan C++ library. See
-- <https://github.com/randombit/botan/issues/2144> for more information.
--
-- 'pwdhashTimed' always returns parameters in this order:
--
-- > (iterations, parallelism, memoryParam)
--
-- 'pwdhash' shoulds be given parameters in this order for the
-- @Argon2@ and @Scrypt@ algorithms:
--
-- > param1 = memoryParam
-- > param2 = iterations
-- > param3 = parallelism
--
pwdhashTimed ::
-- | __algo__: PBKDF algorithm, e.g., "Scrypt" or "PBKDF2(SHA-256)"
PBKDFName
-- | __msec__: the desired runtime in milliseconds
-> Int
-- | __out_len__: the desired length of the key to produce
-> Int
-- | __passphrase__: the password to derive the key from
-> ByteString
-- | __salt[]__: a randomly chosen salt
-> ByteString
-- | A tuple of four elements (in order):
--
-- * __param1__: the first PBKDF algorithm parameter
-- * __param2__: the second PBKDF algorithm parameter (may be zero if unneeded)
-- * __param3__: the third PBKDF algorithm parameter (may be zero if unneeded)
-- * __out[]__: buffer to store the derived key, must be of out_len bytes
-> IO (Int, Int, Int, ByteString)
pwdhashTimed algo msec outLen passphrase salt =
alloca $ \ p1Ptr ->
alloca $ \ p2Ptr ->
alloca $ \ p3Ptr -> do
out <- allocBytes outLen $ \ outPtr ->
asCString algo $ \ algoPtr ->
asCStringLen passphrase $ \ passphrasePtr passphraseLen ->
asBytesLen salt $ \ saltPtr saltLen -> do
throwBotanIfNegative_ $ botan_pwdhash_timed
(ConstPtr algoPtr)
(fromIntegral msec)
p1Ptr
p2Ptr
p3Ptr
outPtr
(fromIntegral outLen)
(ConstPtr passphrasePtr)
passphraseLen
(ConstPtr saltPtr)
saltLen
p1 <- fromIntegral <$> peek p1Ptr
p2 <- fromIntegral <$> peek p2Ptr
p3 <- fromIntegral <$> peek p3Ptr
return (p1, p2, p3, out)
{-------------------------------------------------------------------------------
Available schemes
-------------------------------------------------------------------------------}
{- $available-schemes
A number of algorithms are available to be used as password derivation schemes.
An algorithm is selected by passing the /algorithm specification name/ of the
algorithm as a string to the password hashing functions. In some cases an
algorithm specification name is just the /algorithm name/, like @"Scrypt"@. In
other cases an algorithm specification name is a combination of an algorithm
name paired with some parameters, such as @"PKBDF2(SHA-256)"@ where @"PBKDF2"@
is the algorithm name followed by a single parameter. Pattern synonyms are
included for each available /algorithm name/. Where necessary, such as in the
@"PBKDF2"@ case, these algorithm names can be be manually extended to full
algorithm specification names, or by using utility functions like 'pbkdf2' that
this module also provides.
See the [Available
Schemes](https://botan.randombit.net/handbook/api_ref/pbkdf.html#available-schemes)
section of the C++ API reference for more information about available schemes.
-}
-- | The name of a key derivation scheme used as a PBKDF algorithm
type PBKDFName = ByteString
pattern PBKDF2
, Scrypt
, Argon2d
, Argon2i
, Argon2id
, Bcrypt_PBKDF
, OpenPGP_S2K
:: PBKDFName
-- |
-- >>> PBKDF2
-- "PBKDF2"
pattern PBKDF2 = BOTAN_PBKDF_PBKDF2
-- |
-- >>> pbkdf2 SHA256
-- "PBKDF2(SHA-256)"
--
-- NOTE: "PBKDF(HMAC(SHA-256))" is equivalent to "PBKDF(SHA-256)". 'pbkdf2'
-- always outputs the latter given a hash function name.
pbkdf2 :: HashName -> PBKDFName
pbkdf2 m = PBKDF2 /$ m
-- |
-- >>> Scrypt
-- "Scrypt"
pattern Scrypt = BOTAN_PBKDF_SCRYPT
-- |
-- >>> Argon2d
-- "Argon2d"
pattern Argon2d = BOTAN_PBKDF_ARGON2D
-- |
-- >>> Argon2i
-- "Argon2i"
pattern Argon2i = BOTAN_PBKDF_ARGON2I
-- |
-- >>> Argon2id
-- "Argon2id"
pattern Argon2id = BOTAN_PBKDF_ARGON2ID
-- |
-- >>> Bcrypt_PBKDF
-- "Bcrypt-PBKDF"
pattern Bcrypt_PBKDF = BOTAN_PBKDF_BCRYPT_PBKDF
-- |
-- >>> OpenPGP_S2K
-- "OpenPGP-S2K"
pattern OpenPGP_S2K = BOTAN_PBKDF_OPENPGP_S2K
-- |
-- >>> openPGP_S2K SHA256
-- "OpenPGP-S2K(SHA-256)"
openPGP_S2K:: HashName -> PBKDFName
openPGP_S2K h = OpenPGP_S2K /$ h