botan-low-0.0.2.0: src/Botan/Low/Bcrypt.hs
{-|
Module : Botan.Low.Bcrypt
Description : Bcrypt 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
Generate and validate Bcrypt password hashes
-}
module Botan.Low.Bcrypt
(
-- * Bcrypt
-- $introduction
-- * Usage
-- $usage
-- * Generate a bcrypt digest
bcryptGenerate
-- * Validate a bcrypt digest
, bcryptIsValid
-- * Work factor
, BcryptWorkFactor(..)
, pattern BcryptFast
, pattern BcryptGood
, pattern BcryptStrong
, BcryptPassword(..)
, BcryptDigest(..)
) where
import qualified Data.ByteString as ByteString
import Botan.Bindings.Bcrypt
import Botan.Low.Error
import Botan.Low.Make
import Botan.Low.Prelude
import Botan.Low.RNG
import Data.ByteString.Internal as ByteString
{- $introduction
`bcrypt` is a password-hashing algorithm designed to protect your passwords
against hackers using an expensive key setup phase. Instead of storing a user's
password in plaintext in the database, the server may instead generate a salted
bcrypt digest upon signup, and verify it upon login.
The `bcrypt` implementation provided by `botan` generates a random salt for you
automatically. A work factor of 12 or greater is recommended.
-}
{- $usage
To generate a bcrypt digest:
> import Botan.Low.RNG
> import Botan.Low.Bcrypt
>
> -- The user has sent us a username and password in order to sign up
> onUserSignup :: ByteString -> ByteString -> IO ()
> onUserSignup username password = do
> rng <- rngInit "user"
> digest <- bcryptGenerate password rng 12
> createAndStoreNewUser username digest
To validate a bcrypt digest:
> import Botan.Low.RNG
> import Botan.Low.Bcrypt
>
> -- The user has sent us a username and password in order to log in
> onUserLogin :: ByteString -> ByteString -> IO Bool
> onUserLogin username password = do
> rng <- rngInit "user"
> digestMaybe <- getStoredUserDigest username
> case digestMaybe of
> Nothing -> return False
> Just digest -> bcryptIsValid password digest
-}
-- | A work factor to slow down guessing attacks.
type BcryptWorkFactor = Int
pattern BcryptFast
, BcryptGood
, BcryptStrong
:: BcryptWorkFactor
-- | Should not cause noticable CPU usage
pattern BcryptFast = BOTAN_BCRYPT_WORK_FACTOR_FAST
-- | May cause noticable CPU usage
pattern BcryptGood = BOTAN_BCRYPT_WORK_FACTOR_GOOD
-- | May block for several seconds
pattern BcryptStrong = BOTAN_BCRYPT_WORK_FACTOR_STRONG
-- | A bcrypt password.
type BcryptPassword = ByteString
-- | A bcrypt-hashed password digest.
type BcryptDigest = ByteString
{- |
Create a password hash using Bcrypt
> rng <- rngInit "user"
> digest <- bcryptGenerate password rng 12
Output is formatted bcrypt $2a$...
-}
bcryptGenerate
:: BcryptPassword -- ^ __password__: The password
-> RNG -- ^ __rng__: A random number generator
-> BcryptWorkFactor -- ^ __work_factor__: How much work to do to slow down guessing attacks. A value of 12 to 16 is probably fine.
-> IO BcryptDigest
bcryptGenerate password rng factor = asCString password $ \ passwordPtr -> do
withRNG rng $ \ botanRNG -> do
alloca $ \ szPtr -> do
-- NOTE: bcrypt max pass size should be < 72 in general, we'll
-- do 80 for safety
poke szPtr 80
allocaBytes 80 $ \ outPtr -> do
throwBotanIfNegative_ $ botan_bcrypt_generate
outPtr
szPtr
(ConstPtr passwordPtr)
botanRNG
(fromIntegral factor)
0 -- "@param flags should be 0 in current API revision, all other uses are reserved"
ByteString.packCString (castPtr outPtr)
{- |
Check a previously created password hash
> valid <- bcryptIsValid password digest
> if valid ...
Returns True iff this password/hash combination is valid,
False if the combination is not valid (but otherwise well formed),
and otherwise throws an exception on error.
-}
bcryptIsValid
:: BcryptPassword -- ^ __password__: The password to check against
-> BcryptDigest -- ^ __hash__: The stored hash to check against
-> IO Bool
bcryptIsValid password hash = asCString password $ \ passwordPtr -> do
asCString hash $ \ hashPtr -> do
throwBotanCatchingSuccess $ botan_bcrypt_is_valid (ConstPtr passwordPtr) (ConstPtr hashPtr)