Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync versions #16

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 2 additions & 23 deletions pwstore-fast/Crypto/PasswordStore.hs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ import qualified Data.ByteString.Lazy as BL
import qualified Data.Binary as Binary
import Control.Monad
import Control.Monad.ST
import Data.Byteable (Byteable, toBytes, constEqBytes)
import Data.Byteable (toBytes, constEqBytes)
import Data.STRef
import Data.Bits
import Data.ByteString.Char8 (ByteString)
Expand All @@ -121,10 +121,6 @@ import System.IO
import System.Random
import Data.Maybe
import qualified Control.Exception
import Data.Char
import Data.List
import Data.Function
import qualified Data.Foldable as FL

---------------------
-- Cryptographic base
Expand Down Expand Up @@ -168,7 +164,7 @@ pbkdf2 password (SaltBS salt) c =
let hLen = 32
dkLen = hLen in go hLen dkLen
where
go hLen dkLen | dkLen > (2^32 - 1) * hLen = error "Derived key too long."
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
Expand Down Expand Up @@ -301,9 +297,6 @@ makePasswordSaltWith algorithm strengthModifier pwd salt strength = writePwHash
makePasswordSalt :: ByteString -> Salt -> Int -> ByteString
makePasswordSalt = makePasswordSaltWith pbkdf1 (2^)

instance Byteable [Char] where
toBytes = B.pack

-- | '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
Expand Down Expand Up @@ -421,17 +414,3 @@ modifySTRef' ref f = do
let x' = f x
x' `seq` writeSTRef ref x'
#endif

#if MIN_VERSION_bytestring(0, 10, 0)
toStrict :: BL.ByteString -> BS.ByteString
toStrict = BL.toStrict

fromStrict :: BS.ByteString -> BL.ByteString
fromStrict = BL.fromStrict
#else
toStrict :: BL.ByteString -> BS.ByteString
toStrict = BS.concat . BL.toChunks

fromStrict :: BS.ByteString -> BL.ByteString
fromStrict = BL.fromChunks . return
#endif
112 changes: 91 additions & 21 deletions pwstore-purehaskell/Crypto/PasswordStore.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{-# LANGUAGE OverloadedStrings, ScopedTypeVariables, FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings, FlexibleInstances #-}
-- |
-- Module : Crypto.PasswordStore
-- Copyright : (c) Peter Scott, 2011
Expand Down Expand Up @@ -67,12 +67,24 @@
-- password hash with that strength value, which will match the same password as
-- the old password hash.
--
-- For similarity with "pwstore-fast", generic versions of the algorithms
-- are exposed, although 'pbkdf2' is not (yet) implemented here.

module Crypto.PasswordStore (

-- * Algorithms
pbkdf1, -- :: 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
Expand All @@ -84,23 +96,20 @@ module Crypto.PasswordStore (
genSaltIO, -- :: IO Salt
genSaltRandom, -- :: (RandomGen b) => b -> (Salt, b)
makeSalt, -- :: ByteString -> Salt
exportSalt -- :: Salt -> ByteString
exportSalt, -- :: Salt -> ByteString
importSalt -- :: ByteString -> Salt
) where

import qualified Data.Digest.Pure.SHA as H
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy.Char8 as L
import Data.ByteString.Char8 (ByteString)
import Data.ByteString.Base64 (encode, decodeLenient)
import Data.Byteable (Byteable, toBytes, constEqBytes)
import Data.Byteable (constEqBytes)
import System.IO
import System.Random
import Data.Maybe
import Control.Exception as E
import Data.Bits
import Data.Char
import Data.List
import Data.Function
import qualified Control.Exception

---------------------
-- Cryptographic base
Expand Down Expand Up @@ -131,7 +140,11 @@ hashRounds bs rounds = B.concat $ L.toChunks $ (iterate hash bs_lazy) !! rounds
-- system RNG as a fallback. This is the function used to generate salts by
-- 'makePassword'.
genSaltIO :: IO Salt
genSaltIO = E.catch genSaltDevURandom (\(_::SomeException) -> genSaltSysRandom)
genSaltIO =
Control.Exception.catch genSaltDevURandom def
where
def :: IOError -> IO Salt
def _ = genSaltSysRandom

-- | Generate a 'Salt' from @\/dev\/urandom@.
genSaltDevURandom :: IO Salt
Expand Down Expand Up @@ -180,9 +193,42 @@ writePwHash (strength, SaltBS salt, hash) =
-- @\/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 password strength = do
makePassword = makePasswordWith pbkdf1

-- | A generic version of 'makePassword', which allow the user
-- to choose the algorithm to use.
--
-- >>> makePasswordWith pbkdf1 "password" 17
--
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 $ makePasswordSalt password salt strength
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 (17 is a good default), using a given
-- salt. The output of this function can be written directly to a password file
Expand All @@ -191,21 +237,39 @@ makePassword password strength = do
-- > >>> makePasswordSalt "hunter2" (makeSalt "72cd18b5ebfe6e96") 17
-- > "sha256|17|NzJjZDE4YjVlYmZlNmU5Ng==|i5VbJNJ3I6SPnxdK5pL0dHw4FoqnHYpSUXp70coXjOI="
makePasswordSalt :: ByteString -> Salt -> Int -> ByteString
makePasswordSalt password salt strength = writePwHash (strength, salt, hash)
where hash = encode $ pbkdf1 password salt (2^strength)

instance Byteable [Char] where
toBytes = B.pack
makePasswordSalt = makePasswordSaltWith pbkdf1 (2^)

-- | @verifyPassword userInput pwHash@ verifies the password @userInput@ given
-- by the user against the stored password hash @pwHash@. Returns 'True' if the
-- | '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.
verifyPassword :: ByteString -> ByteString -> Bool
verifyPassword userInput pwHash =
-- 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 $ pbkdf1 userInput salt (2^strength)) `constEqBytes` goodHash
encode (algorithm userInput salt (strengthModifier strength)) `constEqBytes` 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
Expand Down Expand Up @@ -261,6 +325,12 @@ makeSalt = SaltBS . encode . check_length
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
Expand Down