{- | Implementation of Password Based Key Derivation Function, from RSA labs. See PKCS # 5 / RFC 2898 from rsa labs: and haskell cafe discussion on why password hashing is a good idea for web apps and a suggestion that this be implemented: > http://www.ietf.org/rfc/rfc2898.txt > http://groups.google.com/group/fa.haskell/browse_thread/thread/66c7aeeb6e47764a/b15d9d74d68c002c > hashedpass = pbkdf2 ( Password . toOctets $ "password" ) ( Salt . toOctets $ "salt" ) -} module Crypto.PBKDF2 (pbkdf2, pbkdf2', Password(..), Salt(..), HashedPass(..),toOctets,fromOctets ) where import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy as L import GHC.Word import Control.Monad (foldM) import Random import Data.Digest.SHA512 (hash) import Data.Word import Data.Bits import Data.Binary newtype Password = Password [Word8] deriving (Read,Show,Ord,Eq) newtype Salt = Salt [Word8] deriving (Read,Show,Ord,Eq) newtype HashedPass = HashedPass [Word8] deriving (Read,Show,Ord,Eq) t = pbkdf2 (Password . toOctets $ "blee") (Salt . toOctets $ "blah") {- | A reasonable default for rsa pbkdf2. > pbkdf2 = pbkdf2' (prfSHA512,64) 5000 64 SHA512 outputs 64 bytes. At least 1000 iters is suggested by PKCS#5 (rsa link above). I chose 5000 because this takes my computer a little over a second to compute a simple key derivation (see t test function in source) Dklen of 64 seemed reasonable to me: if this is being stored in a database, doesn't take too much space. Computational barriers can be raised by increasing number of iters -} --sha512 generates 64-element octet lists, so set hlen to 64. -- not sure if this is correct. does hlen refer to length in bytes or bits? pbkdf2 :: Password -> Salt -> HashedPass pbkdf2 = pbkdf2' (prfSHA512,64) 5000 64 {- | Password Based Key Derivation Function, from RSA labs. > pbkdf2' (prf,hlen) cIters dklen (Password pass) (Salt salt) prf: pseudo random function hlen: length of prf output cIters: Number of iterations of prf dklen: Length of the derived key (hashed password) -} pbkdf2' :: ( ([Word8] -> [Word8] -> [Word8]),Integer) -> Integer -> Integer -> Password -> Salt -> HashedPass pbkdf2' (prf,hlen) cIters dklen (Password pass) (Salt salt) | dklen > ( (2^32-1) * hlen) = error $ "pbkdf2, (dklen,hlen) : " ++ (show (dklen,hlen)) | otherwise = let --l,r :: Int l = ceiling $ (fromIntegral dklen) / (fromIntegral hlen ) r = dklen - ( (l-1) * hlen) ustream :: [Word8] -> [Word8] -> [[Word8]] ustream p s = let x = prf p s in x : ustream p x --us :: Integer -> [[Word8]] us i = take (fromIntegral cIters) $ ustream pass ( salt `myor` ((intToFourWord8s i) )) --f :: [Word8] -> [Word8] -> Integer -> Integer -> [Word8] f pass salt cIters i = foldr1 myxor $ us i ts :: [[Word8]] ts = map (f pass salt cIters) ( [1..l] ) in HashedPass . take (fromIntegral dklen) . concat $ ts toOctets :: (Binary a) => a -> [Word8] toOctets x = L.unpack . encode $ x fromOctets :: (Binary a) => [Word8] -> a fromOctets = decode . L.pack -- The spec says -- Here, INT (i) is a four-octet encoding of the integer i, most significant octet first. -- I'm reading from the right, which I think is the right thing. --intToFourWord8s :: Integer -> [Word8] intToFourWord8s i = let w8s = toOctets $ i in drop (length w8s -4) w8s myxor :: [Word8] -> [Word8] -> [Word8] myxor = zipWith xor myor :: [Word8] -> [Word8] -> [Word8] myor = zipWith (.|.) {- > prfSHA512 hlen seed pass ... hlen is the length of the pseudo random output. (not really, fix me) output is always 64 bytes long -} prfSHA512 :: [Word8] -> [Word8] -> [Word8] prfSHA512 seed pass = hash $ seed ++ pass t2 = prfSHA512 (toOctets "asdf") (toOctets "jkl; asdfjl; asjdfnkl;ajsdfl;jk;sn")