packages feed

argon2-1.0.0: src/Crypto/Argon2.hs

{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE RecordWildCards #-}

module Crypto.Argon2 (hashEncoded, hash, HashOptions(..), Argon2Variant(..), defaultHashOptions) where

import Control.Exception
import Data.Typeable
import Foreign
import Foreign.C
import Numeric.Natural
import System.IO.Unsafe (unsafePerformIO)
import qualified Data.ByteString as BS
import qualified Data.Text.Encoding as T
import qualified Data.Text as T
import qualified Crypto.Argon2.FFI as FFI

data Argon2Variant = Argon2i | Argon2d

data HashOptions =
  HashOptions {hashIterations :: !Word32
              ,hashMemory :: !Word32
              ,hashParallelism :: !Word32
              ,hashVariant :: !Argon2Variant}

defaultHashOptions :: HashOptions
defaultHashOptions =
  HashOptions {hashIterations = 3
              ,hashMemory = 2 ^ 12
              ,hashParallelism = 1
              ,hashVariant = Argon2i}

hashEncoded :: HashOptions -- ^ Options pertaining to how expensive the hash is to calculate
            -> BS.ByteString -- ^ The password to hash
            -> BS.ByteString -- ^ The salt to use when hashing
            -> T.Text -- ^ The encoded password hash
hashEncoded options password salt =
  unsafePerformIO (hash' options password salt FFI.argon2i_hash_encoded FFI.argon2d_hash_encoded asText)
  where asText = fmap T.decodeUtf8 . BS.packCString

hash :: HashOptions -- ^ Options pertaining to how expensive the hash is to calculate
     -> BS.ByteString -- ^ The password to hash
     -> BS.ByteString -- ^ The salt to use when hashing
     -> BS.ByteString -- ^ The un-encoded password hash
hash options password salt =
  unsafePerformIO (hash' options password salt FFI.argon2i_hash_encoded FFI.argon2d_hash_encoded BS.packCString)

variant :: a -> a -> Argon2Variant -> a
variant a _ Argon2i = a
variant _ b Argon2d = b
{-# INLINE variant #-}

type Argon2 a = Word32 -> Word32 -> Word32 -> CString -> Word64 -> CString -> Word64 -> Word64 -> a -> Word64 -> IO Int32

data Argon2Exception
  = Argon2PasswordLengthOutOfRange Word64 Word64 Word64
  | Argon2SaltLengthOutOfRange Word64 Word64 Word64
  | Argon2MemoryUseOutOfRange Word32 Word32 Word32
  | Argon2IterationCountOutOfRange Word32 Word32 Word32
  | Argon2ParallelismOutOfRange Word32 Word32 Word32
  | Argon2Exception Int32
  deriving (Typeable, Show)

instance Exception Argon2Exception

hash' :: HashOptions
      -> BS.ByteString
      -> BS.ByteString
      -> Argon2 (Ptr a)
      -> Argon2 (Ptr a)
      -> (Ptr a -> IO b)
      -> IO b
hash' HashOptions{..} password salt argon2i argon2d postProcess =
  do out <- mallocBytes 512
     let saltLen = fromIntegral (BS.length salt)
         passwordLen = fromIntegral (BS.length password)
     res <-
       BS.useAsCString password $
       \password' ->
         BS.useAsCString salt $
         \salt' ->
           argon2 hashIterations
                  hashMemory
                  hashParallelism
                  password'
                  passwordLen
                  salt'
                  saltLen
                  64
                  out
                  512
     case res of
       a
         | a `elem` [FFI.ARGON2_OK] -> postProcess out
         | a `elem` [FFI.ARGON2_SALT_TOO_SHORT,FFI.ARGON2_SALT_TOO_LONG] ->
           throwIO (Argon2SaltLengthOutOfRange saltLen
                                               FFI.ARGON2_MIN_SALT_LENGTH
                                               FFI.ARGON2_MAX_SALT_LENGTH)
         | a `elem` [FFI.ARGON2_PWD_TOO_SHORT,FFI.ARGON2_PWD_TOO_LONG] ->
           throwIO (Argon2PasswordLengthOutOfRange passwordLen
                                                   FFI.ARGON2_MIN_PWD_LENGTH
                                                   FFI.ARGON2_MAX_PWD_LENGTH)
         | a `elem` [FFI.ARGON2_TIME_TOO_SMALL,FFI.ARGON2_TIME_TOO_LARGE] ->
           throwIO (Argon2IterationCountOutOfRange hashIterations
                                                   FFI.ARGON2_MIN_TIME
                                                   FFI.ARGON2_MAX_TIME)
         | a `elem` [FFI.ARGON2_MEMORY_TOO_LITTLE,FFI.ARGON2_MEMORY_TOO_MUCH] ->
           throwIO (Argon2MemoryUseOutOfRange
                      hashMemory
                      (max FFI.ARGON2_MIN_MEMORY (8 * hashParallelism))
                      FFI.ARGON2_MAX_MEMORY)
         | a `elem` [FFI.ARGON2_LANES_TOO_FEW,FFI.ARGON2_LANES_TOO_MANY] ->
           throwIO (Argon2ParallelismOutOfRange hashParallelism
                                                FFI.ARGON2_MIN_LANES
                                                FFI.ARGON2_MAX_LANES)
         | otherwise -> throwIO (Argon2Exception a)
  where argon2 = variant argon2i argon2d hashVariant