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 +21/−0
- Tests.hs +84/−0
- argon2.cabal +7/−5
- phc-winner-argon2/src/argon2.c +11/−8
- phc-winner-argon2/src/core.c +8/−15
- phc-winner-argon2/src/ref.c +0/−1
- src/Crypto/Argon2.hs +188/−59
- src/Crypto/Argon2/FFI.hsc +4/−0
+ 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)