packages feed

botan-low-0.0.2.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

Derive a key from a passphrase
-}

module Botan.Low.PwdHash
(

-- * Password hashing

  PBKDFName(..)
, pwdhash
, pwdhashTimed

-- * Password hashing algorithms

, pattern PBKDF2
, pbkdf2
, pattern Scrypt
, pattern Argon2d
, pattern Argon2i
, pattern Argon2id
, pattern Bcrypt_PBKDF
, pattern OpenPGP_S2K
, openPGP_S2K

) where

import qualified Data.ByteString as ByteString

import Botan.Bindings.PwdHash

import Botan.Low.Hash
import Botan.Low.MAC
import Botan.Low.Error
import Botan.Low.Make
import Botan.Low.Prelude

type PBKDFName = ByteString

pattern PBKDF2
    ,   Scrypt
    ,   Argon2d
    ,   Argon2i
    ,   Argon2id
    ,   Bcrypt_PBKDF
    ,   OpenPGP_S2K
    :: PBKDFName

pattern PBKDF2 = BOTAN_PBKDF_PBKDF2
pattern Scrypt = BOTAN_PBKDF_SCRYPT
pattern Argon2d = BOTAN_PBKDF_ARGON2D
pattern Argon2i = BOTAN_PBKDF_ARGON2I
pattern Argon2id = BOTAN_PBKDF_ARGON2ID
pattern Bcrypt_PBKDF = BOTAN_PBKDF_BCRYPT_PBKDF
pattern OpenPGP_S2K = BOTAN_PBKDF_OPENPGP_S2K

-- NOTE: May require HMAC
pbkdf2 :: MACName -> PBKDFName
pbkdf2 m = PBKDF2 /$ m

openPGP_S2K:: HashName -> PBKDFName
openPGP_S2K h = OpenPGP_S2K /$ h

-- NOTE: Should passphrase be Text or ByteString? Text is implied by use of const char*
--  as well as the non-null context implied by passphrase_len == 0. ByteString for now.

-- | Password hash
--
-- 'pwdhash' and 'pwdhashTimed'\'s parameter order may be inconsistent.  See
-- botan-low\/test\/Botan\/Low\/PwdHashSpec.hs for more information.
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 -> do
    asCString algo $ \ algoPtr -> do
        asCStringLen passphrase $ \ passphrasePtr passphraseLen -> do
            asBytesLen salt $ \ saltPtr saltLen -> do
                throwBotanIfNegative_ $ botan_pwdhash
                    (ConstPtr algoPtr)
                    (fromIntegral p1)
                    (fromIntegral p2)
                    (fromIntegral p3)
                    outPtr
                    (fromIntegral outLen)
                    (ConstPtr passphrasePtr)
                    passphraseLen
                    (ConstPtr saltPtr)
                    saltLen

-- | Timed password hash
--
-- 'pwdhash' and 'pwdhashTimed'\'s parameter order may be inconsistent. See
-- botan-low\/test\/Botan\/Low\/PwdHashSpec.hs for more information.
pwdhashTimed
    :: PBKDFName                    -- ^ __algo__: PBKDF algorithm, e.g., "Scrypt" or "PBKDF2(SHA-256)"
    -> Int                          -- ^ __msec__: the desired runtime in milliseconds
    -> 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 (Int,Int,Int,ByteString)  -- ^ __out[]__: buffer to store the derived key, must be of out_len bytes
pwdhashTimed algo msec outLen passphrase salt = alloca $ \ p1Ptr -> alloca $ \ p2Ptr -> alloca $ \ p3Ptr -> do
    out <- allocBytes outLen $ \ outPtr -> do
        asCString algo $ \ algoPtr -> do
            asCStringLen passphrase $ \ passphrasePtr passphraseLen -> do
                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)