password 2.0.0.1 → 2.0.1.0
raw patch · 9 files changed
+44/−62 lines, 9 files
Files
- ChangeLog.md +8/−1
- password.cabal +1/−1
- src/Data/Password/Argon2.hs +2/−2
- src/Data/Password/Internal.hs +6/−2
- src/Data/Password/PBKDF2.hs +2/−2
- src/Data/Password/Scrypt.hs +2/−2
- test/tasty/Bcrypt.hs +2/−6
- test/tasty/Internal.hs +9/−30
- test/tasty/PBKDF2.hs +12/−16
ChangeLog.md view
@@ -1,5 +1,13 @@ # Changelog for password +## 2.0.1.0++- Switched checking hashes to using `Data.ByteArray.constEq`, instead of+ the default `(==)` method of `ByteString`. This is to make it more secure+ against timing attacks. [#16](https://github.com/cdepillabout/password/pull/16)+- Thanks to maralorn ([@maralorn](https://github.com/maralorn)) for bringing+ this up.+ ## 2.0.0.1 - Fixed README markdown for hackage.@@ -47,4 +55,3 @@ ## 0.1.0.0 - Initial version.-
password.cabal view
@@ -1,7 +1,7 @@ cabal-version: 1.12 name: password-version: 2.0.0.1+version: 2.0.1.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.
src/Data/Password/Argon2.hs view
@@ -73,7 +73,7 @@ import Control.Monad.IO.Class (MonadIO(liftIO)) import Crypto.Error (throwCryptoError) import Crypto.KDF.Argon2 as Argon2-import Data.ByteArray (Bytes, convert)+import Data.ByteArray (Bytes, constEq, convert) import Data.ByteString (ByteString) import Data.ByteString.Base64 (encodeBase64) import qualified Data.ByteString.Char8 as C8 (length)@@ -278,7 +278,7 @@ hashedKey <- from64 hashedKey64 let argon2OutputLength = fromIntegral $ C8.length hashedKey -- only here because of warnings producedKey = hashPasswordWithSalt' Argon2Params{..} (Salt salt) pass- guard $ hashedKey == producedKey+ guard $ hashedKey `constEq` producedKey return PasswordCheckSuccess where argon2Salt = 16 -- only here because of warnings
src/Data/Password/Internal.hs view
@@ -29,8 +29,9 @@ import Control.Monad.IO.Class (MonadIO(liftIO)) import Crypto.Random (getRandomBytes)-import Data.ByteArray (Bytes, convert)+import Data.ByteArray (Bytes, constEq, 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)@@ -98,7 +99,10 @@ -- The hashed password can be stored in a database. newtype PasswordHash a = PasswordHash { unPasswordHash :: Text- } deriving (Eq, Ord, Read, Show)+ } 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.
src/Data/Password/PBKDF2.hs view
@@ -69,7 +69,7 @@ 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.ByteArray (ByteArray, ByteArrayAccess, Bytes, constEq, convert) import Data.ByteString (ByteString) import qualified Data.ByteString.Base64 as Base64 import qualified Data.ByteString.Char8 as C8 (length)@@ -239,7 +239,7 @@ hashedKey <- from64 hashedKey64 let pbkdf2OutputLength = fromIntegral $ C8.length hashedKey producedKey = hashPasswordWithSalt' PBKDF2Params{..} (Salt salt) pass- guard $ hashedKey == producedKey+ guard $ hashedKey `constEq` producedKey return PasswordCheckSuccess where pbkdf2Salt = 16
src/Data/Password/Scrypt.hs view
@@ -64,7 +64,7 @@ import Control.Monad (guard) import Control.Monad.IO.Class (MonadIO(liftIO)) import Crypto.KDF.Scrypt as Scrypt-import Data.ByteArray (Bytes, convert)+import Data.ByteArray (Bytes, constEq, convert) import Data.ByteString (ByteString) import Data.ByteString.Base64 (encodeBase64) import qualified Data.ByteString.Char8 as C8 (length)@@ -250,7 +250,7 @@ hashedKey <- from64 hashedKey64 let scryptOutputLength = fromIntegral $ C8.length hashedKey producedKey = hashPasswordWithSalt' ScryptParams{..} (Salt salt) pass- guard $ hashedKey == producedKey+ guard $ hashedKey `constEq` producedKey return PasswordCheckSuccess where scryptSalt = 32 -- only here because of warnings
test/tasty/Bcrypt.hs view
@@ -1,19 +1,15 @@ 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)+import Internal (testCorrectPassword, testIncorrectPassword, testWithSalt) testBcrypt :: TestTree testBcrypt = testGroup "bcrypt" [ testCorrectPassword "Bcrypt (hashPassword)" (hashPasswordWithParams 4) checkPassword- , testIncorrectPassword_ "Bcrypt (hashPassword) fail" (hashPasswordWithParams 4) checkPassword+ , testIncorrectPassword "Bcrypt (hashPassword) fail" (hashPasswordWithParams 4) checkPassword , testWithSalt "Bcrypt (hashPasswordWithSalt)" (hashPasswordWithSalt 4) checkPassword ]
test/tasty/Internal.hs view
@@ -3,7 +3,6 @@ module Internal where import Data.ByteArray (pack)-import Data.Text (Text) import Test.Tasty import Test.Tasty.QuickCheck import Test.QuickCheck.Instances.Text ()@@ -16,7 +15,7 @@ -> (Password -> PasswordHash a -> PasswordCheck) -> TestTree testCorrectPassword s hashF checkF = testProperty s $- \pass -> run10 $ do+ \pass -> ioProperty $ do let pw = mkPassword pass hpw <- hashF pw return $ checkF pw hpw === PasswordCheckSuccess@@ -25,45 +24,25 @@ -> (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+testIncorrectPassword s hashF checkF = testProperty s $+ \pass pass2 -> pass /= pass2 && not (all isEmpty [pass, pass2]) ==>+ ioProperty $ do+ let pw = mkPassword pass+ pw2 = mkPassword pass2+ hpw <- hashF pw+ return $ checkF pw2 hpw === PasswordCheckFail 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 $+ \pass salt -> 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
@@ -17,44 +17,40 @@ import Internal -_10k :: Num a => a-_10k = 10 * 1000- testPBKDF2 :: TestTree testPBKDF2 = testGroup "PBKDF2"- [ testIt "PBKDF2 (hashPassword)" $ _10k defaultParams -- This is PBKDF2_SHA512- , testIncorrectPassword_+ [ testIt "PBKDF2 (hashPassword)" testParams -- This is PBKDF2_SHA512+ , testIncorrectPassword "PBKDF2 (hashPassword) fail"- (hashPasswordWithParams $ _10k defaultParams)+ (hashPasswordWithParams testParams) checkPassword , testWithSalt "PBKDF2 (hashPasswordWithSalt)"- (hashPasswordWithSalt $ _10k defaultParams)+ (hashPasswordWithSalt testParams) 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 })+ , testIt "PBKDF2 (md5)" (defaultParams{ pbkdf2Algorithm = PBKDF2_MD5, pbkdf2Iterations = 1000 })+ , testIt "PBKDF2 (sha1)" (testParams{ pbkdf2Algorithm = PBKDF2_SHA1 })+ , testIt "PBKDF2 (sha256)" (testParams{ 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)+ (hashPasswordWithParams testParams) (\pass (PasswordHash hash) -> checkPassword pass . PasswordHash $ "pbkdf2:" <> hash) ] where testIt s params = testCorrectPassword s (hashPasswordWithParams params) checkPassword- _10k params = params{ pbkdf2Iterations = 10 * 1000 }+ testParams = defaultParams{ pbkdf2Iterations = 4 * 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+testFast alg i f = testProperty s $ \pass salt ->+ f params pass salt === PBKDF2.generate (PBKDF2.prfHMAC alg) params pass salt where params = cryptoParams i s = sAlg ++ " HMAC == fast_" ++ sAlg@@ -62,6 +58,6 @@ cryptoParams :: Int -> PBKDF2.Parameters cryptoParams i = PBKDF2.Parameters {- PBKDF2.iterCounts = 5000,+ PBKDF2.iterCounts = 2000, PBKDF2.outputLength = i }