packages feed

password 2.0.0.1 → 2.0.1.0

raw patch · 9 files changed

+44/−62 lines, 9 files

Files

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   }