module Network.Fernet.Key
( Key(..)
, key
, generateKey
, generateKeyFromPassword
, keyFromBase64
, keyToBase64
) where
import Data.Monoid ((<>))
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import Data.Byteable (Byteable(..))
import Data.ByteArray (ScrubbedBytes, ByteArrayAccess(..))
import qualified Data.ByteArray as BA
import qualified Crypto.KDF.PBKDF2 as PBKDF2
import Crypto.Hash.Algorithms (SHA256(..))
import Crypto.Random (getRandomBytes)
import Network.Fernet.Base64
data Key = Key
{ signingKey :: ScrubbedBytes
, encryptionKey :: ScrubbedBytes
} deriving (Show, Eq)
key :: ByteArrayAccess a
=> a
-> a
-> Maybe Key
key s e = Key <$> toKey checkHashKeyLength s <*> toKey checkCipherKeyLength e
toKey :: ByteArrayAccess a => (Int -> Bool) -> a -> Maybe ScrubbedBytes
toKey checkLength k | checkLength (BA.length k) = Just (BA.convert k)
| otherwise = Nothing
cipherKeyLength :: Int
cipherKeyLength = 16
checkCipherKeyLength :: Int -> Bool
checkCipherKeyLength = (== cipherKeyLength)
checkHashKeyLength :: Int -> Bool
checkHashKeyLength = (>= 16)
generateKey :: IO Key
generateKey = splitKeys <$> getRandomBytes (cipherKeyLength * 2)
splitKeys :: ByteString -> Key
splitKeys = make . BS.splitAt cipherKeyLength
where make (s, e) = Key (BA.convert s) (BA.convert e)
genSalt :: IO ByteString
genSalt = getRandomBytes 16
keyToBase64 :: Key -> ByteString
keyToBase64 (Key s e) = b64url $ s <> e
keyFromBase64 :: ByteString
-> Either String Key
keyFromBase64 = (>>= make) . b64urldec
where make s = case key sk ek of
Just k -> Right k
Nothing -> Left "Invalid key length"
where (sk, ek) = BS.splitAt ((BS.length s) 16) s
generateKeyFromPassword :: Byteable p
=> Int
-> p
-> IO (Key, ByteString)
generateKeyFromPassword iterations p = do
salt <- genSalt
let keys = PBKDF2.generate prf params (toBytes p) salt
return (splitKeys keys, salt)
where
prf = PBKDF2.prfHMAC SHA256
params = PBKDF2.Parameters iterations 32