packages feed

crypton-2.0.0: Crypto/KDF/Argon2.hs

-- |
-- Module      : Crypto.KDF.Argon2
-- License     : BSD-style
-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
-- Stability   : experimental
-- Portability : unknown
--
-- Argon2 hashing function (P-H-C winner)
--
-- Recommended to use this module qualified
--
-- File started from Argon2.hs, from Oliver Charles
-- at https://github.com/ocharles/argon2
module Crypto.KDF.Argon2 (
    Options (..),
    TimeCost,
    MemoryCost,
    Parallelism,
    Variant (..),
    Version (..),
    defaultOptions,

    -- * Hashing function
    hash,
) where

import Crypto.Error
import Crypto.Internal.ByteArray (ByteArray, ByteArrayAccess)
import qualified Crypto.Internal.ByteArray as B
import Crypto.Internal.Compat (unsafeDoIO)
import Data.Word
import Foreign.C
import Foreign.Ptr

-- | Which variant of Argon2 to use. You should choose the variant that is most
-- applicable to your intention to hash inputs.
data Variant
    = -- | Argon2d is faster than Argon2i and uses data-depending memory access,
      -- which makes it suitable for cryptocurrencies and applications with no
      -- threats from side-channel timing attacks.
      Argon2d
    | -- | Argon2i uses data-independent memory access, which is preferred
      -- for password hashing and password-based key derivation. Argon2i
      -- is slower as it makes more passes over the memory to protect from
      -- tradeoff attacks.
      Argon2i
    | -- | Argon2id is a hybrid of Argon2i and Argon2d, using a combination
      -- of data-depending and data-independent memory accesses, which gives
      -- some of Argon2i's resistance to side-channel cache timing attacks
      -- and much of Argon2d's resistance to GPU cracking attacks
      Argon2id
    deriving (Eq, Ord, Read, Show, Enum, Bounded)

-- | Which version of Argon2 to use
data Version = Version10 | Version13
    deriving (Eq, Ord, Read, Show, Enum, Bounded)

-- | The time cost, which defines the amount of computation realized and therefore the execution time, given in number of iterations.
--
-- 'FFI.ARGON2_MIN_TIME' <= 'iterations' <= 'FFI.ARGON2_MAX_TIME'
type TimeCost = Word32

-- | The memory cost, which defines the memory usage, given in kibibytes.
--
-- max 'FFI.ARGON2_MIN_MEMORY' (8 * 'parallelism') <= 'memory' <= 'FFI.ARGON2_MAX_MEMORY'
type MemoryCost = Word32

-- | A parallelism degree, which defines the number of parallel threads.
--
-- 'FFI.ARGON2_MIN_LANES' <= 'parallelism' <= 'FFI.ARGON2_MAX_LANES' && 'FFI.ARGON_MIN_THREADS' <= 'parallelism' <= 'FFI.ARGON2_MAX_THREADS'
type Parallelism = Word32

-- | Parameters that can be adjusted to change the runtime performance of the
-- hashing.
data Options = Options
    { iterations :: !TimeCost
    , memory :: !MemoryCost
    , parallelism :: !Parallelism
    , variant :: !Variant
    -- ^ Which variant of Argon2 to use.
    , version :: !Version
    -- ^ Which version of Argon2 to use.
    }
    deriving (Eq, Ord, Read, Show)

saltMinLength :: Int
saltMinLength = 8

outputMinLength :: Int
outputMinLength = 4

-- specification allows up to 2^32-1 but this is too big for a signed Int
-- on a 32-bit architecture, so we limit tag length to 2^31-1 bytes
outputMaxLength :: Int
outputMaxLength = 0x7fffffff

defaultOptions :: Options
defaultOptions =
    Options
        { iterations = 1
        , memory = 2 ^ (17 :: Int)
        , parallelism = 4
        , variant = Argon2i
        , version = Version13
        }

-- | Hash a password with Argon2.
--
-- Options the underlying implementation refuses -- iterations, memory or
-- parallelism outside the range it accepts -- are reported as
-- 'CryptoError_ParameterInvalid'.
hash
    :: (ByteArrayAccess password, ByteArrayAccess salt, ByteArray out)
    => Options
    -> password
    -> salt
    -> Int
    -> CryptoFailable out
hash options password salt outLen
    | saltLen < saltMinLength = CryptoFailed CryptoError_SaltTooSmall
    | outLen < outputMinLength = CryptoFailed CryptoError_OutputLengthTooSmall
    | outLen > outputMaxLength = CryptoFailed CryptoError_OutputLengthTooBig
    | otherwise = unsafeDoIO $ do
        -- the bounds on iterations, memory and parallelism are checked by the
        -- C implementation, which reports them through its return code
        (res, out) <- B.allocRet outLen $ \pOut ->
            B.withByteArray password $ \pPass ->
                B.withByteArray salt $ \pSalt ->
                    argon2_hash
                        (iterations options)
                        (memory options)
                        (parallelism options)
                        pPass
                        (csizeOfInt passwordLen)
                        pSalt
                        (csizeOfInt saltLen)
                        pOut
                        (csizeOfInt outLen)
                        (cOfVariant $ variant options)
                        (cOfVersion $ version options)
        return $
            if res == 0
                then CryptoPassed out
                else CryptoFailed CryptoError_ParameterInvalid
  where
    saltLen = B.length salt
    passwordLen = B.length password

data Pass
data Salt
data HashOut

type CVariant = CInt -- valid value is 0 (Argon2d), 1 (Argon2i) and 2 (Argon2id)
type CVersion = CInt -- valid value is 0x10, 0x13

cOfVersion :: Version -> CVersion
cOfVersion Version10 = 0x10
cOfVersion Version13 = 0x13

cOfVariant :: Variant -> CVariant
cOfVariant Argon2d = 0
cOfVariant Argon2i = 1
cOfVariant Argon2id = 2

csizeOfInt :: Int -> CSize
csizeOfInt = fromIntegral

foreign import ccall unsafe "crypton_argon2_hash"
    argon2_hash
        :: Word32
        -> Word32
        -> Word32
        -> Ptr Pass
        -> CSize
        -> Ptr Salt
        -> CSize
        -> Ptr HashOut
        -> CSize
        -> CVariant
        -> CVersion
        -> IO CInt