packages feed

argon2 1.0.0 → 1.1.0

raw patch · 8 files changed

+323/−88 lines, 8 filesdep +QuickCheckdep +argon2dep +tastyPVP ok

version bump matches the API change (PVP)

Dependencies added: QuickCheck, argon2, tasty, tasty-quickcheck

API changes (from Hackage documentation)

+ Crypto.Argon2: Argon2Exception :: !Int32 -> Argon2Exception
+ Crypto.Argon2: Argon2IterationCountOutOfRange :: !Word32 -> Argon2Exception
+ Crypto.Argon2: Argon2MemoryUseOutOfRange :: !Word32 -> Argon2Exception
+ Crypto.Argon2: Argon2ParallelismOutOfRange :: !Word32 -> Argon2Exception
+ Crypto.Argon2: Argon2PasswordLengthOutOfRange :: !Word64 -> Argon2Exception
+ Crypto.Argon2: Argon2SaltLengthOutOfRange :: !Word64 -> Argon2Exception
+ Crypto.Argon2: data Argon2Exception
+ Crypto.Argon2: instance GHC.Classes.Eq Crypto.Argon2.Argon2Variant
+ Crypto.Argon2: instance GHC.Classes.Eq Crypto.Argon2.HashOptions
+ Crypto.Argon2: instance GHC.Classes.Ord Crypto.Argon2.Argon2Variant
+ Crypto.Argon2: instance GHC.Classes.Ord Crypto.Argon2.HashOptions
+ Crypto.Argon2: instance GHC.Enum.Bounded Crypto.Argon2.Argon2Variant
+ Crypto.Argon2: instance GHC.Enum.Bounded Crypto.Argon2.HashOptions
+ Crypto.Argon2: instance GHC.Enum.Enum Crypto.Argon2.Argon2Variant
+ Crypto.Argon2: instance GHC.Generics.Constructor Crypto.Argon2.C1_0Argon2Variant
+ Crypto.Argon2: instance GHC.Generics.Constructor Crypto.Argon2.C1_0HashOptions
+ Crypto.Argon2: instance GHC.Generics.Constructor Crypto.Argon2.C1_1Argon2Variant
+ Crypto.Argon2: instance GHC.Generics.Datatype Crypto.Argon2.D1Argon2Variant
+ Crypto.Argon2: instance GHC.Generics.Datatype Crypto.Argon2.D1HashOptions
+ Crypto.Argon2: instance GHC.Generics.Generic Crypto.Argon2.Argon2Variant
+ Crypto.Argon2: instance GHC.Generics.Generic Crypto.Argon2.HashOptions
+ Crypto.Argon2: instance GHC.Generics.Selector Crypto.Argon2.S1_0_0HashOptions
+ Crypto.Argon2: instance GHC.Generics.Selector Crypto.Argon2.S1_0_1HashOptions
+ Crypto.Argon2: instance GHC.Generics.Selector Crypto.Argon2.S1_0_2HashOptions
+ Crypto.Argon2: instance GHC.Generics.Selector Crypto.Argon2.S1_0_3HashOptions
+ Crypto.Argon2: instance GHC.Read.Read Crypto.Argon2.Argon2Variant
+ Crypto.Argon2: instance GHC.Read.Read Crypto.Argon2.HashOptions
+ Crypto.Argon2: instance GHC.Show.Show Crypto.Argon2.Argon2Variant
+ Crypto.Argon2: instance GHC.Show.Show Crypto.Argon2.HashOptions
+ Crypto.Argon2: verify :: Text -> ByteString -> Bool
+ Crypto.Argon2.FFI: argon2d_verify :: CString -> Ptr a -> (Word64) -> IO (Int32)
+ Crypto.Argon2.FFI: argon2i_verify :: CString -> Ptr a -> (Word64) -> IO (Int32)

Files

