packages feed

password 1.0.0.0 → 2.0.0.0

raw patch · 19 files changed

+1617/−292 lines, 19 filesdep +base-compatdep +base64dep +cryptonitedep ~basedep ~bytestringdep ~textbuild-type:Customsetup-changednew-uploaderPVP ok

version bump matches the API change (PVP)

Dependencies added: base-compat, base64, cryptonite, memory, template-haskell

Dependency ranges changed: base, bytestring, text

API changes (from Hackage documentation)

- Data.Password: PassCheckFail :: PassCheck
- Data.Password: PassCheckSuccess :: PassCheck
- Data.Password: PassHash :: Text -> PassHash
- Data.Password: [getSalt] :: Salt -> ByteString
- Data.Password: [unPassHash] :: PassHash -> Text
- Data.Password: checkPass :: Pass -> PassHash -> PassCheck
- Data.Password: data Pass
- Data.Password: data PassCheck
- Data.Password: hashPass :: MonadIO m => Pass -> m PassHash
- Data.Password: hashPassWithSalt :: Salt -> Pass -> PassHash
- Data.Password: instance Data.String.IsString Data.Password.Pass
- Data.Password: instance GHC.Classes.Eq Data.Password.PassCheck
- Data.Password: instance GHC.Classes.Eq Data.Password.PassHash
- Data.Password: instance GHC.Classes.Ord Data.Password.PassHash
- Data.Password: instance GHC.Read.Read Data.Password.PassCheck
- Data.Password: instance GHC.Read.Read Data.Password.PassHash
- Data.Password: instance GHC.Show.Show Data.Password.Pass
- Data.Password: instance GHC.Show.Show Data.Password.PassCheck
- Data.Password: instance GHC.Show.Show Data.Password.PassHash
- Data.Password: mkPass :: Text -> Pass
- Data.Password: newtype PassHash
- Data.Password: unsafeShowPasswordText :: Pass -> Text
+ Data.Password: PasswordCheckFail :: PasswordCheck
+ Data.Password: PasswordCheckSuccess :: PasswordCheck
+ Data.Password: PasswordHash :: Text -> PasswordHash a
+ Data.Password: [unPasswordHash] :: PasswordHash a -> Text
+ Data.Password: data Password
+ Data.Password: data PasswordCheck
+ Data.Password: mkPassword :: Text -> Password
+ Data.Password: newtype PasswordHash a
+ Data.Password.Argon2: Argon2Params :: Word32 -> Variant -> Version -> Word32 -> Word32 -> Word32 -> Word32 -> Argon2Params
+ Data.Password.Argon2: Argon2d :: Variant
+ Data.Password.Argon2: Argon2i :: Variant
+ Data.Password.Argon2: Argon2id :: Variant
+ Data.Password.Argon2: PasswordCheckFail :: PasswordCheck
+ Data.Password.Argon2: PasswordCheckSuccess :: PasswordCheck
+ Data.Password.Argon2: PasswordHash :: Text -> PasswordHash a
+ Data.Password.Argon2: Salt :: ByteString -> Salt a
+ Data.Password.Argon2: Version10 :: Version
+ Data.Password.Argon2: Version13 :: Version
+ Data.Password.Argon2: [argon2MemoryCost] :: Argon2Params -> Word32
+ Data.Password.Argon2: [argon2OutputLength] :: Argon2Params -> Word32
+ Data.Password.Argon2: [argon2Parallelism] :: Argon2Params -> Word32
+ Data.Password.Argon2: [argon2Salt] :: Argon2Params -> Word32
+ Data.Password.Argon2: [argon2TimeCost] :: Argon2Params -> Word32
+ Data.Password.Argon2: [argon2Variant] :: Argon2Params -> Variant
+ Data.Password.Argon2: [argon2Version] :: Argon2Params -> Version
+ Data.Password.Argon2: [unPasswordHash] :: PasswordHash a -> Text
+ Data.Password.Argon2: checkPassword :: Password -> PasswordHash Argon2 -> PasswordCheck
+ Data.Password.Argon2: data Argon2
+ Data.Password.Argon2: data Argon2Params
+ Data.Password.Argon2: data Password
+ Data.Password.Argon2: data PasswordCheck
+ Data.Password.Argon2: data Variant
+ Data.Password.Argon2: data Version
+ Data.Password.Argon2: defaultParams :: Argon2Params
+ Data.Password.Argon2: hashPassword :: MonadIO m => Password -> m (PasswordHash Argon2)
+ Data.Password.Argon2: hashPasswordWithParams :: MonadIO m => Argon2Params -> Password -> m (PasswordHash Argon2)
+ Data.Password.Argon2: hashPasswordWithSalt :: Argon2Params -> Salt Argon2 -> Password -> PasswordHash Argon2
+ Data.Password.Argon2: instance GHC.Classes.Eq Data.Password.Argon2.Argon2Params
+ Data.Password.Argon2: instance GHC.Show.Show Data.Password.Argon2.Argon2Params
+ Data.Password.Argon2: mkPassword :: Text -> Password
+ Data.Password.Argon2: newSalt :: MonadIO m => m (Salt Argon2)
+ Data.Password.Argon2: newtype PasswordHash a
+ Data.Password.Argon2: newtype Salt a
+ Data.Password.Argon2: unsafeShowPassword :: Password -> Text
+ Data.Password.Bcrypt: PasswordCheckFail :: PasswordCheck
+ Data.Password.Bcrypt: PasswordCheckSuccess :: PasswordCheck
+ Data.Password.Bcrypt: PasswordHash :: Text -> PasswordHash a
+ Data.Password.Bcrypt: Salt :: ByteString -> Salt a
+ Data.Password.Bcrypt: [unPasswordHash] :: PasswordHash a -> Text
+ Data.Password.Bcrypt: checkPassword :: Password -> PasswordHash Bcrypt -> PasswordCheck
+ Data.Password.Bcrypt: data Bcrypt
+ Data.Password.Bcrypt: data Password
+ Data.Password.Bcrypt: data PasswordCheck
+ Data.Password.Bcrypt: hashPassword :: MonadIO m => Password -> m (PasswordHash Bcrypt)
+ Data.Password.Bcrypt: hashPasswordWithParams :: MonadIO m => Int -> Password -> m (PasswordHash Bcrypt)
+ Data.Password.Bcrypt: hashPasswordWithSalt :: Int -> Salt Bcrypt -> Password -> PasswordHash Bcrypt
+ Data.Password.Bcrypt: mkPassword :: Text -> Password
+ Data.Password.Bcrypt: newSalt :: MonadIO m => m (Salt Bcrypt)
+ Data.Password.Bcrypt: newtype PasswordHash a
+ Data.Password.Bcrypt: newtype Salt a
+ Data.Password.Bcrypt: unsafeShowPassword :: Password -> Text
+ Data.Password.PBKDF2: PBKDF2Params :: Word32 -> PBKDF2Algorithm -> Word32 -> Word32 -> PBKDF2Params
+ Data.Password.PBKDF2: PBKDF2_MD5 :: PBKDF2Algorithm
+ Data.Password.PBKDF2: PBKDF2_SHA1 :: PBKDF2Algorithm
+ Data.Password.PBKDF2: PBKDF2_SHA256 :: PBKDF2Algorithm
+ Data.Password.PBKDF2: PBKDF2_SHA512 :: PBKDF2Algorithm
+ Data.Password.PBKDF2: PasswordCheckFail :: PasswordCheck
+ Data.Password.PBKDF2: PasswordCheckSuccess :: PasswordCheck
+ Data.Password.PBKDF2: PasswordHash :: Text -> PasswordHash a
+ Data.Password.PBKDF2: Salt :: ByteString -> Salt a
+ Data.Password.PBKDF2: [pbkdf2Algorithm] :: PBKDF2Params -> PBKDF2Algorithm
+ Data.Password.PBKDF2: [pbkdf2Iterations] :: PBKDF2Params -> Word32
+ Data.Password.PBKDF2: [pbkdf2OutputLength] :: PBKDF2Params -> Word32
+ Data.Password.PBKDF2: [pbkdf2Salt] :: PBKDF2Params -> Word32
+ Data.Password.PBKDF2: [unPasswordHash] :: PasswordHash a -> Text
+ Data.Password.PBKDF2: checkPassword :: Password -> PasswordHash PBKDF2 -> PasswordCheck
+ Data.Password.PBKDF2: data PBKDF2
+ Data.Password.PBKDF2: data PBKDF2Algorithm
+ Data.Password.PBKDF2: data PBKDF2Params
+ Data.Password.PBKDF2: data Password
+ Data.Password.PBKDF2: data PasswordCheck
+ Data.Password.PBKDF2: defaultParams :: PBKDF2Params
+ Data.Password.PBKDF2: hashPassword :: MonadIO m => Password -> m (PasswordHash PBKDF2)
+ Data.Password.PBKDF2: hashPasswordWithParams :: MonadIO m => PBKDF2Params -> Password -> m (PasswordHash PBKDF2)
+ Data.Password.PBKDF2: hashPasswordWithSalt :: PBKDF2Params -> Salt PBKDF2 -> Password -> PasswordHash PBKDF2
+ Data.Password.PBKDF2: instance GHC.Classes.Eq Data.Password.PBKDF2.PBKDF2Algorithm
+ Data.Password.PBKDF2: instance GHC.Classes.Eq Data.Password.PBKDF2.PBKDF2Params
+ Data.Password.PBKDF2: instance GHC.Show.Show Data.Password.PBKDF2.PBKDF2Algorithm
+ Data.Password.PBKDF2: instance GHC.Show.Show Data.Password.PBKDF2.PBKDF2Params
+ Data.Password.PBKDF2: mkPassword :: Text -> Password
+ Data.Password.PBKDF2: newSalt :: MonadIO m => m (Salt PBKDF2)
+ Data.Password.PBKDF2: newtype PasswordHash a
+ Data.Password.PBKDF2: newtype Salt a
+ Data.Password.PBKDF2: unsafeShowPassword :: Password -> Text
+ Data.Password.Scrypt: PasswordCheckFail :: PasswordCheck
+ Data.Password.Scrypt: PasswordCheckSuccess :: PasswordCheck
+ Data.Password.Scrypt: PasswordHash :: Text -> PasswordHash a
+ Data.Password.Scrypt: Salt :: ByteString -> Salt a
+ Data.Password.Scrypt: ScryptParams :: Word32 -> Word32 -> Word32 -> Word32 -> Word32 -> ScryptParams
+ Data.Password.Scrypt: [scryptBlockSize] :: ScryptParams -> Word32
+ Data.Password.Scrypt: [scryptOutputLength] :: ScryptParams -> Word32
+ Data.Password.Scrypt: [scryptParallelism] :: ScryptParams -> Word32
+ Data.Password.Scrypt: [scryptRounds] :: ScryptParams -> Word32
+ Data.Password.Scrypt: [scryptSalt] :: ScryptParams -> Word32
+ Data.Password.Scrypt: [unPasswordHash] :: PasswordHash a -> Text
+ Data.Password.Scrypt: checkPassword :: Password -> PasswordHash Scrypt -> PasswordCheck
+ Data.Password.Scrypt: data Password
+ Data.Password.Scrypt: data PasswordCheck
+ Data.Password.Scrypt: data Scrypt
+ Data.Password.Scrypt: data ScryptParams
+ Data.Password.Scrypt: defaultParams :: ScryptParams
+ Data.Password.Scrypt: hashPassword :: MonadIO m => Password -> m (PasswordHash Scrypt)
+ Data.Password.Scrypt: hashPasswordWithParams :: MonadIO m => ScryptParams -> Password -> m (PasswordHash Scrypt)
+ Data.Password.Scrypt: hashPasswordWithSalt :: ScryptParams -> Salt Scrypt -> Password -> PasswordHash Scrypt
+ Data.Password.Scrypt: instance GHC.Classes.Eq Data.Password.Scrypt.ScryptParams
+ Data.Password.Scrypt: instance GHC.Show.Show Data.Password.Scrypt.ScryptParams
+ Data.Password.Scrypt: mkPassword :: Text -> Password
+ Data.Password.Scrypt: newSalt :: MonadIO m => m (Salt Scrypt)
+ Data.Password.Scrypt: newtype PasswordHash a
+ Data.Password.Scrypt: newtype Salt a
+ Data.Password.Scrypt: unsafeShowPassword :: Password -> Text
- Data.Password: Salt :: ByteString -> Salt
+ Data.Password: Salt :: ByteString -> Salt a
- Data.Password: newSalt :: MonadIO m => m Salt
+ Data.Password: newSalt :: MonadIO m => Int -> m (Salt a)
- Data.Password: newtype Salt
+ Data.Password: newtype Salt a
- Data.Password: unsafeShowPassword :: Pass -> String
+ Data.Password: unsafeShowPassword :: Password -> Text

