bcrypt-0.0.4: Crypto/BCrypt.hs
{-# LANGUAGE ForeignFunctionInterface #-}
{- | A module for hashing passwords with bcrypt.
>>> import Crypto.BCrypt
>>> let p = Data.ByteString.Char8.pack
>>> hashPasswordUsingPolicy slowerBcryptHashingPolicy (p "mypassword")
Just "$2y$14$xBBZdWgTa8fSU1aPFP5IxeVdUKfT7hUDjmusZEAiNBiYaYEGY/Sh6"
>>> validatePassword (p "$2y$14$xBBZdWgTa8fSU1aPFP5IxeVdUKfT7hUDjmusZEAiNBiYaYEGY/Sh6") (p "badpass")
False
>>> validatePassword (p "$2y$14$xBBZdWgTa8fSU1aPFP5IxeVdUKfT7hUDjmusZEAiNBiYaYEGY/Sh6") (p "mypassword")
True
>>> hashUsesPolicy slowerBcryptHashingPolicy (p "$2y$14$xBBZdWgTa8fSU1aPFP5IxeVdUKfT7hUDjmusZEAiNBiYaYEGY/Sh6")
True
>>> hashUsesPolicy fastBcryptHashingPolicy (p "$2y$14$xBBZdWgTa8fSU1aPFP5IxeVdUKfT7hUDjmusZEAiNBiYaYEGY/Sh6")
False
-}
module Crypto.BCrypt (HashingPolicy(..), hashPasswordUsingPolicy, validatePassword,
fastBcryptHashingPolicy, slowerBcryptHashingPolicy,
hashUsesPolicy, hashPassword, genSalt, genSaltUsingPolicy)
where
import Foreign
import Foreign.C.String
import Foreign.C.Types
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Unsafe as BS
import qualified System.IO.Unsafe as U
import Control.Monad
import System.Entropy
foreign import ccall "crypt_ra" c_crypt_ra :: CString -> CString -> Ptr CString -> Ptr CInt -> IO CString
foreign import ccall "crypt_gensalt_ra" c_crypt_gensalt_ra :: CString -> CULong -> CString -> CInt -> IO CString
-- | A hashing policy defines the type of password hashing to use.
data HashingPolicy = HashingPolicy {
-- | Preferred cost - how strong new passwords should be. This is a trade-off
-- between making hasing / checking passwords faster in your system, and making
-- brute forcing passwords harder for an adversary.
-- The intention is that this can be increased as computers get faster.
-- To give a rough indication of the scale of preferredCost,
-- on a 2.6 GHz AMD Athlon machine (64 bit kernel), using a single core:
--
-- * Cost 4: 139 passwords / second
--
-- * Cost 5: 85 passwords / second
--
-- * Cost 6: 44 passwords / second
--
-- * Cost 7: 23 passwords / second
--
-- * Cost 8: 11 passwords / second
--
-- * Cost 9: 5.7 passwords / second
--
-- * Cost 10: 2.8 passwords / second
--
-- * Cost 11: 1.4 passwords / second
--
-- * Cost 12: 0.72 passwords / second
preferredHashCost :: Int,
-- | Preferred algorithm - the preferred hash algorithm. $2y$ for bcrypt.
preferredHashAlgorithm :: BS.ByteString
}
-- | Hashes a password, using a hashing policy.
hashPasswordUsingPolicy :: HashingPolicy -> BS.ByteString -> IO (Maybe BS.ByteString)
hashPasswordUsingPolicy hp pw = do
ms <- genSaltUsingPolicy hp
return $ do
s <- ms
hashPassword pw s
-- | Validates a password. The first argument is the hashed password, the second is
-- the password attempt.
-- Note: If a password validates successfully, it is a good idea to check if the
-- password is up to the current policy using hashUsesPolicy, and re-hashing it
-- if not.
validatePassword :: BS.ByteString -> BS.ByteString -> Bool
validatePassword h pw =
case hashPassword pw h
of
Nothing -> False
Just h2 -> h2 == h
-- | A policy that allows passwords to be hashed reasonably quickly, but for that
-- reason isn't suitable for high security applications.
fastBcryptHashingPolicy :: HashingPolicy
fastBcryptHashingPolicy = HashingPolicy 4 (BS.pack "$2y$")
-- | A policy which makes password hashing substantially slower than
-- fastBcryptHashingPolicy, and so makes it more difficult for an adversary to
-- decrypt passwords. In a high security environment, this policy should be
-- regularly reviewed against hardware developments.
slowerBcryptHashingPolicy :: HashingPolicy
slowerBcryptHashingPolicy = fastBcryptHashingPolicy { preferredHashCost = 14 }
-- | Check whether a password hash is consistent with the current policy, or if
-- it should be updated.
hashUsesPolicy :: HashingPolicy -> BS.ByteString -> Bool
hashUsesPolicy (HashingPolicy phc pha) str =
let phaLen = BS.length pha
strPref = BS.take phaLen str
strInfo = BS.take 2 (BS.drop phaLen str)
hcBase = if phc < 10 then '0':(show phc) else show phc
in
(strPref == pha) && (hcBase == BS.unpack strInfo)
-- | Hashes a password (first argument) using the settings specified in second
-- argument. The settings describe the hashing variant and salt to use; because
-- the settings are prepended to password hashes, passing in an existing password
-- hash will cause the same settings to be used again.
-- You can create a hash using genSalt.
-- Result: Just hash on success, Nothing on failure (invalid settings).
hashPassword :: BS.ByteString -> BS.ByteString -> Maybe BS.ByteString
hashPassword pw setting =
U.unsafePerformIO $ BS.useAsCString pw $
\pw' -> BS.useAsCString setting $
\setting' -> alloca $ \data' -> alloca $ \dlen -> do
poke dlen 0
poke data' nullPtr
res <- c_crypt_ra pw' setting' data' dlen
newData <- peek data'
if newData == nullPtr
then
return Nothing
else
do
ret <- if res == nullPtr
then
return Nothing
else
liftM Just $ BS.packCString res
free newData
return ret
-- | Prepares a settings string and salt suitable for use with hashPassword.
-- Takes a prefix specifying the type of hash, an integer specifying the
-- computational cost of hashing (4-32, or 0 for a low default), and a
-- string of random entropy.
genSalt :: BS.ByteString -> Int -> BS.ByteString -> Maybe BS.ByteString
genSalt settings cost entropy =
U.unsafePerformIO $ BS.useAsCString settings $ \settings' ->
BS.unsafeUseAsCString entropy $ \entropy' -> do
res <- c_crypt_gensalt_ra settings' (fromIntegral cost) entropy' (fromIntegral $ BS.length entropy)
if res == nullPtr
then
return Nothing
else
do
ret <- BS.packCString res
free res
return $ Just ret
-- | Generates a salt using a policy, sampling from a system-appropriate source.
genSaltUsingPolicy :: HashingPolicy -> IO (Maybe BS.ByteString)
genSaltUsingPolicy (HashingPolicy hc ha) = do
ent <- getEntropy 16
return $ genSalt ha hc ent