+ Changelog.md view
@@ -0,0 +1,21 @@+# 1.1.0++- First stable release. Same API as 1.0.0, but now features documentation and+  expected type class instances for data types.++- QuickCheck properties added:++  1. verify (hashEncoded options password salt) password == True+  2. hash options password salt /= password++- `hash` now uses the underlying "raw" hash routines, rather than the encoded+  routines. This was a bug in 1.0.0. Thanks to @jorgen for this fix.++- `verify` added, in order to correctly verify that a password matches an +  encoded password.++- `defaultHashOptions` are now more expensive.++# 1.0.0++- Initial release
+ Tests.hs view
@@ -0,0 +1,84 @@+{-# LANGUAGE OverloadedStrings #-}++import Crypto.Argon2+import Test.QuickCheck+import Test.Tasty+import Data.Ix+import Test.Tasty.QuickCheck+import qualified Crypto.Argon2.FFI as FFI+import qualified Data.ByteString as BS+import qualified Data.Text.Encoding as T+import Data.Bits (shiftL)++arbitraryVariant :: Gen Argon2Variant+arbitraryVariant = arbitraryBoundedEnum++-- The hard-coded constants correspond to `decode_string`, but see+-- https://github.com/P-H-C/phc-winner-argon2/issues/77+arbitraryHashOptions :: Gen HashOptions+arbitraryHashOptions =+  do p <-+       arbitraryWithin (max (max FFI.ARGON2_MIN_LANES FFI.ARGON2_MIN_THREADS) 1)+                       (min (min FFI.ARGON2_MAX_LANES FFI.ARGON2_MAX_THREADS) 254)+     HashOptions <$>+       arbitraryWithin FFI.ARGON2_MIN_TIME+                       (min FFI.ARGON2_MAX_TIME (2 ^ 32 - 1)) <*>+       arbitraryWithin+         (max (max FFI.ARGON2_MIN_MEMORY (8 * p))+              (shiftL p 3))+         FFI.ARGON2_MAX_MEMORY <*>+       pure p <*>+       arbitraryVariant++arbitraryWithin lower upper = arbitrary `suchThat` (inRange (lower,upper))++arbitraryBytes :: Gen BS.ByteString+arbitraryBytes = fmap BS.pack arbitrary++arbitraryPassword :: Gen BS.ByteString+arbitraryPassword = arbitraryBytes `suchThat`+                             (\pwd ->+                                (FFI.ARGON2_MIN_PWD_LENGTH+                                ,FFI.ARGON2_MAX_PWD_LENGTH) `inRange`+                                BS.length pwd)++arbitrarySalt =+  arbitraryBytes `suchThat`+  (\salt ->+     (FFI.ARGON2_MIN_SALT_LENGTH,FFI.ARGON2_MAX_SALT_LENGTH) `inRange`+     BS.length salt)++main :: IO ()+main =+  defaultMain+    (testGroup "Properties"+               [testProperty+                  "Round trip"+                  (forAll arbitraryHashOptions $+                   \hashOptions ->+                     forAll arbitraryPassword $+                     \password ->+                       forAll arbitrarySalt $+                       \salt ->+                         verify (hashEncoded hashOptions password salt) password)+               ,testProperty+                  "Unencoded hashing"+                  (forAll arbitraryHashOptions $+                   \hashOptions ->+                     forAll arbitraryPassword $+                     \password ->+                       forAll arbitrarySalt $+                       \salt -> hash hashOptions password salt /= password)+               ,testProperty+                  "defaultHashOptions"+                  (forAll arbitraryVariant $+                   \variant ->+                     (forAll arbitraryPassword $+                      \password ->+                        forAll arbitrarySalt $+                        \salt ->+                          verify (hashEncoded+                                    (defaultHashOptions {hashVariant = variant})+                                    password+                                    salt)+                                 password))])
argon2.cabal view
@@ -1,8 +1,5 @@--- Initial argon2.cabal generated by cabal init.  For further --- documentation, see http://haskell.org/cabal/users-guide/- name:                argon2-version:             1.0.0+version:             1.1.0 synopsis:            Haskell bindings to libargon2 - the reference implementation of the Argon2 password-hashing function -- description:          homepage:            https://github.com/ocharles/argon2.git@@ -13,7 +10,7 @@ -- copyright:            -- category:             build-type:          Simple--- extra-source-files:  +extra-source-files:  Changelog.md cabal-version:       >=1.10  library@@ -29,3 +26,8 @@              phc-winner-argon2/src/ref.c              phc-winner-argon2/src/encoding.c   include-dirs: phc-winner-argon2/src++test-suite tests+  type: exitcode-stdio-1.0+  main-is: Tests.hs+  build-depends: argon2, base >= 4.8 && <4.9, QuickCheck >= 2.0, tasty-quickcheck, tasty, bytestring, text
phc-winner-argon2/src/argon2.c view
@@ -11,11 +11,9 @@  * <http://creativecommons.org/publicdomain/zero/1.0/>.  */ -#include <stdint.h> #include <string.h> #include <stdlib.h> #include <stdio.h>-#include <limits.h>  #include "argon2.h" #include "encoding.h"@@ -102,7 +100,9 @@     /*}, {ARGON2_ENCODING_FAIL, */ "Encoding failed",     /*},-{ARGON2_DECODING_FAIL, */ "Decoding failed", /*},*/+{ARGON2_DECODING_FAIL, */ "Decoding failed",+    /*},+{ARGON2_THREAD_FAIL */ "Threading failure", /*},*/ };  @@ -151,8 +151,11 @@     }      /* 4. Filling memory */-    fill_memory_blocks(&instance);+    result = fill_memory_blocks(&instance); +    if (ARGON2_OK != result) {+        return result;+    }     /* 5. Finalization */     finalize(context, &instance); @@ -191,9 +194,9 @@      context.out = (uint8_t *)out;     context.outlen = (uint32_t)hashlen;-    context.pwd = (uint8_t *)pwd;+    context.pwd = CONST_CAST(uint8_t *)pwd;     context.pwdlen = (uint32_t)pwdlen;-    context.salt = (uint8_t *)salt;+    context.salt = CONST_CAST(uint8_t *)salt;     context.saltlen = (uint32_t)saltlen;     context.secret = NULL;     context.secretlen = 0;@@ -273,7 +276,7 @@                        hash, hashlen, NULL, 0, Argon2_d); } -int argon2_compare(const uint8_t *b1, const uint8_t *b2, size_t len) {+static int argon2_compare(const uint8_t *b1, const uint8_t *b2, size_t len) {     size_t i;     uint8_t d = 0U; @@ -391,7 +394,7 @@             !!((sizeof(Argon2_ErrorMessage) / sizeof(Argon2_ErrorMessage[0])) ==                ARGON2_ERROR_CODES_LENGTH)     };-    if (error_code < ARGON2_ERROR_CODES_LENGTH) {+    if (error_code >= 0 && error_code < ARGON2_ERROR_CODES_LENGTH) {         return Argon2_ErrorMessage[(argon2_error_codes)error_code];     }     return "Unknown error code.";
phc-winner-argon2/src/core.c view
@@ -249,25 +249,25 @@     return 0; } -void fill_memory_blocks(argon2_instance_t *instance) {+int fill_memory_blocks(argon2_instance_t *instance) {     uint32_t r, s;     argon2_thread_handle_t *thread = NULL;     argon2_thread_data *thr_data = NULL;      if (instance == NULL || instance->lanes == 0) {-        return;+        return ARGON2_THREAD_FAIL;     }      /* 1. Allocating space for threads */     thread = calloc(instance->lanes, sizeof(argon2_thread_handle_t));     if (thread == NULL) {-        return;+        return ARGON2_MEMORY_ALLOCATION_ERROR;     }      thr_data = calloc(instance->lanes, sizeof(argon2_thread_data));     if (thr_data == NULL) {         free(thread);-        return;+        return ARGON2_MEMORY_ALLOCATION_ERROR;     }      for (r = 0; r < instance->passes; ++r) {@@ -283,10 +283,7 @@                 if (l >= instance->threads) {                     rc = argon2_thread_join(thread[l - instance->threads]);                     if (rc) {-                        printf(-                            "ERROR; return code from pthread_join() #1 is %d\n",-                            rc);-                        exit(-1);+                        return ARGON2_THREAD_FAIL;                     }                 } @@ -302,10 +299,7 @@                 rc = argon2_thread_create(&thread[l], &fill_segment_thr,                                           (void *)&thr_data[l]);                 if (rc) {-                    printf("ERROR; return code from argon2_thread_create() is "-                           "%d\n",-                           rc);-                    exit(-1);+                    return ARGON2_THREAD_FAIL;                 }                  /* fill_segment(instance, position); */@@ -317,9 +311,7 @@                  ++l) {                 rc = argon2_thread_join(thread[l]);                 if (rc) {-                    printf("ERROR; return code from pthread_join() is %d\n",-                           rc);-                    exit(-1);+                    return ARGON2_THREAD_FAIL;                 }             }         }@@ -335,6 +327,7 @@     if (thr_data != NULL) {         free(thr_data);     }+    return ARGON2_OK; }  int validate_inputs(const argon2_context *context) {
phc-winner-argon2/src/ref.c view
@@ -16,7 +16,6 @@ #include <stdlib.h>  #include "argon2.h"-#include "core.h" #include "ref.h"  #include "blake2/blamka-round-ref.h"
src/Crypto/Argon2.hs view
@@ -1,78 +1,185 @@+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE ForeignFunctionInterface #-} {-# LANGUAGE RecordWildCards #-} -module Crypto.Argon2 (hashEncoded, hash, HashOptions(..), Argon2Variant(..), defaultHashOptions) where+{-| +"Crypto.Argon2" provides bindings to the+<https://github.com/P-H-C/phc-winner-argon2 reference implementation> of Argon2,+the password-hashing function that won the+<https://password-hashing.net/ Password Hashing Competition (PHC)>. ++The main entry points to this module are 'hashEncoded', which produces a+crypt-like ASCII output; and 'hash' which produces a 'BS.ByteString' (a stream+of bytes). Argon2 is a configurable hash function, and can be configured by+supplying a particular set of 'HashOptions' - 'defaultHashOptions' should provide+a good starting point. See 'HashOptions' for more documentation on the particular+parameters that can be adjusted.++For access directly to the C interface, see "Crypto.Argon2.FFI".++-}+module Crypto.Argon2+       ( -- * Computing hashes+         hashEncoded, hash,+         -- * Verification+         verify,+         -- * Configuring hashing+         HashOptions(..), Argon2Variant(..), defaultHashOptions,+         -- * Exceptions+         Argon2Exception(..))+       where++import GHC.Generics (Generic) import Control.Exception import Data.Typeable import Foreign import Foreign.C import Numeric.Natural import System.IO.Unsafe (unsafePerformIO)+import qualified Crypto.Argon2.FFI as FFI 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+import qualified Data.Text.Encoding as T -data Argon2Variant = Argon2i | Argon2d+-- | Which variant of Argon2 to use. You should choose the variant that is most+-- applicable to your intention to hash inputs.+data Argon2Variant+  = Argon2i  -- ^ 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.+  | Argon2d -- ^ Argon2d is faster and uses data-depending memory access, which+            -- makes it suitable for cryptocurrencies and applications with no+            -- threats from side-channel timing attacks.+  deriving (Eq,Ord,Read,Show,Bounded,Generic,Typeable,Enum) +-- | Parameters that can be adjusted to change the runtime performance of the+-- hashing. data HashOptions =-  HashOptions {hashIterations :: !Word32-              ,hashMemory :: !Word32-              ,hashParallelism :: !Word32-              ,hashVariant :: !Argon2Variant}+  HashOptions {hashIterations :: !Word32 -- ^ The time cost, which defines the amount of computation realized and therefore the execution time, given in number of iterations.+                                         --+                                         -- 'FFI.ARGON2_MIN_TIME' <= 'hashIterations' <= 'FFI.ARGON2_MAX_TIME'+              ,hashMemory :: !Word32 -- ^ The memory cost, which defines the memory usage, given in kibibytes.+                                     --+                                     -- max 'FFI.ARGON2_MIN_MEMORY' (8 * 'hashParallelism') <= 'hashMemory' <= 'FFI.ARGON2_MAX_MEMORY'+              ,hashParallelism :: !Word32 -- ^ A parallelism degree, which defines the number of parallel threads.+                                          --+                                          -- 'FFI.ARGON2_MIN_LANES' <= 'hashParallelism' <= 'FFI.ARGON2_MAX_LANES' && 'FFI.ARGON_MIN_THREADS' <= 'hashParallelism' <= 'FFI.ARGON2_MAX_THREADS'+              ,hashVariant :: !Argon2Variant -- ^ Which version of Argon2 to use.+              }+  deriving (Eq,Ord,Read,Show,Bounded,Generic,Typeable) +-- | A set of default 'HashOptions', taken from the @argon2@ executable.+--+-- @+-- 'defaultHashOptions' :: 'HashOptions'+-- 'defaultHashOptions' =+--   'HashOptions' {'hashIterations' = 1+--               ,'hashMemory' = 2 ^ 17+--               ,'hashParallelism' = 4+--               ,'hashVariant' = 'Argon2i'}+-- @ defaultHashOptions :: HashOptions defaultHashOptions =-  HashOptions {hashIterations = 3-              ,hashMemory = 2 ^ 12-              ,hashParallelism = 1+  HashOptions {hashIterations = 1+              ,hashMemory = 2 ^ 17+              ,hashParallelism = 4               ,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+-- | Encode a password with a given salt and 'HashOptions' and produce a textual+-- encoding of the result.+hashEncoded :: HashOptions -- ^ Options pertaining to how expensive the hash is to calculate.+            -> BS.ByteString -- ^ The password to hash. Must be less than 4294967295 bytes.+            -> BS.ByteString -- ^ The salt to use when hashing. Must be less than 4294967295 bytes.+            -> 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+  unsafePerformIO+    (hashEncoded' options password salt FFI.argon2i_hash_encoded FFI.argon2d_hash_encoded) -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+-- | Encode a password with a given salt and 'HashOptions' and produce a stream+-- of bytes.+hash :: HashOptions -- ^ Options pertaining to how expensive the hash is to calculate.+     -> BS.ByteString -- ^ The password to hash. Must be less than 4294967295 bytes.+     -> BS.ByteString -- ^ The salt to use when hashing. Must be less than 4294967295 bytes.+     -> 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)+  unsafePerformIO (hash' options password salt FFI.argon2i_hash_raw FFI.argon2d_hash_raw)  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-+-- | Not all 'HashOptions' can necessarily be used to compute hashes. If you+-- supply invalid 'HashOptions' (or hashing otherwise fails) a 'Argon2Exception'+-- will be throw. data Argon2Exception-  = Argon2PasswordLengthOutOfRange Word64 Word64 Word64-  | Argon2SaltLengthOutOfRange Word64 Word64 Word64-  | Argon2MemoryUseOutOfRange Word32 Word32 Word32-  | Argon2IterationCountOutOfRange Word32 Word32 Word32-  | Argon2ParallelismOutOfRange Word32 Word32 Word32-  | Argon2Exception Int32+  = -- | The length of the supplied password is outside the range supported by @libargon2@.+    Argon2PasswordLengthOutOfRange !Word64 -- ^ The erroneous length.+  | -- | The length of the supplied salt is outside the range supported by @libargon2@.+    Argon2SaltLengthOutOfRange !Word64 -- ^ The erroneous length.+  | -- | Either too much or too little memory was requested via 'hashMemory'.+    Argon2MemoryUseOutOfRange !Word32 -- ^ The erroneous 'hashMemory' value.+  | -- | Either too few or too many iterations were requested via 'hashIterations'.+    Argon2IterationCountOutOfRange !Word32 -- ^ The erroneous 'hashIterations' value.+  | -- | Either too much or too little parallelism was requested via 'hasParallelism'.+    Argon2ParallelismOutOfRange !Word32 -- ^ The erroneous 'hashParallelism' value.+  | -- | An unexpected exception was throw. Please <https://github.com/ocharles/argon2/issues report this as a bug>!+    Argon2Exception !Int32 -- ^ The =libargon2= error code.   deriving (Typeable, Show)  instance Exception Argon2Exception +type Argon2Encoded = Word32 -> Word32 -> Word32 -> CString -> Word64 -> CString -> Word64 -> Word64 -> CString -> Word64 -> IO Int32++hashEncoded' :: HashOptions+             -> BS.ByteString+             -> BS.ByteString+             -> Argon2Encoded+             -> Argon2Encoded+             -> IO T.Text+hashEncoded' options@HashOptions{..} password salt argon2i argon2d =+  do let saltLen = fromIntegral (BS.length salt)+         passwordLen = fromIntegral (BS.length password)+         outLen =+           (BS.length salt * 4 + 32 * 4 ++            length ("$argon2x$m=,t=,p=$$" :: String) ++            3 * 3)+     out <- mallocBytes outLen+     res <-+       BS.useAsCString password $+       \password' ->+         BS.useAsCString salt $+         \salt' ->+           argon2 hashIterations+                  hashMemory+                  hashParallelism+                  password'+                  passwordLen+                  salt'+                  saltLen+                  64+                  out+                  (fromIntegral outLen)+     handleSuccessCode res options password salt+     fmap T.decodeUtf8 (BS.packCString out)+  where argon2 = variant argon2i argon2d hashVariant++type Argon2Unencoded = Word32 -> Word32 -> Word32 -> CString -> Word64 -> CString -> Word64 -> CString -> Word64 -> IO Int32+ 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)+      -> Argon2Unencoded+      -> Argon2Unencoded+      -> IO BS.ByteString+hash' options@HashOptions{..} password salt argon2i argon2d =+  do let saltLen = fromIntegral (BS.length salt)          passwordLen = fromIntegral (BS.length password)+         outLen = 512+     out <- mallocBytes outLen      res <-        BS.useAsCString password $        \password' ->@@ -85,32 +192,54 @@                   passwordLen                   salt'                   saltLen-                  64                   out-                  512-     case res of+                  (fromIntegral outLen)+     handleSuccessCode res options password salt+     BS.packCString out+  where argon2 = variant argon2i argon2d hashVariant++handleSuccessCode :: Int32+                  -> HashOptions+                  -> BS.ByteString+                  -> BS.ByteString+                  -> IO ()+handleSuccessCode res HashOptions{..} password salt =+  let saltLen = fromIntegral (BS.length salt)+      passwordLen = fromIntegral (BS.length password)+  in case res of        a-         | a `elem` [FFI.ARGON2_OK] -> postProcess out+         | a `elem` [FFI.ARGON2_OK] -> return ()          | 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)+           throwIO (Argon2SaltLengthOutOfRange saltLen)          | 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)+           throwIO (Argon2PasswordLengthOutOfRange passwordLen)          | a `elem` [FFI.ARGON2_TIME_TOO_SMALL,FFI.ARGON2_TIME_TOO_LARGE] ->-           throwIO (Argon2IterationCountOutOfRange hashIterations-                                                   FFI.ARGON2_MIN_TIME-                                                   FFI.ARGON2_MAX_TIME)+           throwIO (Argon2IterationCountOutOfRange hashIterations)          | 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)+           throwIO (Argon2MemoryUseOutOfRange hashMemory)+         | a `elem`+             [FFI.ARGON2_LANES_TOO_FEW+             ,FFI.ARGON2_LANES_TOO_MANY+             ,FFI.ARGON2_THREADS_TOO_FEW+             ,FFI.ARGON2_THREADS_TOO_MANY] ->+           throwIO (Argon2ParallelismOutOfRange hashParallelism)          | otherwise -> throwIO (Argon2Exception a)-  where argon2 = variant argon2i argon2d hashVariant++-- | Verify that a given password could result in a given hash output.+-- Automatically determines the correct 'HashOptions' based on the+-- encoded hash (as produced by 'hashEncoded').+verify+  :: T.Text -> BS.ByteString -> Bool+verify encoded password =+  unsafePerformIO+    (BS.useAsCString password $+     \pwd ->+       BS.useAsCString (T.encodeUtf8 encoded) $+       \enc ->+         do res <-+              (variant FFI.argon2i_verify FFI.argon2d_verify v) enc+                                                                pwd+                                                                (fromIntegral (BS.length password))+            return (res == FFI.ARGON2_OK))+    where v | T.pack "$argon2i" `T.isPrefixOf` encoded = Argon2i+            | otherwise = Argon2d
src/Crypto/Argon2/FFI.hsc view
@@ -17,6 +17,10 @@  foreign import ccall unsafe "argon2.h argon2d_hash_raw" argon2d_hash_raw :: (#type const uint32_t) -> (#type const uint32_t) -> (#type const uint32_t) -> Ptr a -> (#type const size_t) -> Ptr b -> (#type size_t) -> Ptr c -> (#type const size_t) -> IO (#type int) +foreign import ccall unsafe "argon2.h argon2i_verify" argon2i_verify :: CString -> Ptr a -> (#type const size_t) -> IO (#type int)++foreign import ccall unsafe "argon2.h argon2d_verify" argon2d_verify :: CString -> Ptr a -> (#type const size_t) -> IO (#type int)+ pattern ARGON2_OK = (#const ARGON2_OK) pattern ARGON2_OUTPUT_PTR_NULL = (#const ARGON2_OUTPUT_PTR_NULL) pattern ARGON2_OUTPUT_TOO_SHORT = (#const ARGON2_OUTPUT_TOO_SHORT)