password 2.1.1.0 → 3.0.0.0
raw patch · 15 files changed
+248/−252 lines, 15 filesdep +password-typesdep ~cryptonitedep ~memoryPVP ok
version bump matches the API change (PVP)
Dependencies added: password-types
Dependency ranges changed: cryptonite, memory
API changes (from Hackage documentation)
- Data.Password: PasswordCheckFail :: PasswordCheck
- Data.Password: PasswordCheckSuccess :: PasswordCheck
- Data.Password: PasswordHash :: Text -> PasswordHash a
- Data.Password: Salt :: ByteString -> Salt a
- Data.Password: [unPasswordHash] :: PasswordHash a -> Text
- Data.Password: data Password
- Data.Password: data PasswordCheck
- Data.Password: mkPassword :: Text -> Password
- Data.Password: newSalt :: MonadIO m => Int -> m (Salt a)
- Data.Password: newtype PasswordHash a
- Data.Password: newtype Salt a
- Data.Password: unsafeShowPassword :: Password -> Text
+ Data.Password.Argon2: [getSalt] :: Salt a -> ByteString
+ Data.Password.Bcrypt: [getSalt] :: Salt a -> ByteString
+ Data.Password.PBKDF2: [getSalt] :: Salt a -> ByteString
+ Data.Password.Scrypt: [getSalt] :: Salt a -> ByteString
Files
- ChangeLog.md +12/−0
- README.md +2/−3
- password.cabal +34/−4
- src/Data/Password.hs +0/−88
- src/Data/Password/Argon2.hs +38/−25
- src/Data/Password/Bcrypt.hs +24/−15
- src/Data/Password/Internal.hs +43/−74
- src/Data/Password/PBKDF2.hs +21/−12
- src/Data/Password/Scrypt.hs +22/−13
- src/Data/Password/Validate.hs +4/−6
- test/tasty/Argon2.hs +37/−2
- test/tasty/Internal.hs +3/−2
- test/tasty/Scrypt.hs +1/−1
- test/tasty/Spec.hs +6/−6
- test/tasty/Validate.hs +1/−1
ChangeLog.md view
@@ -1,5 +1,17 @@ # Changelog for `password` +## 3.0.0.0++- Split the main datatypes module (`Data.Password`) into a separate package: `password-types`.+ The new package just contains `Password`, `PasswordHash`, `Salt` and their helper functions/instances.+- Adjusted entire `password` package to use the `Data.Password.Types` from this new `password-types`.+ Thanks to [@Vlix](https://github.com/Vlix)+ [#40](https://github.com/cdepillabout/password/pull/40)+- Argon2: fixed the producing and checking of Argon2 hashes.+ The base64 padding is removed when producing hashes and when+ checking hashes it will accept hashes with or without padding.+ [#45](https://github.com/cdepillabout/password/pull/45)+ ## 2.1.1.0 - Fixed `homepage` links in the `.cabal` files.
README.md view
@@ -1,13 +1,12 @@ # password -[](http://travis-ci.org/cdepillabout/password)+[](http://github.com/cdepillabout/password) [](https://hackage.haskell.org/package/password) [](http://stackage.org/lts/package/password) [](http://stackage.org/nightly/package/password) [](./LICENSE) -This library provides datatypes and functions for working with passwords and-password hashes in Haskell.+This library provides functions for working with passwords and password hashes in Haskell. Currently supports the following algorithms:
password.cabal view
@@ -1,10 +1,39 @@ cabal-version: 1.12 name: password-version: 2.1.1.0+version: 3.0.0.0 category: Data synopsis: Hashing and checking of passwords-description: A library providing functionality for working with plain-text and hashed passwords with different types of algorithms.+description:+ A library providing functionality for working with plain-text and hashed passwords+ with different types of algorithms.+ .+ == API+ .+ Every supported hashing algorithm has its own module (e.g. "Data.Password.Bcrypt")+ which exports its own @hashPassword@ and @checkPassword@ functions, as well as all the+ types and functions in this module. If you are not sure about the specifics of an+ algorithm you want to use, you can rest assured that by using the @hashPassword@ function+ of the respective algorithm you are not making any big mistakes, security-wise.+ .+ Of course, if you know what you're doing and you want more fine-grained control+ over the hashing function, you can adjust it using the @hashPasswordWithParams@+ function of the respective algorithm.+ .+ == Algorithms+ .+ Generally, the most "secure" algorithm is believed to be @Argon2@, then @scrypt@,+ then @bcrypt@, and lastly @PBKDF2@.+ @bcrypt@ and @PBKDF2@ are the most established algorithms, so they have been tried and+ tested, though they both lack a memory cost, and therefore have a greater vulnerability+ to specialized hardware attacks.+ .+ When choosing an algorithm, and you have no idea which to pick, just go for @bcrypt@ if+ your password does not need the highest security possible.+ It's still a fine way for hashing passwords, and the cost is easily adjustable if needed.+ If your needs do require stronger protection, you should find someone who can advise you+ on this topic. (And if you're already knowledgeable enough, you know what to do)+ homepage: https://github.com/cdepillabout/password/tree/master/password#readme bug-reports: https://github.com/cdepillabout/password/issues author: Dennis Gosnell, Felix Paulusma@@ -31,7 +60,6 @@ hs-source-dirs: src exposed-modules:- Data.Password Data.Password.Argon2 Data.Password.Bcrypt Data.Password.PBKDF2@@ -44,8 +72,9 @@ base >= 4.9 && < 5 , base64 >= 0.3 && < 0.5 , bytestring >= 0.10.8.1 && < 0.11- , cryptonite >= 0.15.1 && < 0.28+ , cryptonite >= 0.15.1 && < 0.29 , memory >= 0.14 && < 0.16+ , password-types < 2 , template-haskell , text >= 1.2.2 && < 1.3 ghc-options:@@ -94,6 +123,7 @@ build-depends: base >=4.9 && <5 , password+ , password-types , bytestring , cryptonite , memory
− src/Data/Password.hs
@@ -1,88 +0,0 @@-{-|-Module : Data.Password-Copyright : (c) Dennis Gosnell, 2019; Felix Paulusma, 2020-License : BSD-style (see LICENSE file)-Maintainer : cdep.illabout@gmail.com-Stability : experimental-Portability : POSIX--This library provides an easy way for interacting with passwords from Haskell.-It provides the types 'Password' and 'PasswordHash', which correspond to plain-text and-hashed passwords.--== API--Every supported hashing algorithm has its own module (e.g. "Data.Password.Bcrypt")-which exports its own @hashPassword@ and @checkPassword@ functions, as well as all the-types and functions in this module. If you are not sure about the specifics of an-algorithm you want to use, you can rest assured that by using the @hashPassword@ function-of the respective algorithm you are not making any big mistakes, security-wise.--Of course, if you know what you're doing and you want more fine-grained control-over the hashing function, you can adjust it using the @hashPasswordWithParams@-function of the respective algorithm.--== Algorithms--Generally, the most "secure" algorithm is believed to be @'Data.Password.Argon2.Argon2'@,-then @'Data.Password.Scrypt.Scrypt'@, then @'Data.Password.Bcrypt.Bcrypt'@, and lastly-@'Data.Password.PBKDF2.PBKDF2'@. @'Data.Password.Bcrypt.Bcrypt'@-and @'Data.Password.PBKDF2.PBKDF2'@ are the most established algorithms, so they have-been tried and tested, though they both lack a memory cost, and therefore have a-greater vulnerability to specialized hardware attacks.--When choosing an algorithm, and you have no idea which to pick, just go for-@'Data.Password.Bcrypt.Bcrypt'@ if your password does not need the highest security possible.-It's still a fine way for hashing passwords, and the cost is easily adjustable if needed.-If your needs do require stronger protection, you should find someone who can advise you-on this topic. (And if you're already knowledgeable enough, you know what to do)--== Special instances--The real benefit of this module is that there is an accompanying-<http://hackage.haskell.org/package/password-instances password-instances>-package that provides canonical typeclass instances for-'Password' and 'PasswordHash' for many common typeclasses, like-<http://hackage.haskell.org/package/aeson/docs/Data-Aeson.html#t:FromJSON FromJSON> from-<http://hackage.haskell.org/package/aeson aeson>,-<http://hackage.haskell.org/package/persistent/docs/Database-Persist-Class.html#t:PersistField PersistField>-from-<http://hackage.haskell.org/package/persistent persistent>, etc.--See the <http://hackage.haskell.org/package/password-instances password-instances> package for more information.--}--module Data.Password (- -- * Plain-text Password- Password- , mkPassword- -- * Password Hashing- , PasswordHash(..)- , PasswordCheck(..)- , Salt(..)- , newSalt- -- * Unsafe debugging function to show a Password- , unsafeShowPassword- ) where--import Data.Password.Internal---- TODO: Create code for checking that plain-text passwords conform to some sort of--- password policy.---- data PasswordPolicy = PasswordPolicy--- { passPolicyLength :: Int--- , passPolicyCharReqs :: [PolicyCharReq]--- , passPolicyCharSet :: PolicyCharSet--- }---- -- | Character requirements for a password policy.--- data PolicyCharReq--- = PolicyCharReqUpper Int--- -- ^ A password requires at least 'Int' upper-case characters.--- | PolicyCharReqLower Int--- -- ^ A password requires at least 'Int' lower-case characters.--- | PolicyCharReqSpecial Int--- -- ^ A password requires at least 'Int' special characters---- data PolicyCharSet = PolicyCharSetAscii
src/Data/Password/Argon2.hs view
@@ -70,29 +70,36 @@ ) where import Control.Monad (guard)-import Control.Monad.IO.Class (MonadIO(liftIO))+import Control.Monad.IO.Class (MonadIO (liftIO)) import Crypto.Error (throwCryptoError)-import Crypto.KDF.Argon2 as Argon2+import Crypto.KDF.Argon2 as Argon2 (Options (..), Variant (..), Version (..), hash) import Data.ByteArray (Bytes, constEq, convert)-import Data.ByteString (ByteString)+import Data.ByteString as B (ByteString, length) import Data.ByteString.Base64 (encodeBase64)-import qualified Data.ByteString.Char8 as C8 (length) import Data.Maybe (fromMaybe)-#if! MIN_VERSION_base(4,13,0)+#if !MIN_VERSION_base(4,13,0) import Data.Semigroup ((<>)) #endif import Data.Text (Text) import qualified Data.Text as T (intercalate, length, split, splitAt) import Data.Word (Word32) -import Data.Password (- PasswordCheck(..)- , PasswordHash(..)- , Salt(..)- , mkPassword- , unsafeShowPassword- )-import Data.Password.Internal (Password(..), from64, readT, showT, toBytes)+import Data.Password.Internal (+ PasswordCheck (..),+ from64,+ readT,+ showT,+ toBytes,+ unsafePad64,+ unsafeRemovePad64,+ )+import Data.Password.Types (+ Password,+ PasswordHash (..),+ Salt (..),+ mkPassword,+ unsafeShowPassword,+ ) import qualified Data.Password.Internal (newSalt) @@ -107,13 +114,13 @@ -- -- Import needed libraries. ----- >>> import Data.Password+-- >>> import Data.Password.Types -- >>> import Data.ByteString (pack) -- >>> import Test.QuickCheck (Arbitrary(arbitrary), Blind(Blind), vector) -- >>> import Test.QuickCheck.Instances.Text () -- -- >>> instance Arbitrary (Salt a) where arbitrary = Salt . pack <$> vector 16--- >>> instance Arbitrary Password where arbitrary = fmap Password arbitrary+-- >>> instance Arbitrary Password where arbitrary = fmap mkPassword arbitrary -- >>> let testParams = defaultParams {argon2TimeCost = 1} -- >>> let salt = Salt "abcdefghijklmnop" @@ -183,7 +190,7 @@ -- -- >>> let salt = Salt "abcdefghijklmnop" -- >>> hashPasswordWithSalt defaultParams salt (mkPassword "foobar")--- PasswordHash {unPasswordHash = "$argon2id$v=19$m=65536,t=2,p=1$YWJjZGVmZ2hpamtsbW5vcA==$BztdyfEefG5V18ZNlztPrfZaU5duVFKZiI6dJeWht0o="}+-- PasswordHash {unPasswordHash = "$argon2id$v=19$m=65536,t=2,p=1$YWJjZGVmZ2hpamtsbW5vcA$BztdyfEefG5V18ZNlztPrfZaU5duVFKZiI6dJeWht0o"} -- -- (Note that we use an explicit 'Salt' in the example above. This is so that the -- example is reproducible, but in general you should use 'hashPassword'. 'hashPassword'@@ -194,10 +201,12 @@ [ variantToLetter argon2Variant , "v=" <> versionToNum argon2Version , parameters- , encodeBase64 salt- , encodeBase64 key+ , encodeWithoutPadding salt+ , encodeWithoutPadding key ] where+ encodeWithoutPadding bs =+ unsafeRemovePad64 (B.length bs) $ encodeBase64 bs parameters = T.intercalate "," [ "m=" <> showT argon2MemoryCost , "t=" <> showT argon2TimeCost@@ -207,11 +216,15 @@ -- | Only for internal use hashPasswordWithSalt' :: Argon2Params -> Salt Argon2 -> Password -> ByteString-hashPasswordWithSalt' Argon2Params{..} (Salt salt) (Password pass) =+hashPasswordWithSalt' Argon2Params{..} (Salt salt) pass = convert (argon2Hash :: Bytes) where argon2Hash = throwCryptoError $- Argon2.hash options (toBytes pass) (convert salt :: Bytes) $ fromIntegral argon2OutputLength+ Argon2.hash+ options+ (toBytes $ unsafeShowPassword pass)+ (convert salt :: Bytes)+ $ fromIntegral argon2OutputLength options = Argon2.Options { iterations = argon2TimeCost, memory = argon2MemoryCost,@@ -264,7 +277,7 @@ checkPassword pass (PasswordHash passHash) = fromMaybe PasswordCheckFail $ do let paramList = T.split (== '$') passHash- guard $ length paramList == 6+ guard $ Prelude.length paramList == 6 let [ _, variantT, versionT,@@ -274,9 +287,9 @@ argon2Variant <- parseVariant variantT argon2Version <- parseVersion versionT (argon2MemoryCost, argon2TimeCost, argon2Parallelism) <- parseParameters parametersT- salt <- from64 salt64- hashedKey <- from64 hashedKey64- let argon2OutputLength = fromIntegral $ C8.length hashedKey -- only here because of warnings+ salt <- from64 $ unsafePad64 salt64+ hashedKey <- from64 $ unsafePad64 hashedKey64+ let argon2OutputLength = fromIntegral $ B.length hashedKey -- only here because of warnings producedKey = hashPasswordWithSalt' Argon2Params{..} (Salt salt) pass guard $ hashedKey `constEq` producedKey return PasswordCheckSuccess@@ -286,7 +299,7 @@ parseVersion = splitMaybe "v=" numToVersion parseParameters params = do let ps = T.split (== ',') params- guard $ length ps == 3+ guard $ Prelude.length ps == 3 go ps (Nothing, Nothing, Nothing) where go [] (Just m, Just t, Just p) = Just (m, t, p)
src/Data/Password/Bcrypt.hs view
@@ -60,17 +60,21 @@ ) where import Control.Monad.IO.Class (MonadIO(liftIO))-import Crypto.KDF.BCrypt as Bcrypt hiding (hashPassword)+import Crypto.KDF.BCrypt as Bcrypt (bcrypt, validatePassword) import Data.ByteArray (Bytes, convert) -import Data.Password (- PasswordCheck(..)- , PasswordHash(..)- , Salt(..)- , mkPassword- , unsafeShowPassword- )-import Data.Password.Internal (Password(..), fromBytes, toBytes)+import Data.Password.Types (+ Password+ , PasswordHash(..)+ , mkPassword+ , unsafeShowPassword+ , Salt(..)+ )+import Data.Password.Internal (+ PasswordCheck(..)+ , fromBytes+ , toBytes+ ) import qualified Data.Password.Internal (newSalt) @@ -85,13 +89,13 @@ -- -- Import needed libraries. ----- >>> import Data.Password+-- >>> import Data.Password.Types -- >>> import Data.ByteString (pack) -- >>> import Test.QuickCheck (Arbitrary(arbitrary), Blind(Blind), vector) -- >>> import Test.QuickCheck.Instances.Text () -- -- >>> instance Arbitrary (Salt a) where arbitrary = Salt . pack <$> vector 16--- >>> instance Arbitrary Password where arbitrary = fmap Password arbitrary+-- >>> instance Arbitrary Password where arbitrary = fmap mkPassword arbitrary -- >>> let salt = Salt "abcdefghijklmnop" -- -- >>> instance Arbitrary (PasswordHash Bcrypt) where arbitrary = hashPasswordWithSalt 8 <$> arbitrary <*> arbitrary@@ -126,8 +130,11 @@ -> Salt Bcrypt -- ^ The salt. MUST be 16 bytes in length or an error will be raised. -> Password -- ^ The password to be hashed. -> PasswordHash Bcrypt -- ^ The bcrypt hash in standard format.-hashPasswordWithSalt cost (Salt salt) (Password pass) =- let hash = Bcrypt.bcrypt cost (convert salt :: Bytes) (toBytes pass)+hashPasswordWithSalt cost (Salt salt) pass =+ let hash = Bcrypt.bcrypt+ cost+ (convert salt :: Bytes)+ (toBytes $ unsafeShowPassword pass) in PasswordHash $ fromBytes hash -- | Hash a password using the /bcrypt/ algorithm with the given cost.@@ -169,8 +176,10 @@ -- -- prop> \(Blind badpass) -> let correctPasswordHash = hashPasswordWithSalt 8 salt "foobar" in checkPassword badpass correctPasswordHash == PasswordCheckFail checkPassword :: Password -> PasswordHash Bcrypt -> PasswordCheck-checkPassword (Password pass) (PasswordHash passHash) =- if Bcrypt.validatePassword (toBytes pass) (toBytes passHash)+checkPassword pass (PasswordHash passHash) =+ if Bcrypt.validatePassword+ (toBytes $ unsafeShowPassword pass)+ (toBytes passHash) then PasswordCheckSuccess else PasswordCheckFail
src/Data/Password/Internal.hs view
@@ -1,5 +1,5 @@-{-# LANGUAGE ExplicitForAll #-}-{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-} {-| Module : Data.Password.Internal Copyright : (c) Dennis Gosnell, 2019; Felix Paulusma, 2020@@ -10,72 +10,40 @@ -} module Data.Password.Internal (- -- * Global types- Password(..)- , mkPassword- , PasswordHash(..)- , PasswordCheck(..)- , Salt(..)+ -- * Global types+ PasswordCheck(..) , newSalt- -- * Unsafe function- , unsafeShowPassword -- * Utility , toBytes , fromBytes , from64+ , unsafePad64+ , unsafeRemovePad64 , readT , showT ) where import Control.Monad.IO.Class (MonadIO(liftIO)) import Crypto.Random (getRandomBytes)-import Data.ByteArray (Bytes, constEq, convert)+import Data.ByteArray (Bytes, convert) import Data.ByteString (ByteString)-import Data.Function (on) import Data.ByteString.Base64 (decodeBase64)-import Data.String (IsString(..))-import Data.Text as T (Text, pack, unpack)+#if !MIN_VERSION_base(4,13,0)+import Data.Semigroup ((<>))+#endif+import Data.Text as T (+ Text,+ dropEnd,+ length,+ pack,+ replicate,+ unpack,+ )+import Data.Password.Types (Salt(..)) import Data.Text.Encoding (decodeUtf8, encodeUtf8) import Text.Read (readMaybe) --- | A plain-text password.------ This represents a plain-text password that has /NOT/ been hashed.------ You should be careful with 'Password'. Make sure not to write it to logs or--- store it in a database.------ You can construct a 'Password' by using the 'mkPassword' function or as literal--- strings together with the OverloadedStrings pragma (or manually, by using--- 'fromString' on a 'String'). Alternatively, you could also use some of the--- instances in the <http://hackage.haskell.org/package/password-instances password-instances>--- library.-newtype Password = Password Text- deriving (IsString)---- | CAREFUL: 'Show'-ing a 'Password' will always print @"**PASSWORD**"@------ >>> show ("hello" :: Password)--- "**PASSWORD**"------ @since 1.0.0.0-instance Show Password where- show _ = "**PASSWORD**"---- | Construct a 'Password'------ @since 1.0.0.0-mkPassword :: Text -> Password-mkPassword = Password-{-# INLINE mkPassword #-}---- | A salt used by a hashing algorithm.------ @since 2.0.0.0-newtype Salt a = Salt ByteString- deriving (Eq, Show)- -- | Generate a random x-byte-long salt. -- -- @since 2.0.0.0@@ -83,27 +51,6 @@ newSalt i = liftIO $ Salt <$> getRandomBytes i {-# INLINE newSalt #-} --- | This is an unsafe function that shows a password in plain-text.------ >>> unsafeShowPassword ("foobar" :: Password)--- "foobar"------ You should generally not use this function.-unsafeShowPassword :: Password -> Text-unsafeShowPassword (Password pass) = pass-{-# INLINE unsafeShowPassword #-}---- | A hashed password.------ This represents a password that has been put through a hashing function.--- The hashed password can be stored in a database.-newtype PasswordHash a = PasswordHash- { unPasswordHash :: Text- } deriving (Ord, Read, Show)--instance Eq (PasswordHash a) where- (==) = constEq `on` encodeUtf8 . unPasswordHash- -- | The result of checking a password against a hashed version. This is -- returned by the @checkPassword@ functions. data PasswordCheck@@ -133,11 +80,33 @@ {-# INLINE from64 #-} -- | Same as 'read' but works on 'Text'-readT :: forall a. Read a => Text -> Maybe a+readT :: Read a => Text -> Maybe a readT = readMaybe . T.unpack {-# INLINE readT #-} -- | Same as 'show' but works on 'Text'-showT :: forall a. Show a => a -> Text+showT :: Show a => a -> Text showT = T.pack . show {-# INLINE showT #-}++-- | (UNSAFE) Pad a base64 text to "length `rem` 4 == 0" with "="+unsafePad64 :: Text -> Text+unsafePad64 t+ | remains == 0 = t+ | otherwise = t <> pad+ where+ remains = T.length t `rem` 4+ pad = T.replicate (4 - remains) "="++-- | (UNSAFE) Removes the "=" padding from a base64 text+-- given the length of the original bytestring.+unsafeRemovePad64 :: Int -> Text -> Text+unsafeRemovePad64 bsLen = T.dropEnd drops+ where+ drops = case bsLen `rem` 3 of+ -- 1 extra byte results in 2 characters (4 - 2 = 2)+ 1 -> 2+ -- 2 extra bytes results in 3 characters (4 - 3 = 1)+ 2 -> 1+ -- This will just be 0+ other -> other
src/Data/Password/PBKDF2.hs view
@@ -80,14 +80,19 @@ import qualified Data.Text as T (intercalate, pack, split, stripPrefix) import Data.Word (Word32) -import Data.Password (- PasswordCheck(..)- , PasswordHash(..)- , Salt(..)- , mkPassword- , unsafeShowPassword- )-import Data.Password.Internal (Password(..), from64, readT, toBytes)+import Data.Password.Types (+ Password+ , PasswordHash(..)+ , mkPassword+ , unsafeShowPassword+ , Salt(..)+ )+import Data.Password.Internal (+ PasswordCheck(..)+ , from64+ , readT+ , toBytes+ ) import qualified Data.Password.Internal (newSalt) @@ -102,13 +107,13 @@ -- -- Import needed libraries. ----- >>> import Data.Password+-- >>> import Data.Password.Types -- >>> import Data.ByteString (pack) -- >>> import Test.QuickCheck (Arbitrary(arbitrary), Blind(Blind), vector) -- >>> import Test.QuickCheck.Instances.Text () -- -- >>> instance Arbitrary (Salt a) where arbitrary = Salt . pack <$> vector 16--- >>> instance Arbitrary Password where arbitrary = fmap Password arbitrary+-- >>> instance Arbitrary Password where arbitrary = fmap mkPassword arbitrary -- >>> let testParams = defaultParams{ pbkdf2Iterations = 5000 } -- >>> let salt = Salt "abcdefghijklmnop" @@ -185,10 +190,14 @@ -- | Only for internal use hashPasswordWithSalt' :: PBKDF2Params -> Salt PBKDF2 -> Password -> ByteString-hashPasswordWithSalt' PBKDF2Params{..} (Salt salt) (Password pass) =+hashPasswordWithSalt' PBKDF2Params{..} (Salt salt) pass = convert (pbkdf2Hash :: Bytes) where- pbkdf2Hash = algToFunc pbkdf2Algorithm params (toBytes pass) (convert salt :: Bytes)+ pbkdf2Hash = algToFunc+ pbkdf2Algorithm+ params+ (toBytes $ unsafeShowPassword pass)+ (convert salt :: Bytes) params = PBKDF2.Parameters { PBKDF2.iterCounts = fromIntegral pbkdf2Iterations, PBKDF2.outputLength = fromIntegral $ maxOutputLength pbkdf2Algorithm pbkdf2OutputLength
src/Data/Password/Scrypt.hs view
@@ -63,7 +63,7 @@ import Control.Monad (guard) import Control.Monad.IO.Class (MonadIO(liftIO))-import Crypto.KDF.Scrypt as Scrypt+import Crypto.KDF.Scrypt as Scrypt (Parameters(..), generate) import Data.ByteArray (Bytes, constEq, convert) import Data.ByteString (ByteString) import Data.ByteString.Base64 (encodeBase64)@@ -72,14 +72,20 @@ import qualified Data.Text as T (intercalate, split) import Data.Word (Word32) -import Data.Password (- PasswordCheck(..)- , PasswordHash(..)- , Salt(..)- , mkPassword- , unsafeShowPassword- )-import Data.Password.Internal (Password(..), from64, readT, showT, toBytes)+import Data.Password.Types (+ Password+ , PasswordHash(..)+ , mkPassword+ , unsafeShowPassword+ , Salt(..)+ )+import Data.Password.Internal (+ PasswordCheck(..)+ , from64+ , readT+ , showT+ , toBytes+ ) import qualified Data.Password.Internal (newSalt) -- | Phantom type for __scrypt__@@ -93,13 +99,13 @@ -- -- Import needed libraries. ----- >>> import Data.Password+-- >>> import Data.Password.Types -- >>> import Data.ByteString (pack) -- >>> import Test.QuickCheck (Arbitrary(arbitrary), Blind(Blind), vector) -- >>> import Test.QuickCheck.Instances.Text () -- -- >>> instance Arbitrary (Salt a) where arbitrary = Salt . pack <$> vector 32--- >>> instance Arbitrary Password where arbitrary = fmap Password arbitrary+-- >>> instance Arbitrary Password where arbitrary = fmap mkPassword arbitrary -- >>> let salt = Salt "abcdefghijklmnopqrstuvwxyz012345" -- >>> let testParams = defaultParams {scryptRounds = 10} @@ -181,10 +187,13 @@ -- | Only for internal use hashPasswordWithSalt' :: ScryptParams -> Salt Scrypt -> Password -> ByteString-hashPasswordWithSalt' ScryptParams{..} (Salt salt) (Password pass) =+hashPasswordWithSalt' ScryptParams{..} (Salt salt) pass = convert (scryptHash :: Bytes) where- scryptHash = Scrypt.generate params (toBytes pass) (convert salt :: Bytes)+ scryptHash = Scrypt.generate+ params+ (toBytes $ unsafeShowPassword pass)+ (convert salt :: Bytes) params = Scrypt.Parameters { n = 2 ^ scryptRounds, r = fromIntegral scryptBlockSize,
src/Data/Password/Validate.hs view
@@ -204,22 +204,19 @@ import Data.Function (on) import Data.List (foldl') -#if !MIN_VERSION_base(4,13,0)-import Data.Semigroup ((<>))-#endif import Data.Text (Text) import qualified Data.Text as T import Language.Haskell.TH (Exp, Q, appE) import Language.Haskell.TH.Syntax (Lift (..)) -import Data.Password.Internal (Password (..))+import Data.Password.Types (Password, unsafeShowPassword) -- $setup -- >>> :set -XOverloadedStrings -- -- Import needed libraries. ----- >>> import Data.Password+-- >>> import Data.Password.Types -- | Set of policies used to validate a 'Password'. --@@ -471,12 +468,13 @@ -- -- @since 2.1.0.0 validatePassword :: ValidPasswordPolicy -> Password -> ValidationResult-validatePassword (VPP PasswordPolicy{..}) (Password password) =+validatePassword (VPP PasswordPolicy{..}) pass = case validationFailures of [] -> ValidPassword _:_ -> InvalidPassword validationFailures where+ password = unsafeShowPassword pass validationFailures = mconcat [ isTooShort , isTooLong
test/tasty/Argon2.hs view
@@ -1,7 +1,8 @@+{-# LANGUAGE OverloadedStrings #-} module Argon2 where import Test.Tasty-import Test.QuickCheck.Instances.Text ()+import Test.Tasty.HUnit (assertBool, assertEqual, testCase) import Data.Password.Argon2 @@ -10,13 +11,15 @@ testArgon2 :: TestTree testArgon2 = testGroup "Argon2"- [ testCorrectPassword "Argon2 (hashPassword)" hashFast checkPassword+ [ referenceTest+ , testCorrectPassword "Argon2 (hashPassword)" hashFast checkPassword , testIncorrectPassword "Argon2 (hashPassword) fail" hashFast checkPassword , testWithSalt "Argon2 (hashPasswordWithSalt)" (hashPasswordWithSalt fastParams) checkPassword , testWithParams "Argon2 (Argon2i)" $ fastParams{ argon2Variant = Argon2i } , testWithParams "Argon2 (Argon2d)" $ fastParams{ argon2Variant = Argon2d }+ , paddingTests ] where testWithParams s params =@@ -27,3 +30,35 @@ argon2MemoryCost = 2 ^ (8 :: Int), argon2TimeCost = 1 }++paddingTests :: TestTree+paddingTests = testGroup "Padding"+ [ testCase "with padding" $+ assertBool "Bad hash" $ checkPassword pass hashWithPadding == PasswordCheckSuccess+ , testCase "without padding" $+ assertBool "Bad hash" $ checkPassword pass hashWithoutPadding == PasswordCheckSuccess+ ]++pass :: Password+pass = "foobar"++-- Hashed password ("foobar") with salt ("abcdefghijklmnop")+hashWithPadding, hashWithoutPadding :: PasswordHash Argon2+hashWithPadding = PasswordHash "$argon2id$v=19$m=65536,t=2,p=1$YWJjZGVmZ2hpamtsbW5vcA==$BztdyfEefG5V18ZNlztPrfZaU5duVFKZiI6dJeWht0o="+hashWithoutPadding = PasswordHash "$argon2id$v=19$m=65536,t=2,p=1$YWJjZGVmZ2hpamtsbW5vcA$BztdyfEefG5V18ZNlztPrfZaU5duVFKZiI6dJeWht0o"++-- Reference check using the Command-line Utility output example+-- from: https://github.com/P-H-C/phc-winner-argon2+referenceTest :: TestTree+referenceTest = testCase "PHC Argon2 reference" $+ assertEqual "output hash is wrong" expected $+ hashPasswordWithSalt params salt pwd+ where+ expected = PasswordHash "$argon2i$v=19$m=65536,t=2,p=4$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG"+ salt = Salt "somesalt"+ pwd = mkPassword "password"+ params = defaultParams {+ argon2Variant = Argon2i,+ argon2Parallelism = 4,+ argon2OutputLength = 24+ }
test/tasty/Internal.hs view
@@ -3,11 +3,12 @@ module Internal where import Data.ByteArray (pack)-import Test.Tasty+import Test.Tasty (TestTree) import Test.Tasty.QuickCheck import Test.QuickCheck.Instances.Text () -import Data.Password+import Data.Password.Types (mkPassword, Password, PasswordHash)+import Data.Password.Bcrypt (PasswordCheck(..), Salt(..)) testCorrectPassword :: String
test/tasty/Scrypt.hs view
@@ -8,7 +8,7 @@ import Test.QuickCheck.Instances.Text () import qualified Crypto.Scrypt as Scrypt-import Data.Password+import Data.Password.Types import Data.Password.Scrypt import Internal
test/tasty/Spec.hs view
@@ -2,13 +2,13 @@ import Test.Tasty.QuickCheck import Test.Tasty.Runners (NumThreads(..)) -import Data.Password+import Data.Password.Types -import Argon2-import Bcrypt-import PBKDF2-import Scrypt-import Validate+import Argon2 (testArgon2)+import Bcrypt (testBcrypt)+import PBKDF2 (testPBKDF2)+import Scrypt (testScrypt)+import Validate (testValidate) main :: IO () main = defaultMain $ localOption (NumThreads 1) $
test/tasty/Validate.hs view
@@ -22,7 +22,7 @@ import Data.Semigroup ((<>)) #endif -import Data.Password (Password, mkPassword)+import Data.Password.Types (Password, mkPassword) import Data.Password.Validate hiding (ValidationResult(..)) import qualified Data.Password.Validate as V