yesod-auth 1.4.17.3 → 1.4.18
raw patch · 6 files changed
+519/−421 lines, 6 filesnew-uploader
Files
- ChangeLog.md +4/−0
- Yesod/Auth/Email.hs +1/−1
- Yesod/Auth/Message.hs +48/−0
- Yesod/Auth/Util/PasswordStore.hs +464/−0
- Yesod/PasswordStore.hs +0/−418
- yesod-auth.cabal +2/−2
ChangeLog.md view
@@ -1,3 +1,7 @@+## 1.4.18++* Expose Yesod.Auth.Util.PasswordStore+ ## 1.4.17.3 * Some translation fixes
Yesod/Auth/Email.hs view
@@ -116,7 +116,7 @@ import qualified Yesod.Auth.Message as Msg import Yesod.Core import Yesod.Form-import qualified Yesod.PasswordStore as PS+import qualified Yesod.Auth.Util.PasswordStore as PS import Control.Applicative ((<$>), (<*>)) import qualified Crypto.Hash as H import qualified Crypto.Nonce as Nonce
Yesod/Auth/Message.hs view
@@ -19,6 +19,7 @@ , russianMessage , dutchMessage , danishMessage+ , koreanMessage ) where import Data.Monoid (mappend, (<>))@@ -784,3 +785,50 @@ danishMessage Logout = "Log ud" danishMessage LogoutTitle = "Log ud" danishMessage AuthError = "Fejl ved bekræftelse af identitet"++koreanMessage :: AuthMessage -> Text+koreanMessage NoOpenID = "OpenID ID가 없습니다"+koreanMessage LoginOpenID = "OpenID로 로그인"+koreanMessage LoginGoogle = "Google로 로그인"+koreanMessage LoginYahoo = "Yahoo로 로그인"+koreanMessage Email = "이메일"+koreanMessage UserName = "사용자 이름"+koreanMessage Password = "비밀번호"+koreanMessage CurrentPassword = "현재 비밀번호"+koreanMessage Register = "등록"+koreanMessage RegisterLong = "새 계정 등록"+koreanMessage EnterEmail = "이메일 주소를 아래에 입력하시면 확인 이메일이 발송됩니다."+koreanMessage ConfirmationEmailSentTitle = "확인 이메일을 보냈습니다"+koreanMessage (ConfirmationEmailSent email) =+ "확인 이메일을 " `mappend`+ email `mappend`+ "에 보냈습니다."+koreanMessage AddressVerified = "주소가 인증되었습니다. 새 비밀번호를 설정하세요."+koreanMessage InvalidKeyTitle = "인증키가 잘못되었습니다"+koreanMessage InvalidKey = "죄송합니다. 잘못된 인증키입니다."+koreanMessage InvalidEmailPass = "이메일 주소나 비밀번호가 잘못되었습니다"+koreanMessage BadSetPass = "비밀번호를 설정하기 위해서는 로그인해야 합니다"+koreanMessage SetPassTitle = "비밀번호 설정"+koreanMessage SetPass = "새 비밀번호 설정"+koreanMessage NewPass = "새 비밀번호"+koreanMessage ConfirmPass = "확인"+koreanMessage PassMismatch = "비밀번호가 맞지 않습니다. 다시 시도해주세요."+koreanMessage PassUpdated = "비밀번호가 업데이트 되었습니다"+koreanMessage Facebook = "Facebook으로 로그인"+koreanMessage LoginViaEmail = "이메일로"+koreanMessage InvalidLogin = "잘못된 로그인입니다"+koreanMessage NowLoggedIn = "로그인했습니다"+koreanMessage LoginTitle = "로그인"+koreanMessage PleaseProvideUsername = "사용자 이름을 입력하세요"+koreanMessage PleaseProvidePassword = "비밀번호를 입력하세요"+koreanMessage NoIdentifierProvided = "이메일 주소나 사용자 이름이 입력되어 있지 않습니다"+koreanMessage InvalidEmailAddress = "이메일 주소가 잘못되었습니다"+koreanMessage PasswordResetTitle = "비밀번호 변경"+koreanMessage ProvideIdentifier = "이메일 주소나 사용자 이름"+koreanMessage SendPasswordResetEmail = "비밀번호 재설정 이메일 보내기"+koreanMessage PasswordResetPrompt = "이메일 주소나 사용자 이름을 아래에 입력하시면 비밀번호 재설정 이메일이 발송됩니다."+koreanMessage InvalidUsernamePass = "사용자 이름이나 비밀번호가 잘못되었습니다"+koreanMessage (IdentifierNotFound ident) = ident `mappend` "는 등록되어 있지 않습니다"+koreanMessage Logout = "로그아웃"+koreanMessage LogoutTitle = "로그아웃"+koreanMessage AuthError = "인증오류"
+ Yesod/Auth/Util/PasswordStore.hs view
@@ -0,0 +1,464 @@+{-# LANGUAGE OverloadedStrings, BangPatterns #-}+{-# LANGUAGE CPP #-}+-- |+-- This is a fork of pwstore-fast, originally copyright (c) Peter Scott, 2011,+-- and released under a BSD-style licence.+--+-- Securely store hashed, salted passwords. If you need to store and verify+-- passwords, there are many wrong ways to do it, most of them all too+-- common. Some people store users' passwords in plain text. Then, when an+-- attacker manages to get their hands on this file, they have the passwords for+-- every user's account. One step up, but still wrong, is to simply hash all+-- passwords with SHA1 or something. This is vulnerable to rainbow table and+-- dictionary attacks. One step up from that is to hash the password along with+-- a unique salt value. This is vulnerable to dictionary attacks, since guessing+-- a password is very fast. The right thing to do is to use a slow hash+-- function, to add some small but significant delay, that will be negligible+-- for legitimate users but prohibitively expensive for someone trying to guess+-- passwords by brute force. That is what this library does. It iterates a+-- SHA256 hash, with a random salt, a few thousand times. This scheme is known+-- as PBKDF1, and is generally considered secure; there is nothing innovative+-- happening here.+--+-- The API here is very simple. What you store are called /password hashes/.+-- They are strings (technically, ByteStrings) that look like this:+--+-- > "sha256|14|jEWU94phx4QzNyH94Qp4CQ==|5GEw+jxP/4WLgzt9VS3Ee3nhqBlDsrKiB+rq7JfMckU="+--+-- Each password hash shows the algorithm, the strength (more on that later),+-- the salt, and the hashed-and-salted password. You store these on your server,+-- in a database, for when you need to verify a password. You make a password+-- hash with the 'makePassword' function. Here's an example:+--+-- > >>> makePassword "hunter2" 14+-- > "sha256|14|Zo4LdZGrv/HYNAUG3q8WcA==|zKjbHZoTpuPLp1lh6ATolWGIKjhXvY4TysuKvqtNFyk="+--+-- This will hash the password @\"hunter2\"@, with strength 14, which is a good+-- default value. The strength here determines how long the hashing will+-- take. When doing the hashing, we iterate the SHA256 hash function+-- @2^strength@ times, so increasing the strength by 1 makes the hashing take+-- twice as long. When computers get faster, you can bump up the strength a+-- little bit to compensate. You can strengthen existing password hashes with+-- the 'strengthenPassword' function. Note that 'makePassword' needs to generate+-- random numbers, so its return type is 'IO' 'ByteString'. If you want to avoid+-- the 'IO' monad, you can generate your own salt and pass it to+-- 'makePasswordSalt'.+--+-- Your strength value should not be less than 12, and 14 is a good default+-- value at the time of this writing, in 2013.+--+-- Once you've got your password hashes, the second big thing you need to do+-- with them is verify passwords against them. When a user gives you a password,+-- you compare it with a password hash using the 'verifyPassword' function:+--+-- > >>> verifyPassword "wrong guess" passwordHash+-- > False+-- > >>> verifyPassword "hunter2" passwordHash+-- > True+--+-- These two functions are really all you need. If you want to make existing+-- password hashes stronger, you can use 'strengthenPassword'. Just pass it an+-- existing password hash and a new strength value, and it will return a new+-- password hash with that strength value, which will match the same password as+-- the old password hash.+--+-- Note that, as of version 2.4, you can also use PBKDF2, and specify the exact+-- iteration count. This does not have a significant effect on security, but can+-- be handy for compatibility with other code.+--+-- @since 1.4.18++module Yesod.Auth.Util.PasswordStore (++ -- * Algorithms+ pbkdf1, -- :: ByteString -> Salt -> Int -> ByteString+ pbkdf2, -- :: ByteString -> Salt -> Int -> ByteString++ -- * Registering and verifying passwords+ makePassword, -- :: ByteString -> Int -> IO ByteString+ makePasswordWith, -- :: (ByteString -> Salt -> Int -> ByteString) ->+ -- ByteString -> Int -> IO ByteString+ makePasswordSalt, -- :: ByteString -> ByteString -> Int -> ByteString+ makePasswordSaltWith, -- :: (ByteString -> Salt -> Int -> ByteString) ->+ -- ByteString -> Salt -> Int -> ByteString+ verifyPassword, -- :: ByteString -> ByteString -> Bool+ verifyPasswordWith, -- :: (ByteString -> Salt -> Int -> ByteString) ->+ -- (Int -> Int) -> ByteString -> ByteString -> Bool++ -- * Updating password hash strength+ strengthenPassword, -- :: ByteString -> Int -> ByteString+ passwordStrength, -- :: ByteString -> Int++ -- * Utilities+ Salt,+ isPasswordFormatValid, -- :: ByteString -> Bool+ genSaltIO, -- :: IO Salt+ genSaltRandom, -- :: (RandomGen b) => b -> (Salt, b)+ makeSalt, -- :: ByteString -> Salt+ exportSalt, -- :: Salt -> ByteString+ importSalt -- :: ByteString -> Salt+ ) where++import qualified Crypto.MAC.HMAC as CH+import qualified Crypto.Hash as CH+import qualified Data.ByteString.Char8 as B+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BL+import qualified Data.Binary as Binary+import Control.Monad+import Control.Monad.ST+import Data.STRef+import Data.Bits+import Data.ByteString.Char8 (ByteString)+import Data.ByteString.Base64 (encode, decodeLenient)+import System.IO+import System.Random+import Data.Maybe+import qualified Control.Exception+import Data.ByteArray (convert)++---------------------+-- Cryptographic base+---------------------++-- | PBKDF1 key-derivation function. Takes a password, a 'Salt', and a number of+-- iterations. The number of iterations should be at least 1000, and probably+-- more. 5000 is a reasonable number, computing almost instantaneously. This+-- will give a 32-byte 'ByteString' as output. Both the salt and this 32-byte+-- key should be stored in the password file. When a user wishes to authenticate+-- a password, just pass it and the salt to this function, and see if the output+-- matches.+--+-- @since 1.4.18+--+pbkdf1 :: ByteString -> Salt -> Int -> ByteString+pbkdf1 password (SaltBS salt) iter = hashRounds first_hash (iter + 1)+ where+ first_hash =+ convert $+ ((CH.hashFinalize $ CH.hashInit `CH.hashUpdate` password `CH.hashUpdate` salt) :: CH.Digest CH.SHA256)+++-- | Hash a 'ByteString' for a given number of rounds. The number of rounds is 0+-- or more. If the number of rounds specified is 0, the ByteString will be+-- returned unmodified.+hashRounds :: ByteString -> Int -> ByteString+hashRounds (!bs) 0 = bs+hashRounds bs rounds = hashRounds (convert (CH.hash bs :: CH.Digest CH.SHA256)) (rounds - 1)++-- | Computes the hmacSHA256 of the given message, with the given 'Salt'.+hmacSHA256 :: ByteString+ -- ^ The secret (the salt)+ -> ByteString+ -- ^ The clear-text message+ -> ByteString+ -- ^ The encoded message+hmacSHA256 secret msg =+ convert (CH.hmacGetDigest (CH.hmac secret msg) :: CH.Digest CH.SHA256)++-- | PBKDF2 key-derivation function.+-- For details see @http://tools.ietf.org/html/rfc2898@.+-- @32@ is the most common digest size for @SHA256@, and is+-- what the algorithm internally uses.+-- @HMAC+SHA256@ is used as @PRF@, because @HMAC+SHA1@ is considered too weak.+--+-- @since 1.4.18+--+pbkdf2 :: ByteString -> Salt -> Int -> ByteString+pbkdf2 password (SaltBS salt) c =+ let hLen = 32+ dkLen = hLen in go hLen dkLen+ where+ go hLen dkLen | dkLen > (2^(32 :: Int) - 1) * hLen = error "Derived key too long."+ | otherwise =+ let !l = ceiling ((fromIntegral dkLen / fromIntegral hLen) :: Double)+ !r = dkLen - (l - 1) * hLen+ chunks = [f i | i <- [1 .. l]]+ in (B.concat . init $ chunks) `B.append` B.take r (last chunks)++ -- The @f@ function, as defined in the spec.+ -- It calls 'u' under the hood.+ f :: Int -> ByteString+ f i = let !u1 = hmacSHA256 password (salt `B.append` int i)+ -- Using the ST Monad, for maximum performance.+ in runST $ do+ u <- newSTRef u1+ accum <- newSTRef u1+ forM_ [2 .. c] $ \_ -> do+ modifySTRef' u (hmacSHA256 password)+ currentU <- readSTRef u+ modifySTRef' accum (`xor'` currentU)+ readSTRef accum++ -- int(i), as defined in the spec.+ int :: Int -> ByteString+ int i = let str = BL.unpack . Binary.encode $ i+ in BS.pack $ drop (length str - 4) str++ -- | A convenience function to XOR two 'ByteString' together.+ xor' :: ByteString -> ByteString -> ByteString+ xor' !b1 !b2 = BS.pack $ BS.zipWith xor b1 b2++-- | Generate a 'Salt' from 128 bits of data from @\/dev\/urandom@, with the+-- system RNG as a fallback. This is the function used to generate salts by+-- 'makePassword'.+--+-- @since 1.4.18+--+genSaltIO :: IO Salt+genSaltIO =+ Control.Exception.catch genSaltDevURandom def+ where+ def :: IOError -> IO Salt+ def _ = genSaltSysRandom++-- | Generate a 'Salt' from @\/dev\/urandom@.+genSaltDevURandom :: IO Salt+genSaltDevURandom = withFile "/dev/urandom" ReadMode $ \h -> do+ rawSalt <- B.hGet h 16+ return $ makeSalt rawSalt++-- | Generate a 'Salt' from 'System.Random'.+genSaltSysRandom :: IO Salt+genSaltSysRandom = randomChars >>= return . makeSalt . B.pack+ where randomChars = sequence $ replicate 16 $ randomRIO ('\NUL', '\255')++-----------------------+-- Password hash format+-----------------------++-- Format: "sha256|strength|salt|hash", where strength is an unsigned int, salt+-- is a base64-encoded 16-byte random number, and hash is a base64-encoded hash+-- value.++-- | Try to parse a password hash.+readPwHash :: ByteString -> Maybe (Int, Salt, ByteString)+readPwHash pw | length broken /= 4+ || algorithm /= "sha256"+ || B.length hash /= 44 = Nothing+ | otherwise = case B.readInt strBS of+ Just (strength, _) -> Just (strength, SaltBS salt, hash)+ Nothing -> Nothing+ where broken = B.split '|' pw+ [algorithm, strBS, salt, hash] = broken++-- | Encode a password hash, from a @(strength, salt, hash)@ tuple, where+-- strength is an 'Int', and both @salt@ and @hash@ are base64-encoded+-- 'ByteString's.+writePwHash :: (Int, Salt, ByteString) -> ByteString+writePwHash (strength, SaltBS salt, hash) =+ B.intercalate "|" ["sha256", B.pack (show strength), salt, hash]++-----------------+-- High level API+-----------------++-- | Hash a password with a given strength (14 is a good default). The output of+-- this function can be written directly to a password file or+-- database. Generates a salt using high-quality randomness from+-- @\/dev\/urandom@ or (if that is not available, for example on Windows)+-- 'System.Random', which is included in the hashed output.+--+-- @since 1.4.18+--+makePassword :: ByteString -> Int -> IO ByteString+makePassword = makePasswordWith pbkdf1++-- | A generic version of 'makePassword', which allow the user+-- to choose the algorithm to use.+--+-- >>> makePasswordWith pbkdf1 "password" 14+--+-- @since 1.4.18+--+makePasswordWith :: (ByteString -> Salt -> Int -> ByteString)+ -- ^ The algorithm to use (e.g. pbkdf1)+ -> ByteString+ -- ^ The password to encrypt+ -> Int+ -- ^ log2 of the number of iterations+ -> IO ByteString+makePasswordWith algorithm password strength = do+ salt <- genSaltIO+ return $ makePasswordSaltWith algorithm (2^) password salt strength++-- | A generic version of 'makePasswordSalt', meant to give the user+-- the maximum control over the generation parameters.+-- Note that, unlike 'makePasswordWith', this function takes the @raw@+-- number of iterations. This means the user will need to specify a+-- sensible value, typically @10000@ or @20000@.+--+-- @since 1.4.18+--+makePasswordSaltWith :: (ByteString -> Salt -> Int -> ByteString)+ -- ^ A function modeling an algorithm (e.g. 'pbkdf1')+ -> (Int -> Int)+ -- ^ A function to modify the strength+ -> ByteString+ -- ^ A password, given as clear text+ -> Salt+ -- ^ A hash 'Salt'+ -> Int+ -- ^ The password strength (e.g. @10000, 20000, etc.@)+ -> ByteString+makePasswordSaltWith algorithm strengthModifier pwd salt strength = writePwHash (strength, salt, hash)+ where hash = encode $ algorithm pwd salt (strengthModifier strength)++-- | Hash a password with a given strength (14 is a good default), using a given+-- salt. The output of this function can be written directly to a password file+-- or database. Example:+--+-- > >>> makePasswordSalt "hunter2" (makeSalt "72cd18b5ebfe6e96") 14+-- > "sha256|14|NzJjZDE4YjVlYmZlNmU5Ng==|yuiNrZW3KHX+pd0sWy9NTTsy5Yopmtx4UYscItSsoxc="+--+-- @since 1.4.18+--+makePasswordSalt :: ByteString -> Salt -> Int -> ByteString+makePasswordSalt = makePasswordSaltWith pbkdf1 (2^)++-- | 'verifyPasswordWith' @algorithm userInput pwHash@ verifies+-- the password @userInput@ given by the user against the stored password+-- hash @pwHash@, with the hashing algorithm @algorithm@. Returns 'True' if the+-- given password is correct, and 'False' if it is not.+-- This function allows the programmer to specify the algorithm to use,+-- e.g. 'pbkdf1' or 'pbkdf2'.+-- Note: If you want to verify a password previously generated with+-- 'makePasswordSaltWith', but without modifying the number of iterations,+-- you can do:+--+-- > >>> verifyPasswordWith pbkdf2 id "hunter2" "sha256..."+-- > True+--+-- @since 1.4.18+--+verifyPasswordWith :: (ByteString -> Salt -> Int -> ByteString)+ -- ^ A function modeling an algorithm (e.g. pbkdf1)+ -> (Int -> Int)+ -- ^ A function to modify the strength+ -> ByteString+ -- ^ User password+ -> ByteString+ -- ^ The generated hash (e.g. sha256|14...)+ -> Bool+verifyPasswordWith algorithm strengthModifier userInput pwHash =+ case readPwHash pwHash of+ Nothing -> False+ Just (strength, salt, goodHash) ->+ encode (algorithm userInput salt (strengthModifier strength)) == goodHash++-- | Like 'verifyPasswordWith', but uses 'pbkdf1' as algorithm.+--+-- @since 1.4.18+--+verifyPassword :: ByteString -> ByteString -> Bool+verifyPassword = verifyPasswordWith pbkdf1 (2^)++-- | Try to strengthen a password hash, by hashing it some more+-- times. @'strengthenPassword' pwHash new_strength@ will return a new password+-- hash with strength at least @new_strength@. If the password hash already has+-- strength greater than or equal to @new_strength@, then it is returned+-- unmodified. If the password hash is invalid and does not parse, it will be+-- returned without comment.+--+-- This function can be used to periodically update your password database when+-- computers get faster, in order to keep up with Moore's law. This isn't hugely+-- important, but it's a good idea.+--+-- @since 1.4.18+--+strengthenPassword :: ByteString -> Int -> ByteString+strengthenPassword pwHash newstr =+ case readPwHash pwHash of+ Nothing -> pwHash+ Just (oldstr, salt, hashB64) ->+ if oldstr < newstr then+ writePwHash (newstr, salt, newHash)+ else+ pwHash+ where newHash = encode $ hashRounds hash extraRounds+ extraRounds = (2^newstr) - (2^oldstr)+ hash = decodeLenient hashB64++-- | Return the strength of a password hash.+--+-- @since 1.4.18+--+passwordStrength :: ByteString -> Int+passwordStrength pwHash = case readPwHash pwHash of+ Nothing -> 0+ Just (strength, _, _) -> strength++------------+-- Utilities+------------++-- | A salt is a unique random value which is stored as part of the password+-- hash. You can generate a salt with 'genSaltIO' or 'genSaltRandom', or if you+-- really know what you're doing, you can create them from your own ByteString+-- values with 'makeSalt'.+--+-- @since 1.4.18+--+newtype Salt = SaltBS ByteString+ deriving (Show, Eq, Ord)++-- | Create a 'Salt' from a 'ByteString'. The input must be at least 8+-- characters, and can contain arbitrary bytes. Most users will not need to use+-- this function.+--+-- @since 1.4.18+--+makeSalt :: ByteString -> Salt+makeSalt = SaltBS . encode . check_length+ where check_length salt | B.length salt < 8 =+ error "Salt too short. Minimum length is 8 characters."+ | otherwise = salt++-- | Convert a 'Salt' into a 'ByteString'. The resulting 'ByteString' will be+-- base64-encoded. Most users will not need to use this function.+--+-- @since 1.4.18+--+exportSalt :: Salt -> ByteString+exportSalt (SaltBS bs) = bs++-- | Convert a raw 'ByteString' into a 'Salt'.+-- Use this function with caution, since using a weak salt will result in a+-- weak password.+--+-- @since 1.4.18+--+importSalt :: ByteString -> Salt+importSalt = SaltBS++-- | Is the format of a password hash valid? Attempts to parse a given password+-- hash. Returns 'True' if it parses correctly, and 'False' otherwise.+--+-- @since 1.4.18+--+isPasswordFormatValid :: ByteString -> Bool+isPasswordFormatValid = isJust . readPwHash++-- | Generate a 'Salt' with 128 bits of data taken from a given random number+-- generator. Returns the salt and the updated random number generator. This is+-- meant to be used with 'makePasswordSalt' by people who would prefer to either+-- use their own random number generator or avoid the 'IO' monad.+--+-- @since 1.4.18+--+genSaltRandom :: (RandomGen b) => b -> (Salt, b)+genSaltRandom gen = (salt, newgen)+ where rands _ 0 = []+ rands g n = (a, g') : rands g' (n-1 :: Int)+ where (a, g') = randomR ('\NUL', '\255') g+ salt = makeSalt $ B.pack $ map fst (rands gen 16)+ newgen = snd $ last (rands gen 16)++#if !MIN_VERSION_base(4, 6, 0)+-- | Strict version of 'modifySTRef'+modifySTRef' :: STRef s a -> (a -> a) -> ST s ()+modifySTRef' ref f = do+ x <- readSTRef ref+ let x' = f x+ x' `seq` writeSTRef ref x'+#endif
− Yesod/PasswordStore.hs
@@ -1,418 +0,0 @@-{-# LANGUAGE OverloadedStrings, BangPatterns #-}-{-# LANGUAGE CPP #-}--- |--- Module : Crypto.PasswordStore--- Copyright : (c) Peter Scott, 2011--- License : BSD-style------ Maintainer : pjscott@iastate.edu--- Stability : experimental--- Portability : portable------ Securely store hashed, salted passwords. If you need to store and verify--- passwords, there are many wrong ways to do it, most of them all too--- common. Some people store users' passwords in plain text. Then, when an--- attacker manages to get their hands on this file, they have the passwords for--- every user's account. One step up, but still wrong, is to simply hash all--- passwords with SHA1 or something. This is vulnerable to rainbow table and--- dictionary attacks. One step up from that is to hash the password along with--- a unique salt value. This is vulnerable to dictionary attacks, since guessing--- a password is very fast. The right thing to do is to use a slow hash--- function, to add some small but significant delay, that will be negligible--- for legitimate users but prohibitively expensive for someone trying to guess--- passwords by brute force. That is what this library does. It iterates a--- SHA256 hash, with a random salt, a few thousand times. This scheme is known--- as PBKDF1, and is generally considered secure; there is nothing innovative--- happening here.------ The API here is very simple. What you store are called /password hashes/.--- They are strings (technically, ByteStrings) that look like this:------ > "sha256|14|jEWU94phx4QzNyH94Qp4CQ==|5GEw+jxP/4WLgzt9VS3Ee3nhqBlDsrKiB+rq7JfMckU="------ Each password hash shows the algorithm, the strength (more on that later),--- the salt, and the hashed-and-salted password. You store these on your server,--- in a database, for when you need to verify a password. You make a password--- hash with the 'makePassword' function. Here's an example:------ > >>> makePassword "hunter2" 14--- > "sha256|14|Zo4LdZGrv/HYNAUG3q8WcA==|zKjbHZoTpuPLp1lh6ATolWGIKjhXvY4TysuKvqtNFyk="------ This will hash the password @\"hunter2\"@, with strength 14, which is a good--- default value. The strength here determines how long the hashing will--- take. When doing the hashing, we iterate the SHA256 hash function--- @2^strength@ times, so increasing the strength by 1 makes the hashing take--- twice as long. When computers get faster, you can bump up the strength a--- little bit to compensate. You can strengthen existing password hashes with--- the 'strengthenPassword' function. Note that 'makePassword' needs to generate--- random numbers, so its return type is 'IO' 'ByteString'. If you want to avoid--- the 'IO' monad, you can generate your own salt and pass it to--- 'makePasswordSalt'.------ Your strength value should not be less than 12, and 14 is a good default--- value at the time of this writing, in 2013.------ Once you've got your password hashes, the second big thing you need to do--- with them is verify passwords against them. When a user gives you a password,--- you compare it with a password hash using the 'verifyPassword' function:------ > >>> verifyPassword "wrong guess" passwordHash--- > False--- > >>> verifyPassword "hunter2" passwordHash--- > True------ These two functions are really all you need. If you want to make existing--- password hashes stronger, you can use 'strengthenPassword'. Just pass it an--- existing password hash and a new strength value, and it will return a new--- password hash with that strength value, which will match the same password as--- the old password hash.------ Note that, as of version 2.4, you can also use PBKDF2, and specify the exact--- iteration count. This does not have a significant effect on security, but can--- be handy for compatibility with other code.--module Yesod.PasswordStore (-- -- * Algorithms- pbkdf1, -- :: ByteString -> Salt -> Int -> ByteString- pbkdf2, -- :: ByteString -> Salt -> Int -> ByteString-- -- * Registering and verifying passwords- makePassword, -- :: ByteString -> Int -> IO ByteString- makePasswordWith, -- :: (ByteString -> Salt -> Int -> ByteString) ->- -- ByteString -> Int -> IO ByteString- makePasswordSalt, -- :: ByteString -> ByteString -> Int -> ByteString- makePasswordSaltWith, -- :: (ByteString -> Salt -> Int -> ByteString) ->- -- ByteString -> Salt -> Int -> ByteString- verifyPassword, -- :: ByteString -> ByteString -> Bool- verifyPasswordWith, -- :: (ByteString -> Salt -> Int -> ByteString) ->- -- (Int -> Int) -> ByteString -> ByteString -> Bool-- -- * Updating password hash strength- strengthenPassword, -- :: ByteString -> Int -> ByteString- passwordStrength, -- :: ByteString -> Int-- -- * Utilities- Salt,- isPasswordFormatValid, -- :: ByteString -> Bool- genSaltIO, -- :: IO Salt- genSaltRandom, -- :: (RandomGen b) => b -> (Salt, b)- makeSalt, -- :: ByteString -> Salt- exportSalt, -- :: Salt -> ByteString- importSalt -- :: ByteString -> Salt- ) where--import qualified Crypto.MAC.HMAC as CH-import qualified Crypto.Hash as CH-import qualified Data.ByteString.Char8 as B-import qualified Data.ByteString as BS-import qualified Data.ByteString.Lazy as BL-import qualified Data.Binary as Binary-import Control.Monad-import Control.Monad.ST-import Data.STRef-import Data.Bits-import Data.ByteString.Char8 (ByteString)-import Data.ByteString.Base64 (encode, decodeLenient)-import System.IO-import System.Random-import Data.Maybe-import qualified Control.Exception-import Data.ByteArray (convert)-------------------------- Cryptographic base-------------------------- | PBKDF1 key-derivation function. Takes a password, a 'Salt', and a number of--- iterations. The number of iterations should be at least 1000, and probably--- more. 5000 is a reasonable number, computing almost instantaneously. This--- will give a 32-byte 'ByteString' as output. Both the salt and this 32-byte--- key should be stored in the password file. When a user wishes to authenticate--- a password, just pass it and the salt to this function, and see if the output--- matches.-pbkdf1 :: ByteString -> Salt -> Int -> ByteString-pbkdf1 password (SaltBS salt) iter = hashRounds first_hash (iter + 1)- where- first_hash =- convert $- ((CH.hashFinalize $ CH.hashInit `CH.hashUpdate` password `CH.hashUpdate` salt) :: CH.Digest CH.SHA256)----- | Hash a 'ByteString' for a given number of rounds. The number of rounds is 0--- or more. If the number of rounds specified is 0, the ByteString will be--- returned unmodified.-hashRounds :: ByteString -> Int -> ByteString-hashRounds (!bs) 0 = bs-hashRounds bs rounds = hashRounds (convert (CH.hash bs :: CH.Digest CH.SHA256)) (rounds - 1)---- | Computes the hmacSHA256 of the given message, with the given 'Salt'.-hmacSHA256 :: ByteString- -- ^ The secret (the salt)- -> ByteString- -- ^ The clear-text message- -> ByteString- -- ^ The encoded message-hmacSHA256 secret msg =- convert (CH.hmacGetDigest (CH.hmac secret msg) :: CH.Digest CH.SHA256)---- | PBKDF2 key-derivation function.--- For details see @http://tools.ietf.org/html/rfc2898@.--- @32@ is the most common digest size for @SHA256@, and is--- what the algorithm internally uses.--- @HMAC+SHA256@ is used as @PRF@, because @HMAC+SHA1@ is considered too weak.-pbkdf2 :: ByteString -> Salt -> Int -> ByteString-pbkdf2 password (SaltBS salt) c =- let hLen = 32- dkLen = hLen in go hLen dkLen- where- go hLen dkLen | dkLen > (2^(32 :: Int) - 1) * hLen = error "Derived key too long."- | otherwise =- let !l = ceiling ((fromIntegral dkLen / fromIntegral hLen) :: Double)- !r = dkLen - (l - 1) * hLen- chunks = [f i | i <- [1 .. l]]- in (B.concat . init $ chunks) `B.append` B.take r (last chunks)-- -- The @f@ function, as defined in the spec.- -- It calls 'u' under the hood.- f :: Int -> ByteString- f i = let !u1 = hmacSHA256 password (salt `B.append` int i)- -- Using the ST Monad, for maximum performance.- in runST $ do- u <- newSTRef u1- accum <- newSTRef u1- forM_ [2 .. c] $ \_ -> do- modifySTRef' u (hmacSHA256 password)- currentU <- readSTRef u- modifySTRef' accum (`xor'` currentU)- readSTRef accum-- -- int(i), as defined in the spec.- int :: Int -> ByteString- int i = let str = BL.unpack . Binary.encode $ i- in BS.pack $ drop (length str - 4) str-- -- | A convenience function to XOR two 'ByteString' together.- xor' :: ByteString -> ByteString -> ByteString- xor' !b1 !b2 = BS.pack $ BS.zipWith xor b1 b2---- | Generate a 'Salt' from 128 bits of data from @\/dev\/urandom@, with the--- system RNG as a fallback. This is the function used to generate salts by--- 'makePassword'.-genSaltIO :: IO Salt-genSaltIO =- Control.Exception.catch genSaltDevURandom def- where- def :: IOError -> IO Salt- def _ = genSaltSysRandom---- | Generate a 'Salt' from @\/dev\/urandom@.-genSaltDevURandom :: IO Salt-genSaltDevURandom = withFile "/dev/urandom" ReadMode $ \h -> do- rawSalt <- B.hGet h 16- return $ makeSalt rawSalt---- | Generate a 'Salt' from 'System.Random'.-genSaltSysRandom :: IO Salt-genSaltSysRandom = randomChars >>= return . makeSalt . B.pack- where randomChars = sequence $ replicate 16 $ randomRIO ('\NUL', '\255')---------------------------- Password hash format---------------------------- Format: "sha256|strength|salt|hash", where strength is an unsigned int, salt--- is a base64-encoded 16-byte random number, and hash is a base64-encoded hash--- value.---- | Try to parse a password hash.-readPwHash :: ByteString -> Maybe (Int, Salt, ByteString)-readPwHash pw | length broken /= 4- || algorithm /= "sha256"- || B.length hash /= 44 = Nothing- | otherwise = case B.readInt strBS of- Just (strength, _) -> Just (strength, SaltBS salt, hash)- Nothing -> Nothing- where broken = B.split '|' pw- [algorithm, strBS, salt, hash] = broken---- | Encode a password hash, from a @(strength, salt, hash)@ tuple, where--- strength is an 'Int', and both @salt@ and @hash@ are base64-encoded--- 'ByteString's.-writePwHash :: (Int, Salt, ByteString) -> ByteString-writePwHash (strength, SaltBS salt, hash) =- B.intercalate "|" ["sha256", B.pack (show strength), salt, hash]---------------------- High level API---------------------- | Hash a password with a given strength (14 is a good default). The output of--- this function can be written directly to a password file or--- database. Generates a salt using high-quality randomness from--- @\/dev\/urandom@ or (if that is not available, for example on Windows)--- 'System.Random', which is included in the hashed output.-makePassword :: ByteString -> Int -> IO ByteString-makePassword = makePasswordWith pbkdf1---- | A generic version of 'makePassword', which allow the user--- to choose the algorithm to use.------ >>> makePasswordWith pbkdf1 "password" 14----makePasswordWith :: (ByteString -> Salt -> Int -> ByteString)- -- ^ The algorithm to use (e.g. pbkdf1)- -> ByteString- -- ^ The password to encrypt- -> Int- -- ^ log2 of the number of iterations- -> IO ByteString-makePasswordWith algorithm password strength = do- salt <- genSaltIO- return $ makePasswordSaltWith algorithm (2^) password salt strength---- | A generic version of 'makePasswordSalt', meant to give the user--- the maximum control over the generation parameters.--- Note that, unlike 'makePasswordWith', this function takes the @raw@--- number of iterations. This means the user will need to specify a--- sensible value, typically @10000@ or @20000@.-makePasswordSaltWith :: (ByteString -> Salt -> Int -> ByteString)- -- ^ A function modeling an algorithm (e.g. 'pbkdf1')- -> (Int -> Int)- -- ^ A function to modify the strength- -> ByteString- -- ^ A password, given as clear text- -> Salt- -- ^ A hash 'Salt'- -> Int- -- ^ The password strength (e.g. @10000, 20000, etc.@)- -> ByteString-makePasswordSaltWith algorithm strengthModifier pwd salt strength = writePwHash (strength, salt, hash)- where hash = encode $ algorithm pwd salt (strengthModifier strength)---- | Hash a password with a given strength (14 is a good default), using a given--- salt. The output of this function can be written directly to a password file--- or database. Example:------ > >>> makePasswordSalt "hunter2" (makeSalt "72cd18b5ebfe6e96") 14--- > "sha256|14|NzJjZDE4YjVlYmZlNmU5Ng==|yuiNrZW3KHX+pd0sWy9NTTsy5Yopmtx4UYscItSsoxc="-makePasswordSalt :: ByteString -> Salt -> Int -> ByteString-makePasswordSalt = makePasswordSaltWith pbkdf1 (2^)---- | 'verifyPasswordWith' @algorithm userInput pwHash@ verifies--- the password @userInput@ given by the user against the stored password--- hash @pwHash@, with the hashing algorithm @algorithm@. Returns 'True' if the--- given password is correct, and 'False' if it is not.--- This function allows the programmer to specify the algorithm to use,--- e.g. 'pbkdf1' or 'pbkdf2'.--- Note: If you want to verify a password previously generated with--- 'makePasswordSaltWith', but without modifying the number of iterations,--- you can do:------ > >>> verifyPasswordWith pbkdf2 id "hunter2" "sha256..."--- > True----verifyPasswordWith :: (ByteString -> Salt -> Int -> ByteString)- -- ^ A function modeling an algorithm (e.g. pbkdf1)- -> (Int -> Int)- -- ^ A function to modify the strength- -> ByteString- -- ^ User password- -> ByteString- -- ^ The generated hash (e.g. sha256|14...)- -> Bool-verifyPasswordWith algorithm strengthModifier userInput pwHash =- case readPwHash pwHash of- Nothing -> False- Just (strength, salt, goodHash) ->- encode (algorithm userInput salt (strengthModifier strength)) == goodHash---- | Like 'verifyPasswordWith', but uses 'pbkdf1' as algorithm.-verifyPassword :: ByteString -> ByteString -> Bool-verifyPassword = verifyPasswordWith pbkdf1 (2^)---- | Try to strengthen a password hash, by hashing it some more--- times. @'strengthenPassword' pwHash new_strength@ will return a new password--- hash with strength at least @new_strength@. If the password hash already has--- strength greater than or equal to @new_strength@, then it is returned--- unmodified. If the password hash is invalid and does not parse, it will be--- returned without comment.------ This function can be used to periodically update your password database when--- computers get faster, in order to keep up with Moore's law. This isn't hugely--- important, but it's a good idea.-strengthenPassword :: ByteString -> Int -> ByteString-strengthenPassword pwHash newstr =- case readPwHash pwHash of- Nothing -> pwHash- Just (oldstr, salt, hashB64) ->- if oldstr < newstr then- writePwHash (newstr, salt, newHash)- else- pwHash- where newHash = encode $ hashRounds hash extraRounds- extraRounds = (2^newstr) - (2^oldstr)- hash = decodeLenient hashB64---- | Return the strength of a password hash.-passwordStrength :: ByteString -> Int-passwordStrength pwHash = case readPwHash pwHash of- Nothing -> 0- Just (strength, _, _) -> strength----------------- Utilities----------------- | A salt is a unique random value which is stored as part of the password--- hash. You can generate a salt with 'genSaltIO' or 'genSaltRandom', or if you--- really know what you're doing, you can create them from your own ByteString--- values with 'makeSalt'.-newtype Salt = SaltBS ByteString- deriving (Show, Eq, Ord)---- | Create a 'Salt' from a 'ByteString'. The input must be at least 8--- characters, and can contain arbitrary bytes. Most users will not need to use--- this function.-makeSalt :: ByteString -> Salt-makeSalt = SaltBS . encode . check_length- where check_length salt | B.length salt < 8 =- error "Salt too short. Minimum length is 8 characters."- | otherwise = salt---- | Convert a 'Salt' into a 'ByteString'. The resulting 'ByteString' will be--- base64-encoded. Most users will not need to use this function.-exportSalt :: Salt -> ByteString-exportSalt (SaltBS bs) = bs---- | Convert a raw 'ByteString' into a 'Salt'.--- Use this function with caution, since using a weak salt will result in a--- weak password.-importSalt :: ByteString -> Salt-importSalt = SaltBS---- | Is the format of a password hash valid? Attempts to parse a given password--- hash. Returns 'True' if it parses correctly, and 'False' otherwise.-isPasswordFormatValid :: ByteString -> Bool-isPasswordFormatValid = isJust . readPwHash---- | Generate a 'Salt' with 128 bits of data taken from a given random number--- generator. Returns the salt and the updated random number generator. This is--- meant to be used with 'makePasswordSalt' by people who would prefer to either--- use their own random number generator or avoid the 'IO' monad.-genSaltRandom :: (RandomGen b) => b -> (Salt, b)-genSaltRandom gen = (salt, newgen)- where rands _ 0 = []- rands g n = (a, g') : rands g' (n-1 :: Int)- where (a, g') = randomR ('\NUL', '\255') g- salt = makeSalt $ B.pack $ map fst (rands gen 16)- newgen = snd $ last (rands gen 16)--#if !MIN_VERSION_base(4, 6, 0)--- | Strict version of 'modifySTRef'-modifySTRef' :: STRef s a -> (a -> a) -> ST s ()-modifySTRef' ref f = do- x <- readSTRef ref- let x' = f x- x' `seq` writeSTRef ref x'-#endif
yesod-auth.cabal view
@@ -1,5 +1,5 @@ name: yesod-auth-version: 1.4.17.3+version: 1.4.18 license: MIT license-file: LICENSE author: Michael Snoyman, Patrick Brisbin@@ -77,8 +77,8 @@ Yesod.Auth.GoogleEmail Yesod.Auth.GoogleEmail2 Yesod.Auth.Hardcoded+ Yesod.Auth.Util.PasswordStore other-modules: Yesod.Auth.Routes- Yesod.PasswordStore ghc-options: -Wall source-repository head