packages feed

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 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 -[![Build Status](https://secure.travis-ci.org/cdepillabout/password.svg)](http://travis-ci.org/cdepillabout/password)+[![Build Status](https://github.com/cdepillabout/password/workflows/password/badge.svg)](http://github.com/cdepillabout/password) [![Hackage](https://img.shields.io/hackage/v/password.svg)](https://hackage.haskell.org/package/password) [![Stackage LTS](http://stackage.org/package/password/badge/lts)](http://stackage.org/lts/package/password) [![Stackage Nightly](http://stackage.org/package/password/badge/nightly)](http://stackage.org/nightly/package/password) [![BSD3 license](https://img.shields.io/badge/license-BSD3-blue.svg)](./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