module Crypto.KDF.BCrypt (
hashPassword,
tryHashPassword,
validatePassword,
validatePasswordEither,
bcrypt,
tryBcrypt,
)
where
import Control.Monad (unless, when)
import Crypto.Cipher.Blowfish.Primitive (bcryptHash)
import Crypto.Error
import Crypto.Random (MonadRandom, getRandomBytes)
import Data.ByteArray (
ByteArray,
ByteArrayAccess,
Bytes,
)
import qualified Data.ByteArray as B
import Data.ByteArray.Encoding
import Data.Char
data BCryptHash = BCH Char Int Bytes Bytes
hashPassword
:: (MonadRandom m, ByteArray password, ByteArray hash)
=> Int
-> password
-> m hash
hashPassword :: forall (m :: * -> *) password hash.
(MonadRandom m, ByteArray password, ByteArray hash) =>
Int -> password -> m hash
hashPassword Int
cost password
password = CryptoFailable hash -> hash
forall a. CryptoFailable a -> a
throwCryptoError (CryptoFailable hash -> hash) -> m (CryptoFailable hash) -> m hash
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Int -> password -> m (CryptoFailable hash)
forall (m :: * -> *) password hash.
(MonadRandom m, ByteArray password, ByteArray hash) =>
Int -> password -> m (CryptoFailable hash)
tryHashPassword Int
cost password
password
tryHashPassword
:: (MonadRandom m, ByteArray password, ByteArray hash)
=> Int
-> password
-> m (CryptoFailable hash)
tryHashPassword :: forall (m :: * -> *) password hash.
(MonadRandom m, ByteArray password, ByteArray hash) =>
Int -> password -> m (CryptoFailable hash)
tryHashPassword Int
cost password
password = do
Bytes
salt <- Int -> m Bytes
forall byteArray. ByteArray byteArray => Int -> m byteArray
forall (m :: * -> *) byteArray.
(MonadRandom m, ByteArray byteArray) =>
Int -> m byteArray
getRandomBytes Int
16
CryptoFailable hash -> m (CryptoFailable hash)
forall a. a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return (CryptoFailable hash -> m (CryptoFailable hash))
-> CryptoFailable hash -> m (CryptoFailable hash)
forall a b. (a -> b) -> a -> b
$ Int -> Bytes -> password -> CryptoFailable hash
forall salt password output.
(ByteArray salt, ByteArray password, ByteArray output) =>
Int -> salt -> password -> CryptoFailable output
tryBcrypt Int
cost (Bytes
salt :: Bytes) password
password
bcrypt
:: (ByteArray salt, ByteArray password, ByteArray output)
=> Int
-> salt
-> password
-> output
bcrypt :: forall salt password output.
(ByteArray salt, ByteArray password, ByteArray output) =>
Int -> salt -> password -> output
bcrypt Int
cost salt
salt password
password = CryptoFailable output -> output
forall a. CryptoFailable a -> a
throwCryptoError (Int -> salt -> password -> CryptoFailable output
forall salt password output.
(ByteArray salt, ByteArray password, ByteArray output) =>
Int -> salt -> password -> CryptoFailable output
tryBcrypt Int
cost salt
salt password
password)
tryBcrypt
:: (ByteArray salt, ByteArray password, ByteArray output)
=> Int
-> salt
-> password
-> CryptoFailable output
tryBcrypt :: forall salt password output.
(ByteArray salt, ByteArray password, ByteArray output) =>
Int -> salt -> password -> CryptoFailable output
tryBcrypt Int
cost salt
salt password
password
| Int
cost Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
4 Bool -> Bool -> Bool
|| Int
cost Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
31 = CryptoError -> CryptoFailable output
forall a. CryptoError -> CryptoFailable a
CryptoFailed CryptoError
CryptoError_ParameterInvalid
| salt -> Int
forall ba. ByteArrayAccess ba => ba -> Int
B.length salt
salt Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
/= Int
16 = CryptoError -> CryptoFailable output
forall a. CryptoError -> CryptoFailable a
CryptoFailed CryptoError
CryptoError_ParameterInvalid
| Bool
otherwise =
output -> CryptoFailable output
forall a. a -> CryptoFailable a
CryptoPassed (output -> CryptoFailable output)
-> output -> CryptoFailable output
forall a b. (a -> b) -> a -> b
$
[salt] -> output
forall bin bout.
(ByteArrayAccess bin, ByteArray bout) =>
[bin] -> bout
B.concat [salt
header, salt -> Word8 -> salt
forall a. ByteArray a => a -> Word8 -> a
B.snoc salt
costBytes Word8
dollar, salt -> salt
forall ba. ByteArray ba => ba -> ba
b64 salt
salt, salt -> salt
forall ba. ByteArray ba => ba -> ba
b64 salt
hash]
where
hash :: salt
hash = Char -> Int -> salt -> password -> salt
forall salt password output.
(ByteArrayAccess salt, ByteArray password, ByteArray output) =>
Char -> Int -> salt -> password -> output
rawHash Char
'b' Int
cost salt
salt password
password
header :: salt
header = [Word8] -> salt
forall a. ByteArray a => [Word8] -> a
B.pack [Word8
dollar, Int -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Char -> Int
ord Char
'2'), Int -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Char -> Int
ord Char
'b'), Word8
dollar]
dollar :: Word8
dollar = Int -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Char -> Int
ord Char
'$')
zero :: Word8
zero = Int -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Char -> Int
ord Char
'0')
costBytes :: salt
costBytes =
[Word8] -> salt
forall a. ByteArray a => [Word8] -> a
B.pack
[ Word8
zero Word8 -> Word8 -> Word8
forall a. Num a => a -> a -> a
+ Int -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int
cost Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
10)
, Word8
zero Word8 -> Word8 -> Word8
forall a. Num a => a -> a -> a
+ Int -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int
cost Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
10)
]
b64 :: ByteArray ba => ba -> ba
b64 :: forall ba. ByteArray ba => ba -> ba
b64 = Base -> ba -> ba
forall bin bout.
(ByteArrayAccess bin, ByteArray bout) =>
Base -> bin -> bout
convertToBase Base
Base64OpenBSD
validatePassword
:: (ByteArray password, ByteArray hash) => password -> hash -> Bool
validatePassword :: forall password hash.
(ByteArray password, ByteArray hash) =>
password -> hash -> Bool
validatePassword password
password hash
bcHash = (String -> Bool) -> (Bool -> Bool) -> Either String Bool -> Bool
forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either (Bool -> String -> Bool
forall a b. a -> b -> a
const Bool
False) Bool -> Bool
forall a. a -> a
id (password -> hash -> Either String Bool
forall password hash.
(ByteArray password, ByteArray hash) =>
password -> hash -> Either String Bool
validatePasswordEither password
password hash
bcHash)
validatePasswordEither
:: (ByteArray password, ByteArray hash) => password -> hash -> Either String Bool
validatePasswordEither :: forall password hash.
(ByteArray password, ByteArray hash) =>
password -> hash -> Either String Bool
validatePasswordEither password
password hash
bcHash = do
BCH Char
version Int
cost Bytes
salt Bytes
hash <- hash -> Either String BCryptHash
forall ba. ByteArray ba => ba -> Either String BCryptHash
parseBCryptHash hash
bcHash
Bool -> Either String Bool
forall a. a -> Either String a
forall (m :: * -> *) a. Monad m => a -> m a
return (Bool -> Either String Bool) -> Bool -> Either String Bool
forall a b. (a -> b) -> a -> b
$ (Char -> Int -> Bytes -> password -> Bytes
forall salt password output.
(ByteArrayAccess salt, ByteArray password, ByteArray output) =>
Char -> Int -> salt -> password -> output
rawHash Char
version Int
cost Bytes
salt password
password :: Bytes) Bytes -> Bytes -> Bool
forall bs1 bs2.
(ByteArrayAccess bs1, ByteArrayAccess bs2) =>
bs1 -> bs2 -> Bool
`B.constEq` Bytes
hash
rawHash
:: (ByteArrayAccess salt, ByteArray password, ByteArray output)
=> Char -> Int -> salt -> password -> output
rawHash :: forall salt password output.
(ByteArrayAccess salt, ByteArray password, ByteArray output) =>
Char -> Int -> salt -> password -> output
rawHash Char
_ Int
cost salt
salt password
password = case Int -> salt -> Bytes -> Maybe output
forall salt key output.
(ByteArrayAccess salt, ByteArrayAccess key, ByteArray output) =>
Int -> salt -> key -> Maybe output
bcryptHash Int
cost salt
salt Bytes
key of
Just output
hash -> Int -> output -> output
forall bs. ByteArray bs => Int -> bs -> bs
B.take Int
23 output
hash
Maybe output
Nothing -> String -> output
forall a. HasCallStack => String -> a
error String
"bcrypt: the cost or the salt is not one bcrypt takes"
where
key :: Bytes
key = Bytes -> Word8 -> Bytes
forall a. ByteArray a => a -> Word8 -> a
B.snoc (Int -> Bytes -> Bytes
forall bs. ByteArray bs => Int -> bs -> bs
B.take Int
72 (password -> Bytes
forall bin bout.
(ByteArrayAccess bin, ByteArray bout) =>
bin -> bout
B.convert password
password :: Bytes)) Word8
0
parseBCryptHash :: ByteArray ba => ba -> Either String BCryptHash
parseBCryptHash :: forall ba. ByteArray ba => ba -> Either String BCryptHash
parseBCryptHash ba
bc = do
Bool -> Either String () -> Either String ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless
( ba -> Int
forall ba. ByteArrayAccess ba => ba -> Int
B.length ba
bc Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
60
Bool -> Bool -> Bool
&& ba -> Int -> Word8
forall a. ByteArrayAccess a => a -> Int -> Word8
B.index ba
bc Int
0 Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
dollar
Bool -> Bool -> Bool
&& ba -> Int -> Word8
forall a. ByteArrayAccess a => a -> Int -> Word8
B.index ba
bc Int
1 Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Int -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Char -> Int
ord Char
'2')
Bool -> Bool -> Bool
&& ba -> Int -> Word8
forall a. ByteArrayAccess a => a -> Int -> Word8
B.index ba
bc Int
3 Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
dollar
Bool -> Bool -> Bool
&& ba -> Int -> Word8
forall a. ByteArrayAccess a => a -> Int -> Word8
B.index ba
bc Int
6 Word8 -> Word8 -> Bool
forall a. Eq a => a -> a -> Bool
== Word8
dollar
)
(String -> Either String ()
forall a b. a -> Either a b
Left String
"Invalid hash format")
Bool -> Either String () -> Either String ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
unless
(Char
version Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'b' Bool -> Bool -> Bool
|| Char
version Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'a' Bool -> Bool -> Bool
|| Char
version Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'y')
(String -> Either String ()
forall a b. a -> Either a b
Left (String
"Unsupported minor version: " String -> String -> String
forall a. [a] -> [a] -> [a]
++ [Char
version]))
Bool -> Either String () -> Either String ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Int
costTens Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
3 Bool -> Bool -> Bool
|| Int
cost Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
31 Bool -> Bool -> Bool
|| Int
cost Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
4) (String -> Either String ()
forall a b. a -> Either a b
Left String
"Invalid bcrypt cost")
(Bytes
salt, Bytes
hash) <- ba -> Either String (Bytes, Bytes)
forall {a} {b} {bin}.
(ByteArray a, ByteArray b, ByteArray bin) =>
bin -> Either String (a, b)
decodeSaltHash (Int -> ba -> ba
forall bs. ByteArray bs => Int -> bs -> bs
B.drop Int
7 ba
bc)
BCryptHash -> Either String BCryptHash
forall a. a -> Either String a
forall (m :: * -> *) a. Monad m => a -> m a
return (Char -> Int -> Bytes -> Bytes -> BCryptHash
BCH Char
version Int
cost Bytes
salt Bytes
hash)
where
dollar :: Word8
dollar = Int -> Word8
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Char -> Int
ord Char
'$')
zero :: Int
zero = Char -> Int
ord Char
'0'
costTens :: Int
costTens = Word8 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ba -> Int -> Word8
forall a. ByteArrayAccess a => a -> Int -> Word8
B.index ba
bc Int
4) Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
zero
costUnits :: Int
costUnits = Word8 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ba -> Int -> Word8
forall a. ByteArrayAccess a => a -> Int -> Word8
B.index ba
bc Int
5) Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
zero
version :: Char
version = Int -> Char
chr (Word8 -> Int
forall a b. (Integral a, Num b) => a -> b
fromIntegral (ba -> Int -> Word8
forall a. ByteArrayAccess a => a -> Int -> Word8
B.index ba
bc Int
2))
cost :: Int
cost = Int
costUnits Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
10 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
costTens :: Int
decodeSaltHash :: bin -> Either String (a, b)
decodeSaltHash bin
saltHash = do
let (bin
s, bin
h) = Int -> bin -> (bin, bin)
forall bs. ByteArray bs => Int -> bs -> (bs, bs)
B.splitAt Int
22 bin
saltHash
a
salt <- Base -> bin -> Either String a
forall bin bout.
(ByteArrayAccess bin, ByteArray bout) =>
Base -> bin -> Either String bout
convertFromBase Base
Base64OpenBSD bin
s
b
hash <- Base -> bin -> Either String b
forall bin bout.
(ByteArrayAccess bin, ByteArray bout) =>
Base -> bin -> Either String bout
convertFromBase Base
Base64OpenBSD bin
h
(a, b) -> Either String (a, b)
forall a. a -> Either String a
forall (m :: * -> *) a. Monad m => a -> m a
return (a
salt, b
hash)