Files

ChangeLog.md view
@@ -1,5 +1,21 @@ # Changelog for password +## 2.0.0.0++-   Complete overhaul of the library to include hashing and checking+    passwords with not just `scrypt`, but also `PBKDF2`, `bcrypt` and+    `Argon2`.+    [#8](https://github.com/cdepillabout/password/pull/8)+-   `cryptonite` is now used as a dependency, instead of the `scrypt` package.+    [#8](https://github.com/cdepillabout/password/pull/8)+-   Done away with abbreviating "password" (`Pass/pass` -> `Password/password`)+    [#8](https://github.com/cdepillabout/password/pull/8)+-   Removed `unsafeShowPasswordText` and changed `unsafeShowPassword` to be+    `Password -> Text`. (Anyone who needs it to be a `String` knows where to+    find `Data.Text.unpack`)+    [#8](https://github.com/cdepillabout/password/pull/8)+-   GHC versions < 8.2 are no longer actively supported. (Tested to work for GHC 8.2.2)+ ## 1.0.0.0  -   `hashPassWithSalt` has switched function arguments for better currying.
LICENSE view
@@ -1,4 +1,4 @@-Copyright Dennis Gosnell (c) 2019+Copyright (c) Dennis Gosnell, 2019; Felix Paulusma, 2020  All rights reserved. @@ -13,9 +13,9 @@       disclaimer in the documentation and/or other materials provided       with the distribution. -    * Neither the name of Dennis Gosnell nor the names of other-      contributors may be used to endorse or promote products derived-      from this software without specific prior written permission.+    * Neither the name of Dennis Gosnell, Felix Paulusma nor the names+      of other contributors may be used to endorse or promote products+      derived from this software without specific prior written permission.  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ README view
@@ -0,0 +1,20 @@+# password++[![Build Status](https://secure.travis-ci.org/cdepillabout/password.svg)](http://travis-ci.org/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.++Currently supports the following algorithms:++* `PBKDF2`+* `bcrypt`+* `scrypt`+* `Argon2`++Also, see the [password-instances](https://hackage.haskell.org/package/password-instances)+package for instances for common typeclasses.
− README.md
@@ -1,13 +0,0 @@-# password--[![Build Status](https://secure.travis-ci.org/cdepillabout/password.svg)](http://travis-ci.org/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.--Also, see the [password-instances](../password-instances) package for instances-for common typeclasses.
Setup.hs view
@@ -1,2 +1,33 @@+{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -Wall #-}+module Main (main) where++#ifndef MIN_VERSION_cabal_doctest+#define MIN_VERSION_cabal_doctest(x,y,z) 0+#endif++#if MIN_VERSION_cabal_doctest(1,0,0)++import Distribution.Extra.Doctest ( defaultMainWithDoctests )+main :: IO ()+main = defaultMainWithDoctests "doctests"++#else++#ifdef MIN_VERSION_Cabal+-- If the macro is defined, we have new cabal-install,+-- but for some reason we don't have cabal-doctest in package-db+--+-- Probably we are running cabal sdist, when otherwise using new-build+-- workflow+#warning You are configuring this package without cabal-doctest installed. \+         The doctests test-suite will not work as a result. \+         To fix this, install cabal-doctest before configuring.+#endif+ import Distribution.Simple++main :: IO () main = defaultMain++#endif
password.cabal view
@@ -1,22 +1,28 @@ cabal-version: 1.12  name:           password-version:        1.0.0.0+version:        2.0.0.0 category:       Data-synopsis:       plain-text password and hashed password datatypes and functions-description:    A library providing types for working with plain-text and hashed passwords, generally for web applications.+synopsis:       Hashing and checking of passwords+description:    A library providing functionality for working with plain-text and hashed passwords with different types of algorithms. homepage:       https://github.com/cdepillabout/password/password#readme bug-reports:    https://github.com/cdepillabout/password/issues-author:         Dennis Gosnell-maintainer:     cdep.illabout@gmail.com-copyright:      Copyright (c) 2019 Dennis Gosnell+author:         Dennis Gosnell, Felix Paulusma+maintainer:     cdep.illabout@gmail.com, felix.paulusma@gmail.com+copyright:      Copyright (c) Dennis Gosnell, 2019; Felix Paulusma, 2020 license:        BSD3 license-file:   LICENSE-build-type:     Simple+build-type:     Custom extra-source-files:-    README.md+    README     ChangeLog.md +custom-setup+  setup-depends:+      base+    , Cabal+    , cabal-doctest  >=1.0.6 && <1.1+ source-repository head   type: git   location: https://github.com/cdepillabout/password@@ -26,18 +32,26 @@       src   exposed-modules:       Data.Password+      Data.Password.Argon2+      Data.Password.Bcrypt+      Data.Password.PBKDF2+      Data.Password.Scrypt   other-modules:       Paths_password+      Data.Password.Internal   build-depends:-      base >=4.7 && <5-    , scrypt-    , text+      base        >= 4.9      && < 5+    , base64      >= 0.3      && < 0.5+    , bytestring  >= 0.10.8.1 && < 0.11+    , cryptonite  >= 0.15.1   && < 0.27+    , memory      >= 0.14     && < 0.16+    , text        >= 1.2.2    && < 1.3   ghc-options:       -Wall   default-language:       Haskell2010 -test-suite password-doctest+test-suite doctests   type:       exitcode-stdio-1.0   hs-source-dirs:@@ -45,16 +59,15 @@   main-is:       doctest.hs   ghc-options:-      -threaded -rtsopts -with-rtsopts=-N+      -threaded -O2 -rtsopts -with-rtsopts=-N   build-depends:-      base >=4.7 && <5-    -- bytestring is used when actually running the doctests-    , bytestring+      base >=4.9 && <5+    , base-compat     , doctest     , password     , QuickCheck     , quickcheck-instances-    , scrypt+    , template-haskell   default-language:       Haskell2010 @@ -66,12 +79,20 @@   main-is:       Spec.hs   other-modules:-      Paths_password+      Argon2+    , Bcrypt+    , Internal+    , PBKDF2+    , Scrypt+    , Paths_password   ghc-options:-      -threaded -rtsopts -with-rtsopts=-N+      -threaded -O2 -rtsopts -with-rtsopts=-N   build-depends:-      base >=4.7 && <5+      base >=4.9 && <5     , password+    , bytestring+    , cryptonite+    , memory     , quickcheck-instances     , scrypt     , tasty
src/Data/Password.hs view
@@ -1,260 +1,75 @@-{-# LANGUAGE GeneralizedNewtypeDeriving #-}- {-| Module      : Data.Password-Copyright   : (c) Dennis Gosnell, 2019+Copyright   : (c) Dennis Gosnell, 2019; Felix Paulusma, 2020 License     : BSD-style (see LICENSE file) Maintainer  : cdep.illabout@gmail.com Stability   : experimental Portability : POSIX -This module provides an easy way for interacting with passwords from Haskell.-It provides the types 'Pass' and 'PassHash', which correspond to plain-text and+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. -It also provides functions for hashing ('hashPass') and checking passwords-('checkPass').+== API -The real benefit of this module is that there is a corresponding+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)++== Special instances++The real benefit of this module is that there is an accompanying <http://hackage.haskell.org/package/password-instances password-instances>-module that provides canonical typeclass instances for-'Pass' and 'PassHash' for many common typeclasses, like+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> module for more information.+See the <http://hackage.haskell.org/package/password-instances password-instances> package for more information. -} -module Data.Password-  (-    -- * Plaintext Password-    Pass-  , mkPass-    -- * Hashed Password-  , PassHash(..)+module Data.Password (+    -- * Plain-text Password+    Password+  , mkPassword+    -- * Password Hashing+  , PasswordHash(..)+  , PasswordCheck(..)   , Salt(..)-    -- * Functions for Hashing Plaintext Passwords-  , hashPass-  , hashPassWithSalt   , newSalt-    -- * Functions for Checking Plaintext Passwords Against Hashed Passwords-  , checkPass-  , PassCheck(..)-    -- * Unsafe Debugging Functions for Showing a Password+    -- * Unsafe debugging function to show a Password   , unsafeShowPassword-  , unsafeShowPasswordText-  , -- * Setup for doctests.-    -- $setup   ) where -import Control.Monad.IO.Class (MonadIO(liftIO))-import Crypto.Scrypt (EncryptedPass(..), Salt(..), defaultParams, encryptPass,-                      verifyPass')-import qualified Crypto.Scrypt as Scrypt-import Data.String (IsString(..))-import Data.Text (Text, unpack)-import Data.Text.Encoding (decodeUtf8With, encodeUtf8)-import Data.Text.Encoding.Error (lenientDecode)---- $setup--- >>> :set -XOverloadedStrings------ Import needed libraries.------ >>> import Data.ByteString (pack)--- >>> import Test.QuickCheck (Arbitrary(arbitrary), Blind(Blind), vector)--- >>> import Test.QuickCheck.Instances.ByteString ()--- >>> import Test.QuickCheck.Instances.Text ()------ 'Arbitrary' instances for types exported from this library.------ >>> instance Arbitrary Salt where arbitrary = Salt . pack <$> vector 32--- >>> instance Arbitrary Pass where arbitrary = fmap Pass arbitrary--- >>> instance Arbitrary PassHash where arbitrary = hashPassWithSalt <$> arbitrary <*> arbitrary------ 'Arbitrary' instances for types exported from "Crypto.Scrypt".------ >>> instance Arbitrary Scrypt.Pass where arbitrary = fmap Scrypt.Pass arbitrary--- >>> instance Arbitrary EncryptedPass where arbitrary = encryptPass defaultParams <$> arbitrary <*> arbitrary---- | A plain-text password.------ This represents a plain-text password that has /NOT/ been hashed.------ You should be careful with 'Pass'. Make sure not to write it to logs or--- store it in a database.------ You can construct a 'Pass' by using the 'mkPass' 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 @password-instances@ library.-newtype Pass = Pass Text-  deriving (IsString)---- | CAREFUL: 'Show'-ing a 'Pass' will always print @"**PASSWORD**"@------ >>> show $ mkPass "hello"--- "**PASSWORD**"------ @since 1.0.0.0-instance Show Pass where- show _ = "**PASSWORD**"---- | Construct a 'Pass'------ @since 1.0.0.0-mkPass :: Text -> Pass-mkPass = Pass---- | This is an unsafe function that shows a password in plain-text.------ >>> unsafeShowPasswordText $ mkPass "foobar"--- "foobar"------ You should generally not use this function.-unsafeShowPassword :: Pass -> String-unsafeShowPassword = unpack . unsafeShowPasswordText---- | This is like 'unsafeShowPassword' but produces a 'Text' instead of a--- 'String'.-unsafeShowPasswordText :: Pass -> Text-unsafeShowPasswordText (Pass pass) = pass---- | 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 PassHash = PassHash-  { unPassHash :: Text-  } deriving (Eq, Ord, Read, Show)---- | Convert an "Crypto.Scrypt".'Scrypt.Pass' to our 'Pass' type.------ >>> passToScryptPass "foobar"--- Pass {getPass = "foobar"}-passToScryptPass :: Pass -> Scrypt.Pass-passToScryptPass (Pass pass) = Scrypt.Pass $ encodeUtf8 pass---- Convert our 'Pass' type to an "Crypto.Scrypt".'Script.Pass'.------ Opposite of 'passToScryptPass'.------ >>> scryptPassToPass $ Scrypt.Pass "foobar"--- Pass {getPass = "foobar"}--- scryptPassToPass :: Scrypt.Pass -> Pass--- scryptPassToPass (Scrypt.Pass pass) = Pass $ decodeUtf8With lenientDecode pass---- | Convert an "Crypto.Scrypt".'EncryptedPass' to our 'PassHash' type.------ This is the opposite of 'passHashToScryptEncryptedPass'.------ >>> let salt = Salt "abcdefghijklmnopqrstuvwxyz012345"--- >>> let pass = Scrypt.Pass "foobar"--- >>> let encryptedPass = encryptPass defaultParams salt pass--- >>> encryptedPass--- EncryptedPass {getEncryptedPass = "14|8|1|YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU=|nENDaqWBmPKapAqQ3//H0iBImweGjoTqn5SvBS8Mc9FPFbzq6w65maYPZaO+SPamVZRXQjARQ8Y+5rhuDhjIhw=="}--- >>> scryptEncryptedPassToPassHash encryptedPass--- PassHash {unPassHash = "14|8|1|YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU=|nENDaqWBmPKapAqQ3//H0iBImweGjoTqn5SvBS8Mc9FPFbzq6w65maYPZaO+SPamVZRXQjARQ8Y+5rhuDhjIhw=="}------ prop> scryptEncryptedPassToPassHash (passHashToScryptEncryptedPass passHash) == passHash-scryptEncryptedPassToPassHash :: EncryptedPass -> PassHash-scryptEncryptedPassToPassHash (EncryptedPass encPass) =-  PassHash $ decodeUtf8With lenientDecode encPass---- | Convert out 'PassHash' type to "Crypto.Scrypt".'EncryptedPass'.------ This is the opposite of 'scryptEncryptedPassToPassHash'.------ >>> let salt = Salt "abcdefghijklmnopqrstuvwxyz012345"--- >>> let pass = Scrypt.Pass "foobar"--- >>> let encryptedPass = encryptPass defaultParams salt pass--- >>> encryptedPass--- EncryptedPass {getEncryptedPass = "14|8|1|YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU=|nENDaqWBmPKapAqQ3//H0iBImweGjoTqn5SvBS8Mc9FPFbzq6w65maYPZaO+SPamVZRXQjARQ8Y+5rhuDhjIhw=="}--- >>> scryptEncryptedPassToPassHash encryptedPass--- PassHash {unPassHash = "14|8|1|YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU=|nENDaqWBmPKapAqQ3//H0iBImweGjoTqn5SvBS8Mc9FPFbzq6w65maYPZaO+SPamVZRXQjARQ8Y+5rhuDhjIhw=="}------ prop> passHashToScryptEncryptedPass (scryptEncryptedPassToPassHash encPass) == encPass-passHashToScryptEncryptedPass :: PassHash -> EncryptedPass-passHashToScryptEncryptedPass (PassHash passHash) =-  EncryptedPass $ encodeUtf8 passHash---- | Just like 'hashPassWithSalt', but generate a new 'Salt' everytime with a--- call to 'newSalt'.------ >>> hashPass $ mkPass "foobar"--- PassHash {unPassHash = "14|8|1|...|..."}-hashPass :: MonadIO m => Pass -> m PassHash-hashPass pass = do-  salt <- liftIO newSalt-  pure $ hashPassWithSalt salt pass---- | Hash a password with the given 'Salt'.------ The resulting 'PassHash' has the parameters used to hash it, as well as the--- 'Salt' appended to it, separated by @|@.------ The input 'Salt' and resulting 'PassHash' are both byte-64 encoded.------ >>> let salt = Salt "abcdefghijklmnopqrstuvwxyz012345"--- >>> hashPassWithSalt salt (mkPass "foobar")--- PassHash {unPassHash = "14|8|1|YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU=|nENDaqWBmPKapAqQ3//H0iBImweGjoTqn5SvBS8Mc9FPFbzq6w65maYPZaO+SPamVZRXQjARQ8Y+5rhuDhjIhw=="}------ (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 'hashPass'.  'hashPass'--- generates a new 'Salt' everytime it is called.)------ This function uses the hash function from the scrypt package: 'encryptPass'.-hashPassWithSalt :: Salt -> Pass -> PassHash-hashPassWithSalt salt pass =-  scryptEncryptedPassToPassHash $-    encryptPass defaultParams salt (passToScryptPass pass)---- | The result of a checking a password against a hashed version.  This is--- returned by 'checkPass'.-data PassCheck-  = PassCheckSuccess-  -- ^ The password check was successful. The plain-text password matches the-  -- hashed password.-  | PassCheckFail-  -- ^ The password check failed.  The plain-text password does not match the-  -- hashed password.-  deriving (Eq, Read, Show)---- | Check a 'Pass' against a 'PassHash'.------ Returns 'PassCheckSuccess' on success.------ >>> let salt = Salt "abcdefghijklmnopqrstuvwxyz012345"--- >>> let pass = mkPass "foobar"--- >>> let passHash = hashPassWithSalt salt pass--- >>> checkPass pass passHash--- PassCheckSuccess------ Returns 'PassCheckFail' If an incorrect 'Pass' or 'PassHash' is used.------ >>> let badpass = mkPass "incorrect-password"--- >>> checkPass badpass passHash--- PassCheckFail------ This should always fail if an incorrect password is given.------ prop> \(Blind badpass) -> let correctPassHash = hashPassWithSalt salt "foobar" in checkPass badpass correctPassHash == PassCheckFail-checkPass :: Pass -> PassHash -> PassCheck-checkPass pass passHash =-  if verifyPass' (passToScryptPass pass) (passHashToScryptEncryptedPass passHash)-  then PassCheckSuccess-  else PassCheckFail---- | Generate a random 32-byte salt.-newSalt :: MonadIO m => m Salt-newSalt = liftIO Scrypt.newSalt+import Data.Password.Internal --- TODO: Create code for checking that plaintext passwords conform to some sort of+-- TODO: Create code for checking that plain-text passwords conform to some sort of -- password policy. --- data PassPolicy = PassPolicy+-- data PasswordPolicy = PasswordPolicy --   { passPolicyLength :: Int --   , passPolicyCharReqs :: [PolicyCharReq] --   , passPolicyCharSet :: PolicyCharSet
+ src/Data/Password/Argon2.hs view
@@ -0,0 +1,336 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE ExplicitForAll #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-|+Module      : Data.Password.Argon2+Copyright   : (c) Felix Paulusma, 2020+License     : BSD-style (see LICENSE file)+Maintainer  : cdep.illabout@gmail.com+Stability   : experimental+Portability : POSIX++= Argon2++@Argon2@ is probably the newest password algorithm out there. Argon2 was+selected as the winner of the Password Hashing Competition in July 2015.++It has three variants, namely 'Argon2d', 'Argon2i' and 'Argon2id'. These protect+against GPU cracking attacks, side-channel attacks, and both, respectively.++All three modes allow specification by three parameters that control:++* execution time+* memory required+* degree of parallelism++== Other algorithms++In comparison to other algorithms, Argon2 is the least "battle-tested",+being the newest algorithm out there.++It is, however, recommended over @"Scrypt"@ most of the time,+and it also seems like it might become the go-to password algorithm if no+vulnarabilities are discovered within the next couple of years.+-}++-- I think the portability is broadened to+-- whatever, now that we use cryptonite... I think+module Data.Password.Argon2 (+  -- Algorithm+  Argon2+  -- * Plain-text Password+  , Password+  , mkPassword+  -- * Hash Passwords (Argon2)+  , hashPassword+  , PasswordHash(..)+  -- * Verify Passwords (Argon2)+  , checkPassword+  , PasswordCheck(..)+  -- * Hashing Manually (Argon2)+  , hashPasswordWithParams+  , defaultParams+  , Argon2Params(..)+  , Argon2.Variant(..)+  , Argon2.Version(..)+  -- ** Hashing with salt (DISADVISED)+  --+  -- | Hashing with a set 'Salt' is almost never what you want+  -- to do. Use 'hashPassword' or 'hashPasswordWithParams' to have+  -- automatic generation of randomized salts.+  , hashPasswordWithSalt+  , newSalt+  , Salt(..)+  -- * Unsafe debugging function to show a Password+  , unsafeShowPassword+  , -- * Setup for doctests.+    -- $setup+  ) where++import Control.Monad (guard)+import Control.Monad.IO.Class (MonadIO(liftIO))+import Crypto.Error (throwCryptoError)+import Crypto.KDF.Argon2 as Argon2+import Data.ByteArray (Bytes, convert)+import Data.ByteString (ByteString)+import Data.ByteString.Base64 (encodeBase64)+import qualified Data.ByteString.Char8 as C8 (length)+import Data.Maybe (fromMaybe)+#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 qualified Data.Password.Internal (newSalt)+++-- | Phantom type for __Argon2__+--+-- @since 2.0.0.0+data Argon2++-- $setup+-- >>> :set -XFlexibleInstances+-- >>> :set -XOverloadedStrings+--+-- Import needed libraries.+--+-- >>> import Data.Password+-- >>> 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+-- >>> let testParams = defaultParams {argon2TimeCost = 1}+-- >>> let salt = Salt "abcdefghijklmnop"++-- -- >>> instance Arbitrary (PasswordHash Argon2) where arbitrary = hashPasswordWithSalt testParams <$> arbitrary <*> arbitrary++-- | Hash the 'Password' using the 'Argon2' hash algorithm+--+-- >>> hashPassword $ mkPassword "foobar"+-- PasswordHash {unPasswordHash = "$argon2id$v=19$m=65536,t=2,p=1$...$..."}+hashPassword :: MonadIO m => Password -> m (PasswordHash Argon2)+hashPassword = hashPasswordWithParams defaultParams++-- | Parameters used in the 'Argon2' hashing algorithm.+--+-- @since 2.0.0.0+data Argon2Params = Argon2Params {+  argon2Salt :: Word32,+  -- ^ Bytes to randomly generate as a unique salt, default is __16__+  --+  -- Limits are min: @8@, and max: @(2 ^ 32) - 1@+  argon2Variant :: Argon2.Variant,+  -- ^ Which variant of Argon2 to use, default is __'Argon2id'__+  argon2Version :: Argon2.Version,+  -- ^ Which version of Argon2 to use, default is __'Version13'__+  argon2MemoryCost :: Word32,+  -- ^ Memory cost, given in /kibibytes/, default is __65536__ (i.e. 64MB)+  --+  -- Limits are min: @8 * 'argon2Parallelism'@, and max is addressing+  -- space / 2, or @(2 ^ 32) - 1@, whichever is lower.+  argon2TimeCost :: Word32,+  -- ^ Amount of computation realized, default is __2__+  --+  -- Limits are min: @1@, and max: @(2 ^ 32) - 1@+  argon2Parallelism :: Word32,+  -- ^ Parallelism factor, default is __1__+  --+  -- Limits are min: @1@, and max: @(2 ^ 24) - 1@+  argon2OutputLength :: Word32+  -- ^ Output key length in bytes, default is __32__+  --+  -- Limits are min: @4@, and max: @(2 ^ 32) - 1@+} deriving (Eq, Show)++-- | Default parameters for the 'Argon2' algorithm.+--+-- >>> defaultParams+-- Argon2Params {argon2Salt = 16, argon2Variant = Argon2id, argon2Version = Version13, argon2MemoryCost = 65536, argon2TimeCost = 2, argon2Parallelism = 1, argon2OutputLength = 32}+--+-- @since 2.0.0.0+defaultParams :: Argon2Params+defaultParams = Argon2Params {+  argon2Salt = 16,+  argon2Variant = Argon2id,+  argon2Version = Version13,+  argon2MemoryCost = 2 ^ (16 :: Int),+  argon2TimeCost = 2,+  argon2Parallelism = 1,+  argon2OutputLength = 32+}++-- | Hash a password with the given 'Argon2Params' and also with the given 'Salt'+-- instead of a random generated salt using 'argon2Salt' from 'Argon2Params'. (cf. 'hashPasswordWithParams')+-- Using 'hashPasswordWithSalt' is strongly __disadvised__ and 'hashPasswordWithParams' should be used instead.+-- /Never use a static salt in production applications!/+--+-- __N.B.__: The salt HAS to be 8 bytes or more, or this function will throw an error!+--+-- >>> let salt = Salt "abcdefghijklmnop"+-- >>> hashPasswordWithSalt defaultParams salt (mkPassword "foobar")+-- 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'+-- generates a new 'Salt' everytime it is called.)+hashPasswordWithSalt :: Argon2Params -> Salt Argon2 -> Password -> PasswordHash Argon2+hashPasswordWithSalt params@Argon2Params{..} s@(Salt salt) pass =+  PasswordHash . mappend "$argon2" $ T.intercalate "$"+    [ variantToLetter argon2Variant+    , "v=" <> versionToNum argon2Version+    , parameters+    , encodeBase64 salt+    , encodeBase64 key+    ]+  where+    parameters = T.intercalate ","+        [ "m=" <> showT argon2MemoryCost+        , "t=" <> showT argon2TimeCost+        , "p=" <> showT argon2Parallelism+        ]+    key = hashPasswordWithSalt' params s pass++-- | Only for internal use+hashPasswordWithSalt' :: Argon2Params -> Salt Argon2 -> Password -> ByteString+hashPasswordWithSalt' Argon2Params{..} (Salt salt) (Password pass) =+    convert (argon2Hash :: Bytes)+  where+    argon2Hash = throwCryptoError $+        Argon2.hash options (toBytes pass) (convert salt :: Bytes) $ fromIntegral argon2OutputLength+    options = Argon2.Options {+        iterations = argon2TimeCost,+        memory = argon2MemoryCost,+        parallelism = argon2Parallelism,+        variant = argon2Variant,+        version = argon2Version+      }++-- | Hash a password using the 'Argon2' algorithm with the given 'Argon2Params'.+--+-- __N.B.__: If you have any doubt in your knowledge of cryptography and/or the+-- 'Argon2' algorithm, please just use 'hashPassword'.+--+-- Advice to set the parameters:+--+-- * Figure out how many threads you can use, choose "parallelism" accordingly.+-- * Figure out how much memory you can use, choose "memory cost" accordingly.+-- * Decide on the maximum time @x@ you can spend on it, choose the largest+-- "time cost" such that it takes less than @x@ with your system and other+-- parameter choices.+--+-- @since 2.0.0.0+hashPasswordWithParams :: MonadIO m => Argon2Params -> Password -> m (PasswordHash Argon2)+hashPasswordWithParams params pass = liftIO $ do+    salt <- Data.Password.Internal.newSalt . fromIntegral $ argon2Salt params+    return $ hashPasswordWithSalt params salt pass++-- TODO: Parse different kinds of hashes, not only the ones from this library.+-- e.g. hashes that miss the first $, or have 'argon2$' in front of the 'argon2id' part.++-- | Check a 'Password' against a 'PasswordHash' 'Argon2'.+--+-- Returns 'PasswordCheckSuccess' on success.+--+-- >>> let pass = mkPassword "foobar"+-- >>> passHash <- hashPassword pass+-- >>> checkPassword pass passHash+-- PasswordCheckSuccess+--+-- Returns 'PasswordCheckFail' if an incorrect 'Password' or 'PasswordHash' 'Argon2' is used.+--+-- >>> let badpass = mkPassword "incorrect-password"+-- >>> checkPassword badpass passHash+-- PasswordCheckFail+--+-- This should always fail if an incorrect password is given.+--+-- prop> \(Blind badpass) -> let correctPasswordHash = hashPasswordWithSalt testParams salt "foobar" in checkPassword badpass correctPasswordHash == PasswordCheckFail+checkPassword :: Password -> PasswordHash Argon2 -> PasswordCheck+checkPassword pass (PasswordHash passHash) =+  fromMaybe PasswordCheckFail $ do+    let paramList = T.split (== '$') passHash+    guard $ length paramList == 6+    let [ _,+          variantT,+          versionT,+          parametersT,+          salt64,+          hashedKey64 ] = paramList+    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+        producedKey = hashPasswordWithSalt' Argon2Params{..} (Salt salt) pass+    guard $ hashedKey == producedKey+    return PasswordCheckSuccess+  where+    argon2Salt = 16 -- only here because of warnings+    parseVariant = splitMaybe "argon2" letterToVariant+    parseVersion = splitMaybe "v=" numToVersion+    parseParameters params = do+        let ps = T.split (== ',') params+        guard $ length ps == 3+        go ps (Nothing, Nothing, Nothing)+      where+        go [] (Just m, Just t, Just p) = Just (m, t, p)+        go [] _ = Nothing+        go (x:xs) (m, t, p) =+          case T.splitAt 2 x of+            ("m=", i) -> go xs (readT i, t, p)+            ("t=", i) -> go xs (m, readT i, p)+            ("p=", i) -> go xs (m, t, readT i)+            _ -> Nothing+    splitMaybe :: Text -> (Text -> Maybe a) -> Text -> Maybe a+    splitMaybe match f t =+      case T.splitAt (T.length match) t of+        (m, x) | m == match -> f x+        _  -> Nothing++-- | Generate a random 16-byte @Argon2@ salt+--+-- @since 2.0.0.0+newSalt :: MonadIO m => m (Salt Argon2)+newSalt = Data.Password.Internal.newSalt 16++-- | Makes a letter out of the variant+variantToLetter :: Argon2.Variant -> Text+variantToLetter = \case+    Argon2i  -> "i"+    Argon2d  -> "d"+    Argon2id -> "id"++-- | Parses the variant parameter in the encoded hash+letterToVariant :: Text -> Maybe Argon2.Variant+letterToVariant = \case+    "i"  -> Just Argon2i+    "d"  -> Just Argon2d+    "id" -> Just Argon2id+    _ -> Nothing++-- | Parses the "v=" parameter in the encoded hash+numToVersion :: Text -> Maybe Argon2.Version+numToVersion "16" = Just Argon2.Version10+numToVersion "19" = Just Argon2.Version13+numToVersion _ = Nothing++-- | Makes number for the "v=" parameter in the encoded hash+versionToNum :: Argon2.Version -> Text+versionToNum Version10 = "16"+versionToNum Version13 = "19"
+ src/Data/Password/Bcrypt.hs view
@@ -0,0 +1,181 @@+{-|+Module      : Data.Password.Bcrypt+Copyright   : (c) Felix Paulusma, 2020+License     : BSD-style (see LICENSE file)+Maintainer  : cdep.illabout@gmail.com+Stability   : experimental+Portability : POSIX++= bcrypt++The @bcrypt@ algorithm is a popular way of hashing passwords.+It is based on the Blowfish cipher and fairly straightfoward+in its usage. It has a cost parameter that, when increased,+slows down the hashing speed.++It is a straightforward and easy way to get decent protection+on passwords, it is also been around long enough to be battle-tested+and generally considered to provide a good amount of security.++== Other algorithms++@bcrypt@, together with @"PBKDF2"@, are only computationally intensive.+And to protect from specialized hardware, new algorithms have been+developed that are also resource intensive, like @"Scrypt"@ and+@"Argon2"@. Not having high resource demands, means an attacker with+specialized software could take less time to brute-force a password,+though with the default cost (10) and a decently long password,+the amount of time to brute-force would still be significant.++This the algorithm to use if you're not sure about your needs, but+just want a decent, proven way to encrypt your passwords.+-}++module Data.Password.Bcrypt (+  -- * Algorithm+  Bcrypt+  -- * Plain-text Password+  , Password+  , mkPassword+  -- * Hash Passwords (bcrypt)+  , hashPassword+  , PasswordHash(..)+  -- * Verify Passwords (bcrypt)+  , checkPassword+  , PasswordCheck(..)+  -- * Hashing Manually (bcrypt)+  , hashPasswordWithParams+  -- ** Hashing with salt (DISADVISED)+  --+  -- | Hashing with a set 'Salt' is almost never what you want+  -- to do. Use 'hashPassword' or 'hashPasswordWithParams' to have+  -- automatic generation of randomized salts.+  , hashPasswordWithSalt+  , newSalt+  , Salt(..)+  -- * Unsafe debugging function to show a Password+  , unsafeShowPassword+  , -- * Setup for doctests.+    -- $setup+  ) where++import Control.Monad.IO.Class (MonadIO(liftIO))+import Crypto.KDF.BCrypt as Bcrypt hiding (hashPassword)+import Data.ByteArray (Bytes, convert)++import Data.Password (+         PasswordCheck(..)+       , PasswordHash(..)+       , Salt(..)+       , mkPassword+       , unsafeShowPassword+       )+import Data.Password.Internal (Password(..), fromBytes, toBytes)+import qualified Data.Password.Internal (newSalt)+++-- | Phantom type for __bcrypt__+--+-- @since 2.0.0.0+data Bcrypt++-- $setup+-- >>> :set -XFlexibleInstances+-- >>> :set -XOverloadedStrings+--+-- Import needed libraries.+--+-- >>> import Data.Password+-- >>> 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+-- >>> let salt = Salt "abcdefghijklmnop"++-- -- >>> instance Arbitrary (PasswordHash Bcrypt) where arbitrary = hashPasswordWithSalt 8 <$> arbitrary <*> arbitrary++-- | Hash the 'Password' using the /bcrypt/ hash algorithm.+--+-- __N.B.__: @bcrypt@ has a limit of 72 bytes as input, so anything longer than that+-- will be cut off at the 72 byte point and thus any password that is 72 bytes+-- or longer will match as long as the first 72 bytes are the same.+--+-- >>> hashPassword $ mkPassword "foobar"+-- PasswordHash {unPasswordHash = "$2b$10$..."}+hashPassword :: MonadIO m => Password -> m (PasswordHash Bcrypt)+hashPassword = hashPasswordWithParams 10++-- | Hash a password with the given cost and also with the given 'Salt'+-- instead of generating a random salt. Using 'hashPasswordWithSalt' is strongly __disadvised__,+-- and 'hashPasswordWithParams' should be used instead. /Never use a static salt/+-- /in production applications!/+--+-- __N.B.__: The salt HAS to be 16 bytes or this function will throw an error!+--+-- >>> let salt = Salt "abcdefghijklmnop"+-- >>> hashPasswordWithSalt 10 salt (mkPassword "foobar")+-- PasswordHash {unPasswordHash = "$2b$10$WUHhXETkX0fnYkrqZU3ta.N8Utt4U77kW4RVbchzgvBvBBEEdCD/u"}+--+-- (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'+-- (and 'hashPasswordWithParams') generates a new 'Salt' everytime it is called.)+hashPasswordWithSalt+  :: Int -- ^ The cost parameter. Should be between 4 and 31 (inclusive). Values which lie outside this range will be adjusted accordingly.+  -> 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)+    in PasswordHash $ fromBytes hash++-- | Hash a password using the /bcrypt/ algorithm with the given cost.+--+-- The higher the cost, the longer 'hashPassword' and 'checkPassword' will take to run,+-- thus increasing the security, but taking longer and taking up more resources.+-- The optimal cost for generic user logins would be one that would take between+-- 0.05 - 0.5 seconds to check on the machine that will run it.+--+-- __N.B.__: It is advised to use 'hashPassword' if you're unsure about the+-- implications that changing the cost brings with it.+--+-- @since 2.0.0.0+hashPasswordWithParams+  :: MonadIO m+  => Int -- ^ The cost parameter. Should be between 4 and 31 (inclusive). Values which lie outside this range will be adjusted accordingly.+  -> Password -- ^ The password to be hashed.+  -> m (PasswordHash Bcrypt) -- ^ The bcrypt hash in standard format.+hashPasswordWithParams cost pass = liftIO $ do+    salt <- newSalt+    return $ hashPasswordWithSalt cost salt pass++-- | Check a 'Password' against a 'PasswordHash' 'Bcrypt'.+--+-- Returns 'PasswordCheckSuccess' on success.+--+-- >>> let pass = mkPassword "foobar"+-- >>> passHash <- hashPassword pass+-- >>> checkPassword pass passHash+-- PasswordCheckSuccess+--+-- Returns 'PasswordCheckFail' if an incorrect 'Password' or 'PasswordHash' 'Bcrypt' is used.+--+-- >>> let badpass = mkPassword "incorrect-password"+-- >>> checkPassword badpass passHash+-- PasswordCheckFail+--+-- This should always fail if an incorrect password is given.+--+-- 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)+      then PasswordCheckSuccess+      else PasswordCheckFail++-- | Generate a random 16-byte @bcrypt@ salt+--+-- @since 2.0.0.0+newSalt :: MonadIO m => m (Salt Bcrypt)+newSalt = Data.Password.Internal.newSalt 16
+ src/Data/Password/Internal.hs view
@@ -0,0 +1,140 @@+{-# LANGUAGE ExplicitForAll #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-|+Module      : Data.Password.Internal+Copyright   : (c) Dennis Gosnell, 2019; Felix Paulusma, 2020+License     : BSD-style (see LICENSE file)+Maintainer  : cdep.illabout@gmail.com+Stability   : experimental+Portability : POSIX+-}++module Data.Password.Internal (+    -- * Global types+    Password(..)+  , mkPassword+  , PasswordHash(..)+  , PasswordCheck(..)+  , Salt(..)+  , newSalt+  -- * Unsafe function+  , unsafeShowPassword+  -- * Utility+  , toBytes+  , fromBytes+  , from64+  , readT+  , showT+  ) where++import Control.Monad.IO.Class (MonadIO(liftIO))+import Crypto.Random (getRandomBytes)+import Data.ByteArray (Bytes, convert)+import Data.ByteString (ByteString)+import Data.ByteString.Base64 (decodeBase64)+import Data.String (IsString(..))+import Data.Text as T (Text, pack, unpack)+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+newSalt :: MonadIO m => Int -> m (Salt a)+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 (Eq, Ord, Read, Show)++-- | The result of checking a password against a hashed version. This is+-- returned by the @checkPassword@ functions.+data PasswordCheck+  = PasswordCheckSuccess+  -- ^ The password check was successful. The plain-text password matches the+  -- hashed password.+  | PasswordCheckFail+  -- ^ The password check failed. The plain-text password does not match the+  -- hashed password.+  deriving (Eq, Read, Show)++-- | Converting 'Text' to 'Bytes'+toBytes :: Text -> Bytes+toBytes = convert . encodeUtf8+{-# INLINE toBytes #-}++-- | Converting 'Bytes' to 'Text'+fromBytes :: Bytes -> Text+fromBytes = decodeUtf8 . convert+{-# INLINE fromBytes #-}++-- | Decodes a base64 'Text' to a regular 'ByteString' (if possible)+from64 :: Text -> Maybe ByteString+from64 = toMaybe . decodeBase64 . encodeUtf8+  where+    toMaybe = either (const Nothing) Just+{-# INLINE from64 #-}++-- | Same as 'read' but works on 'Text'+readT :: forall a. 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 = T.pack . show+{-# INLINE showT #-}+
+ src/Data/Password/PBKDF2.hs view
@@ -0,0 +1,294 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-|+Module      : Data.Password.PBKDF2+Copyright   : (c) Felix Paulusma, 2020+License     : BSD-style (see LICENSE file)+Maintainer  : cdep.illabout@gmail.com+Stability   : experimental+Portability : POSIX++= PBKDF2++The PBKDF2 algorithm is one of the oldest and most solid password+algorithms out there. It has also, however, been shown to be+the least secure out of all major password algorithms. The main+reason for this is that is doesn't make use of any memory cost+or other method of making it difficult for specialized hardware+attacks, like GPU cracking attacks.++It is still, however used all over the world, since it has been+shown to be a very reliable way to encrypt passwords. Though it is+most definitely better than trying to develop a password algorithm+on your own, or god-forbid, not using /any/ encryption on your stored+passwords.++== Other algorithms++Seeing as PBKDF2 is shown to be very weak in terms of protection+against GPU cracking attacks, it is generally advised to go with+@"Bcrypt"@, if not @"Scrypt"@ or @"Argon2"@. When unsure, @"Bcrypt"@+would probably be the safest option, as it has no memory cost which+could become a problem if not properly calibrated to the machine+doing the password verifications.+-}++module Data.Password.PBKDF2 (+  -- Algorithm+  PBKDF2+  -- * Plain-text Password+  , Password+  , mkPassword+  -- * Hash Passwords (PBKDF2)+  , hashPassword+  , PasswordHash(..)+  -- * Verify Passwords (PBKDF2)+  , checkPassword+  , PasswordCheck(..)+  -- * Hashing Manually (PBKDF2)+  , hashPasswordWithParams+  , defaultParams+  , PBKDF2Params(..)+  , PBKDF2Algorithm(..)+  -- ** Hashing with salt (DISADVISED)+  --+  -- | Hashing with a set 'Salt' is almost never what you want+  -- to do. Use 'hashPassword' or 'hashPasswordWithParams' to have+  -- automatic generation of randomized salts.+  , hashPasswordWithSalt+  , newSalt+  , Salt(..)+  -- * Unsafe debugging function to show a Password+  , unsafeShowPassword+  , -- * Setup for doctests.+    -- $setup+  ) where++import Control.Monad (guard)+import Control.Monad.IO.Class (MonadIO(liftIO))+import Crypto.Hash.Algorithms as Crypto (MD5(..))+import Crypto.KDF.PBKDF2 as PBKDF2+import Data.ByteArray (ByteArray, ByteArrayAccess, Bytes, convert)+import Data.ByteString (ByteString)+import qualified Data.ByteString.Base64 as Base64+import qualified Data.ByteString.Char8 as C8 (length)+import Data.Maybe (fromMaybe)+import Data.Text (Text)+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 qualified Data.Password.Internal (newSalt)+++-- | Phantom type for __PBKDF2__+--+-- @since 2.0.0.0+data PBKDF2++-- $setup+-- >>> :set -XFlexibleInstances+-- >>> :set -XOverloadedStrings+--+-- Import needed libraries.+--+-- >>> import Data.Password+-- >>> 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+-- >>> let testParams = defaultParams{ pbkdf2Iterations = 5000 }+-- >>> let salt = Salt "abcdefghijklmnop"++-- -- >>> instance Arbitrary (PasswordHash PBKDF2) where arbitrary = hashPasswordWithSalt defaultParams <$> arbitrary <*> arbitrary++-- | Hash the 'Password' using the 'PBKDF2' hash algorithm+--+-- >>> hashPassword $ mkPassword "foobar"+-- PasswordHash {unPasswordHash = "sha512:25000:...:..."}+hashPassword :: MonadIO m => Password -> m (PasswordHash PBKDF2)+hashPassword = hashPasswordWithParams defaultParams++-- TODO: Add way to parse the following:+-- $pbkdf2-md5$29000$...$...+-- $pbkdf2$25000$...$... (SHA1)+-- $pbkdf2-sha256$29000$x9h7j/Ge8x6DMEao1VqrdQ$kra3R1wEnY8mPdDWOpTqOTINaAmZvRMcYd8u5OBQP9A+-- $pbkdf2-sha512$25000$LyWE0HrP2RsjZCxlDGFMKQ$1vC5Ohk2mCS9b6akqsEfgeb4l74SF8XjH.SljXf3dMLHdlY1GK9ojcCKts6/asR4aPqBmk74nCDddU3tvSCJvw++-- | Parameters used in the 'PBKDF2' hashing algorithm.+--+-- @since 2.0.0.0+data PBKDF2Params = PBKDF2Params {+  pbkdf2Salt :: Word32,+  -- ^ Bytes to randomly generate as a unique salt, default is __16__+  pbkdf2Algorithm :: PBKDF2Algorithm,+  -- ^ Which algorithm to use for hashing, default is __'PBKDF2_SHA512'__+  pbkdf2Iterations :: Word32,+  -- ^ Rounds to hash, default is __25,000__+  pbkdf2OutputLength :: Word32+  -- ^ Output key length in bytes, default is __64__+  --+  -- Limits are min: 1, max: /the amount of entropy of the hashing algorithm/.+  -- This is limited automatically to __16, 20, 32, 64__+  -- for __MD5, SHA1, SHA256, SHA512__, respectively.+} deriving (Eq, Show)++-- | Default parameters for the 'PBKDF2' algorithm.+--+-- >>> defaultParams+-- PBKDF2Params {pbkdf2Salt = 16, pbkdf2Algorithm = PBKDF2_SHA512, pbkdf2Iterations = 25000, pbkdf2OutputLength = 64}+--+-- @since 2.0.0.0+defaultParams :: PBKDF2Params+defaultParams = PBKDF2Params {+  pbkdf2Salt = 16,+  pbkdf2Algorithm = PBKDF2_SHA512,+  pbkdf2Iterations = 25 * 1000,+  pbkdf2OutputLength = 64+}++-- | Hash a password with the given 'PBKDF2Params' and also with the given 'Salt'+-- instead of a randomly generated salt using 'pbkdf2Salt' from 'PBKDF2Params'. (cf. 'hashPasswordWithParams')+-- Using 'hashPasswordWithSalt' is strongly __disadvised__ and 'hashPasswordWithParams' should be used instead.+-- /Never use a static salt in production applications!/+--+-- >>> let salt = Salt "abcdefghijklmnop"+-- >>> hashPasswordWithSalt defaultParams salt (mkPassword "foobar")+-- PasswordHash {unPasswordHash = "sha512:25000:YWJjZGVmZ2hpamtsbW5vcA==:JRElYYrOMe9OIV4LDxaLTgO9ho8fFBVofXoQcdngi7AcuH6Amvmlj2B0y6y1UtQciXXBepSCS+rpy8/vDDQvoA=="}+--+-- (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'+-- (and 'hashPasswordWithParams') generates a new 'Salt' everytime it is called.)+hashPasswordWithSalt :: PBKDF2Params -> Salt PBKDF2 -> Password -> PasswordHash PBKDF2+hashPasswordWithSalt params@PBKDF2Params{..} s@(Salt salt) pass =+  PasswordHash $ T.intercalate ":"+    [ algToText pbkdf2Algorithm+    , T.pack $ show pbkdf2Iterations+    , b64 salt+    , b64 key+    ]+  where+    b64 = Base64.encodeBase64+    key = hashPasswordWithSalt' params s pass++-- | Only for internal use+hashPasswordWithSalt' :: PBKDF2Params -> Salt PBKDF2 -> Password -> ByteString+hashPasswordWithSalt' PBKDF2Params{..} (Salt salt) (Password pass) =+    convert (pbkdf2Hash :: Bytes)+  where+    pbkdf2Hash = algToFunc pbkdf2Algorithm params (toBytes pass) (convert salt :: Bytes)+    params = PBKDF2.Parameters {+        PBKDF2.iterCounts = fromIntegral pbkdf2Iterations,+        PBKDF2.outputLength = fromIntegral $ maxOutputLength pbkdf2Algorithm pbkdf2OutputLength+      }++-- | Hash a password using the 'PBKDF2' algorithm with the given 'PBKDF2Params'.+--+-- __N.B.__: If you have any doubt in your knowledge of cryptography and/or the+-- 'PBKDF2' algorithm, please just use 'hashPassword'.+--+-- @since 2.0.0.0+hashPasswordWithParams :: MonadIO m => PBKDF2Params -> Password -> m (PasswordHash PBKDF2)+hashPasswordWithParams params pass = liftIO $ do+    salt <- Data.Password.Internal.newSalt . fromIntegral $ pbkdf2Salt params+    return $ hashPasswordWithSalt params salt pass++-- | Check a 'Password' against a 'PasswordHash' 'PBKDF2'.+--+-- Returns 'PasswordCheckSuccess' on success.+--+-- >>> let pass = mkPassword "foobar"+-- >>> passHash <- hashPassword pass+-- >>> checkPassword pass passHash+-- PasswordCheckSuccess+--+-- Returns 'PasswordCheckFail' if an incorrect 'Password' or 'PasswordHash' 'PBKDF2' is used.+--+-- >>> let badpass = mkPassword "incorrect-password"+-- >>> checkPassword badpass passHash+-- PasswordCheckFail+--+-- This should always fail if an incorrect password is given.+--+-- prop> \(Blind badpass) -> let correctPasswordHash = hashPasswordWithSalt testParams salt "foobar" in checkPassword badpass correctPasswordHash == PasswordCheckFail+checkPassword :: Password -> PasswordHash PBKDF2 -> PasswordCheck+checkPassword pass (PasswordHash passHash) =+  fromMaybe PasswordCheckFail $ do+    -- This step makes it possible to also check the following format:+    -- "pbkdf2:sha256:150000:etc.etc."+    let passHash' = fromMaybe passHash $ "pbkdf2:" `T.stripPrefix` passHash+        paramList = T.split (== ':') passHash'+    guard $ length paramList == 4+    let [ algT,+          iterationsT,+          salt64,+          hashedKey64 ] = paramList+    pbkdf2Algorithm <- textToAlg algT+    pbkdf2Iterations <- readT iterationsT+    salt <- from64 salt64+    hashedKey <- from64 hashedKey64+    let pbkdf2OutputLength = fromIntegral $ C8.length hashedKey+        producedKey = hashPasswordWithSalt' PBKDF2Params{..} (Salt salt) pass+    guard $ hashedKey == producedKey+    return PasswordCheckSuccess+  where+    pbkdf2Salt = 16+++-- | Type of algorithm to use for hashing PBKDF2 passwords.+--+-- N.B.: 'PBKDF2_MD5' and 'PBKDF2_SHA1' are not considered very secure.+data PBKDF2Algorithm =+    PBKDF2_MD5+  | PBKDF2_SHA1+  | PBKDF2_SHA256+  | PBKDF2_SHA512+  deriving (Eq, Show)++-- | Depending on the given algorithm limits the output length.+maxOutputLength :: PBKDF2Algorithm -> Word32 -> Word32+maxOutputLength = min . \case+  PBKDF2_MD5 -> 16+  PBKDF2_SHA1 -> 20+  PBKDF2_SHA256 -> 32+  PBKDF2_SHA512 -> 64++algToText :: PBKDF2Algorithm -> Text+algToText = \case+  PBKDF2_MD5 -> "md5"+  PBKDF2_SHA1 -> "sha1"+  PBKDF2_SHA256 -> "sha256"+  PBKDF2_SHA512 -> "sha512"++textToAlg :: Text -> Maybe PBKDF2Algorithm+textToAlg = \case+  "md5" -> Just PBKDF2_MD5+  "sha1" -> Just PBKDF2_SHA1+  "sha256" -> Just PBKDF2_SHA256+  "sha512" -> Just PBKDF2_SHA512+  _ -> Nothing++-- Which function to use, based on the given algorithm+algToFunc :: (ByteArrayAccess password, ByteArrayAccess salt, ByteArray hash)+          => PBKDF2Algorithm -> PBKDF2.Parameters -> password -> salt -> hash+algToFunc = \case+  PBKDF2_MD5 -> PBKDF2.generate (PBKDF2.prfHMAC Crypto.MD5)+  PBKDF2_SHA1 -> PBKDF2.fastPBKDF2_SHA1+  PBKDF2_SHA256 -> PBKDF2.fastPBKDF2_SHA256+  PBKDF2_SHA512 -> PBKDF2.fastPBKDF2_SHA512++-- | Generate a random 16-byte @PBKDF2@ salt+--+-- @since 2.0.0.0+newSalt :: MonadIO m => m (Salt PBKDF2)+newSalt = Data.Password.Internal.newSalt 16
+ src/Data/Password/Scrypt.hs view
@@ -0,0 +1,262 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-|+Module      : Data.Password.Scrypt+Copyright   : (c) Dennis Gosnell, 2019; Felix Paulusma, 2020+License     : BSD-style (see LICENSE file)+Maintainer  : cdep.illabout@gmail.com+Stability   : experimental+Portability : POSIX++= scrypt++The @scrypt@ algorithm is a fairly new one. First published+in 2009, but published by the IETF in 2016 as <https://tools.ietf.org/html/rfc7914 RFC 7914>.+Originally used for the Tarsnap backup service, it is+designed to be costly by requiring large amounts of memory.++== Other algorithms++@scrypt@ does increase the memory requirement in contrast to+@"Bcrypt"@ and @"PBKDF2"@, but it turns out it is not as optimal+as it could be, and thus others have set out to search for other+algorithms that do fulfill on their promises. @"Argon2"@ seems+to be the winner in that search.++That is not to say using @scrypt@ somehow means your passwords+won't be properly protected. The cryptography is sound and+thus is fine for protection against brute-force attacks.+Because of the memory cost, it is generally advised to use+@"Bcrypt"@ if you're not sure this might be a+problem on your system.+-}++module Data.Password.Scrypt (+  -- * Algorithm+  Scrypt+  -- * Plain-text Password+  , Password+  , mkPassword+  -- * Hash Passwords (scrypt)+  , hashPassword+  , PasswordHash(..)+  -- * Verify Passwords (scrypt)+  , checkPassword+  , PasswordCheck(..)+  -- * Hashing Manually (scrypt)+  , hashPasswordWithParams+  , defaultParams+  , ScryptParams(..)+  -- ** Hashing with salt (DISADVISED)+  --+  -- | Hashing with a set 'Salt' is almost never what you want+  -- to do. Use 'hashPassword' or 'hashPasswordWithParams' to have+  -- automatic generation of randomized salts.+  , hashPasswordWithSalt+  , newSalt+  , Salt(..)+  -- * Unsafe debugging function to show a Password+  , unsafeShowPassword+  , -- * Setup for doctests.+    -- $setup+  ) where++import Control.Monad (guard)+import Control.Monad.IO.Class (MonadIO(liftIO))+import Crypto.KDF.Scrypt as Scrypt+import Data.ByteArray (Bytes, convert)+import Data.ByteString (ByteString)+import Data.ByteString.Base64 (encodeBase64)+import qualified Data.ByteString.Char8 as C8 (length)+import Data.Maybe (fromMaybe)+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 qualified Data.Password.Internal (newSalt)++-- | Phantom type for __Argon2__+--+-- @since 2.0.0.0+data Scrypt++-- $setup+-- >>> :set -XFlexibleInstances+-- >>> :set -XOverloadedStrings+--+-- Import needed libraries.+--+-- >>> import Data.Password+-- >>> 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+-- >>> let salt = Salt "abcdefghijklmnopqrstuvwxyz012345"+-- >>> let testParams = defaultParams {scryptRounds = 10}++-- -- >>> instance Arbitrary (PasswordHash Scrypt) where arbitrary = hashPasswordWithSalt testParams <$> arbitrary <*> arbitrary++-- | Hash the 'Password' using the 'Scrypt' hash algorithm+--+-- >>> hashPassword $ mkPassword "foobar"+-- PasswordHash {unPasswordHash = "14|8|1|...|..."}+hashPassword :: MonadIO m => Password -> m (PasswordHash Scrypt)+hashPassword = hashPasswordWithParams defaultParams++-- TODO: Add way to parse the following. From [https://hashcat.net/wiki/doku.php?id=example_hashes]+-- SCRYPT:1024:1:1:MDIwMzMwNTQwNDQyNQ==:5FW+zWivLxgCWj7qLiQbeC8zaNQ+qdO0NUinvqyFcfo=++-- | Parameters used in the 'Scrypt' hashing algorithm.+--+-- @since 2.0.0.0+data ScryptParams = ScryptParams {+  scryptSalt :: Word32,+  -- ^ Bytes to randomly generate as a unique salt, default is __32__+  scryptRounds :: Word32,+  -- ^ log2(N) rounds to hash, default is __14__ (i.e. 2^14 rounds)+  scryptBlockSize :: Word32,+  -- ^ Block size, default is __8__+  --+  -- Limits are min: @1@, and max: @scryptBlockSize * scryptParallelism < 2 ^ 30@+  scryptParallelism :: Word32,+  -- ^ Parallelism factor, default is __1__+  --+  -- Limits are min: @0@, and max: @scryptBlockSize * scryptParallelism < 2 ^ 30@+  scryptOutputLength :: Word32+  -- ^ Output key length in bytes, default is __64__+} deriving (Eq, Show)++-- | Default parameters for the 'Scrypt' algorithm.+--+-- >>> defaultParams+-- ScryptParams {scryptSalt = 32, scryptRounds = 14, scryptBlockSize = 8, scryptParallelism = 1, scryptOutputLength = 64}+--+-- @since 2.0.0.0+defaultParams :: ScryptParams+defaultParams = ScryptParams {+  scryptSalt = 32,+  scryptRounds = 14,+  scryptBlockSize = 8,+  scryptParallelism = 1,+  scryptOutputLength = 64+}++-- | Hash a password with the given 'ScryptParams' and also with the given 'Salt'+-- instead of a randomly generated salt using 'scryptSalt' from 'ScryptParams'.+-- Using 'hashPasswordWithSalt' is strongly __disadvised__ and 'hashPasswordWithParams'+-- should be used instead. /Never use a static salt in production applications!/+--+-- The resulting 'PasswordHash' has the parameters used to hash it, as well as the+-- 'Salt' appended to it, separated by @|@.+--+-- The input 'Salt' and resulting 'PasswordHash' are both base64 encoded.+--+-- >>> let salt = Salt "abcdefghijklmnopqrstuvwxyz012345"+-- >>> hashPasswordWithSalt defaultParams salt (mkPassword "foobar")+-- PasswordHash {unPasswordHash = "14|8|1|YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU=|nENDaqWBmPKapAqQ3//H0iBImweGjoTqn5SvBS8Mc9FPFbzq6w65maYPZaO+SPamVZRXQjARQ8Y+5rhuDhjIhw=="}+--+-- (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'+-- generates a new 'Salt' everytime it is called.)+hashPasswordWithSalt :: ScryptParams -> Salt Scrypt -> Password -> PasswordHash Scrypt+hashPasswordWithSalt params@ScryptParams{..} s@(Salt salt) pass =+  PasswordHash $ T.intercalate "|"+    [ showT scryptRounds+    , showT scryptBlockSize+    , showT scryptParallelism+    , encodeBase64 salt+    , encodeBase64 key+    ]+  where+    key = hashPasswordWithSalt' params s pass++-- | Only for internal use+hashPasswordWithSalt' :: ScryptParams -> Salt Scrypt -> Password -> ByteString+hashPasswordWithSalt' ScryptParams{..} (Salt salt) (Password pass) =+    convert (scryptHash :: Bytes)+  where+    scryptHash = Scrypt.generate params (toBytes pass) (convert salt :: Bytes)+    params = Scrypt.Parameters {+        n = 2 ^ scryptRounds,+        r = fromIntegral scryptBlockSize,+        p = fromIntegral scryptParallelism,+        outputLength = fromIntegral scryptOutputLength+      }+++-- | Hash a password using the 'Scrypt' algorithm with the given 'ScryptParams'.+--+-- __N.B.__: If you have any doubt in your knowledge of cryptography and/or the+-- 'Scrypt' algorithm, please just use 'hashPassword'.+--+-- Advice for setting the parameters:+--+-- * Memory used is about: @(2 ^ 'scryptRounds') * 'scryptBlockSize' * 128@+-- * Increasing 'scryptBlockSize' and 'scryptRounds' will increase CPU time+--   and memory used.+-- * Increasing 'scryptParallelism' will increase CPU time. (since this+--   implementation, like most, runs the 'scryptParallelism' parameter in+--   sequence, not in parallel)+--+-- @since 2.0.0.0+hashPasswordWithParams :: MonadIO m => ScryptParams -> Password -> m (PasswordHash Scrypt)+hashPasswordWithParams params pass = liftIO $ do+    salt <- Data.Password.Internal.newSalt saltLength+    return $ hashPasswordWithSalt params salt pass+  where+    saltLength = fromIntegral $ scryptSalt params++-- | Check a 'Password' against a 'PasswordHash' 'Scrypt'.+--+-- Returns 'PasswordCheckSuccess' on success.+--+-- >>> let pass = mkPassword "foobar"+-- >>> passHash <- hashPassword pass+-- >>> checkPassword pass passHash+-- PasswordCheckSuccess+--+-- Returns 'PasswordCheckFail' if an incorrect 'Password' or 'PasswordHash' 'Scrypt' is used.+--+-- >>> let badpass = mkPassword "incorrect-password"+-- >>> checkPassword badpass passHash+-- PasswordCheckFail+--+-- This should always fail if an incorrect password is given.+--+-- prop> \(Blind badpass) -> let correctPasswordHash = hashPasswordWithSalt testParams salt "foobar" in checkPassword badpass correctPasswordHash == PasswordCheckFail+checkPassword :: Password -> PasswordHash Scrypt -> PasswordCheck+checkPassword pass (PasswordHash passHash) =+  fromMaybe PasswordCheckFail $ do+    let paramList = T.split (== '|') passHash+    guard $ length paramList == 5+    let [ scryptRoundsT,+          scryptBlockSizeT,+          scryptParallelismT,+          salt64,+          hashedKey64 ] = paramList+    scryptRounds <- readT scryptRoundsT+    scryptBlockSize <- readT scryptBlockSizeT+    scryptParallelism <- readT scryptParallelismT+    salt <- from64 salt64+    hashedKey <- from64 hashedKey64+    let scryptOutputLength = fromIntegral $ C8.length hashedKey+        producedKey = hashPasswordWithSalt' ScryptParams{..} (Salt salt) pass+    guard $ hashedKey == producedKey+    return PasswordCheckSuccess+  where+    scryptSalt = 32 -- only here because of warnings++-- | Generate a random 32-byte @scrypt@ salt+--+-- @since 2.0.0.0+newSalt :: MonadIO m => m (Salt Scrypt)+newSalt = Data.Password.Internal.newSalt 32
test/doctest/doctest.hs view
@@ -1,7 +1,14 @@--module Main (main) where+module Main where -import Test.DocTest+import Build_doctests (flags, pkgs, module_sources)+-- import Data.Foldable (traverse_)+import System.Environment.Compat (unsetEnv)+import Test.DocTest (doctest)  main :: IO ()-main = doctest ["src"]+main = do+  -- traverse_ putStrLn args+  unsetEnv "GHC_ENVIRONMENT"+  doctest args+  where+    args = flags ++ pkgs ++ module_sources
+ test/tasty/Argon2.hs view
@@ -0,0 +1,29 @@+module Argon2 where++import Test.Tasty+import Test.QuickCheck.Instances.Text ()++import Data.Password.Argon2++import Internal+++testArgon2 :: TestTree+testArgon2 = testGroup "Argon2"+  [ 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 }+  ]+  where+    testWithParams s params =+      testWithSalt s (hashPasswordWithSalt params) checkPassword+    hashFast = hashPasswordWithParams fastParams+    fastParams =+      defaultParams{+        argon2MemoryCost = 2 ^ (8 :: Int),+        argon2TimeCost = 1+      }
+ test/tasty/Bcrypt.hs view
@@ -0,0 +1,19 @@+module Bcrypt where++import Data.Text (Text)+import Test.Tasty+import Test.Tasty.QuickCheck+import Test.QuickCheck.Instances.Text ()++import Data.Password+import Data.Password.Bcrypt++import Internal (testCorrectPassword, testIncorrectPassword_, testWithSalt)+++testBcrypt :: TestTree+testBcrypt = testGroup "bcrypt"+  [ testCorrectPassword "Bcrypt (hashPassword)" (hashPasswordWithParams 4) checkPassword+  , testIncorrectPassword_ "Bcrypt (hashPassword) fail" (hashPasswordWithParams 4) checkPassword+  , testWithSalt "Bcrypt (hashPasswordWithSalt)" (hashPasswordWithSalt 4) checkPassword+  ]
+ test/tasty/Internal.hs view
@@ -0,0 +1,69 @@+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -Wno-orphans #-}+module Internal where++import Data.ByteArray (pack)+import Data.Text (Text)+import Test.Tasty+import Test.Tasty.QuickCheck+import Test.QuickCheck.Instances.Text ()++import Data.Password+++testCorrectPassword :: String+                    -> (Password -> IO (PasswordHash a))+                    -> (Password -> PasswordHash a -> PasswordCheck)+                    -> TestTree+testCorrectPassword s hashF checkF = testProperty s $+  \pass -> run10 $ do+    let pw = mkPassword pass+    hpw <- hashF pw+    return $ checkF pw hpw === PasswordCheckSuccess++testIncorrectPassword :: String+                      -> (Password -> IO (PasswordHash a))+                      -> (Password -> PasswordHash a -> PasswordCheck)+                      -> TestTree+testIncorrectPassword s hashF checkF =+    testProperty s $ testIncorrectPassword' hashF checkF++-- Similar to 'testIncorrectPassword', but exempts the comparison of+-- "" and "\NUL", since 'bcrypt' and 'PBKDF2' match those as well.+testIncorrectPassword_ :: String+                       -> (Password -> IO (PasswordHash a))+                       -> (Password -> PasswordHash a -> PasswordCheck)+                       -> TestTree+testIncorrectPassword_ s hashF checkF =+    testProperty s $ \pass pass2 ->+      not (all isEmpty [pass, pass2]) ==>+        testIncorrectPassword' hashF checkF pass pass2+  where+    isEmpty c = c `elem` ["", "\NUL"]++testIncorrectPassword' :: (Password -> IO (PasswordHash a))+                       -> (Password -> PasswordHash a -> PasswordCheck)+                       -> Text -> Text -> Property+testIncorrectPassword' hashF checkF pass pass2 = run10 $ do+    let pw = mkPassword pass+        pw2 = mkPassword pass2+        result = if pass == pass2 then PasswordCheckSuccess+                                  else PasswordCheckFail+    hpw <- hashF pw+    return $ checkF pw2 hpw === result++testWithSalt :: String+             -> (Salt a -> Password -> PasswordHash a)+             -> (Password -> PasswordHash a -> PasswordCheck)+             -> TestTree+testWithSalt s hashWithSalt checkF = testProperty s $+  \pass salt -> withMaxSuccess 10 $+    let pw = mkPassword pass+        hpw = hashWithSalt salt pw+    in checkF pw hpw === PasswordCheckSuccess++run10 :: Testable prop => IO prop -> Property+run10 = withMaxSuccess 10 . ioProperty++instance Arbitrary (Salt a) where+  arbitrary = Salt . pack <$> vector 16
+ test/tasty/PBKDF2.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}+module PBKDF2 where++import Crypto.Hash.Algorithms as Crypto (HashAlgorithm, SHA1(..), SHA256(..), SHA512(..))+import Crypto.KDF.PBKDF2 as PBKDF2+import Data.ByteString (ByteString)+#if !MIN_VERSION_base(4,13,0)+import Data.Semigroup ((<>))+#endif+import Test.Tasty+import Test.Tasty.QuickCheck+import Test.QuickCheck.Instances.ByteString ()+import Test.QuickCheck.Instances.Text ()++import Data.Password.PBKDF2++import Internal++_10k :: Num a => a+_10k = 10 * 1000++testPBKDF2 :: TestTree+testPBKDF2 = testGroup "PBKDF2"+  [ testIt "PBKDF2 (hashPassword)" $ _10k defaultParams -- This is PBKDF2_SHA512+  , testIncorrectPassword_+      "PBKDF2 (hashPassword) fail"+      (hashPasswordWithParams $ _10k defaultParams)+      checkPassword+  , testWithSalt+      "PBKDF2 (hashPasswordWithSalt)"+      (hashPasswordWithSalt $ _10k defaultParams)+      checkPassword+  , testIt "PBKDF2 (md5)"    (defaultParams{ pbkdf2Algorithm = PBKDF2_MD5, pbkdf2Iterations = 5000 })+  , testIt "PBKDF2 (sha1)"   (_10k defaultParams{ pbkdf2Algorithm = PBKDF2_SHA1 })+  , testIt "PBKDF2 (sha256)" (_10k defaultParams{ pbkdf2Algorithm = PBKDF2_SHA256 })+  , testFast Crypto.SHA1   20 PBKDF2.fastPBKDF2_SHA1+  , testFast Crypto.SHA256 32 PBKDF2.fastPBKDF2_SHA256+  , testFast Crypto.SHA512 64 PBKDF2.fastPBKDF2_SHA512+  -- Check to see if a hash with "pbkdf2:" prefixed also works+  , testCorrectPassword+      "PBKDF2 (pbkdf2:sha-...)"+      (hashPasswordWithParams $ _10k defaultParams)+      (\pass (PasswordHash hash) -> checkPassword pass . PasswordHash $ "pbkdf2:" <> hash)+  ]+  where+    testIt s params = testCorrectPassword s (hashPasswordWithParams params) checkPassword+    _10k params = params{ pbkdf2Iterations = 10 * 1000 }++testFast :: (HashAlgorithm a, Show a)+         => a+         -> Int+         -> (Parameters -> ByteString -> ByteString -> ByteString)+         -> TestTree+testFast alg i f = testProperty s $ \pass salt -> run10 $+    return $ f params pass salt ===+             PBKDF2.generate (PBKDF2.prfHMAC alg) params pass salt+  where+    params = cryptoParams i+    s = sAlg ++ " HMAC == fast_" ++ sAlg+    sAlg = show alg++cryptoParams :: Int -> PBKDF2.Parameters+cryptoParams i = PBKDF2.Parameters {+    PBKDF2.iterCounts = 5000,+    PBKDF2.outputLength = i+  }
+ test/tasty/Scrypt.hs view
@@ -0,0 +1,37 @@+module Scrypt where++import Data.Maybe (fromJust)+import Data.Text (Text)+import Data.Text.Encoding (encodeUtf8)+import Test.Tasty+import Test.Tasty.QuickCheck+import Test.QuickCheck.Instances.Text ()++import qualified Crypto.Scrypt as Scrypt+import Data.Password+import Data.Password.Scrypt++import Internal+++testScrypt :: TestTree+testScrypt = testGroup "scrypt"+  [ testCorrectPassword "Scrypt (hashPassword)" hash8Rounds checkPassword+  , testIncorrectPassword "Scrypt (hashPassword) fail" hash8Rounds checkPassword+  , testWithSalt "Scrypt (hashPasswordWithSalt)"+                 (hashPasswordWithSalt defaultParams{ scryptRounds = 8 })+                 checkPassword+  , testProperty "scrypt <-> cryptonite" $ withMaxSuccess 10 checkScrypt+  ]+  where+    hash8Rounds = hashPasswordWithParams defaultParams{ scryptRounds = 8 }++checkScrypt :: Text -> Property+checkScrypt pass = ioProperty $ do+  s@(Scrypt.Salt salt) <- Scrypt.newSalt+  let params = fromJust $ Scrypt.scryptParams 8 8 1+      Scrypt.EncryptedPass scryptHash =+        Scrypt.encryptPass params s $ Scrypt.Pass $ encodeUtf8 pass+      PasswordHash ourHash =+        hashPasswordWithSalt defaultParams{ scryptRounds = 8 } (Salt salt) $ mkPassword pass+  return $ scryptHash === encodeUtf8 ourHash
test/tasty/Spec.hs view
@@ -1,27 +1,21 @@-import Crypto.Scrypt (Salt(..))-import Data.Text.Encoding (encodeUtf8) import Test.Tasty import Test.Tasty.QuickCheck-import Test.QuickCheck.Instances.Text ()+import Test.Tasty.Runners (NumThreads(..))  import Data.Password +import Argon2+import Bcrypt+import PBKDF2+import Scrypt+ main :: IO ()-main = defaultMain $ testGroup "Password"-  [ testProperty "Pass" $ \pass ->-      unsafeShowPasswordText (mkPass pass) === pass-  , testProperty "Scrypt (hashPass)" $ \pass -> ioProperty $ do-      let pw = mkPass pass-      hpw <- hashPass pw-      return $ checkPass pw hpw === PassCheckSuccess-  , testProperty "Scrypt (hashPass) fail" $ \pass pass2 -> ioProperty $ do-      let pw = mkPass pass-          pw2 = mkPass pass2-          result = if pass == pass2 then PassCheckSuccess else PassCheckFail-      hpw <- hashPass pw-      return $ checkPass pw2 hpw === result-  , testProperty "Scrypt (hashPassWithSalt)" $ \pass salt ->-      let pw = mkPass pass-          hpw = hashPassWithSalt (Salt $ encodeUtf8 salt) pw-      in checkPass pw hpw === PassCheckSuccess-  ]+main = defaultMain $ localOption (NumThreads 1) $+  testGroup "Password"+    [ testProperty "Password" $ \pass ->+        unsafeShowPassword (mkPassword pass) === pass+    , testArgon2+    , testBcrypt+    , testPBKDF2+    , testScrypt+    ]