This post has been updated with an improved version provided at the end of the article.

Dealing with passwords is something most web applications have to do, and it is fraught with danger.

We have to be very careful with the plain text. We don’t want to store it, even accidentally in logs.

To help prevent common password errors, I want to create a type with a restrictive API that encourages correct use.

Ideally, I would like to immediately encrypt the password in the FromJSON instance, but parseJSON does not have access to IO which I need to generate salt.

So, I will have to store the plain text temporarily.

I don’t want to be able to print it, compare it or examine it directly in any way. The only thing I can do is apply a cryptographically secure hash function to it.

The Code

I make the type but I don’t derive any type classes.

data PlainText = PlainText Text

I can create a plain text passwords, but not with the constructor, which I keep hidden to prevent pattern matching.

fromText :: Text -> PlainText

fromText = PlainText

I can then hash the PlainText

hash :: PlainText -> IO ByteString

hash (PlainText x) = do

salt <- getEntropy 64 :: IO ByteString

let params = Parameters

{ iterCounts = 10000

, outputLength = 64

}

return $ generate (prfHMAC SHA512) params (encodeUtf8 x) salt

Finally, I only export the type PlainText , hash , and fromText .

module Data.PlainText

( PlainText

, fromText

, hash

) where

That’s it (I also added a FromJSON instance). I can now use this type as part of a larger record for creating a user, like:

data CreateUser = CreateUser

{ email :: Text

, password :: PlainText

}

and feel safe in the knowledge that the plain text can’t leak anywhere (although it can still be stolen by directly reading the machine’s memory).

The code is here: https://github.com/jfischoff/safe-password. I haven’t used this type yet; it’s just an idea I came up with a few days ago. I’m curious to know what people think.

UPDATED:

Roman Cheplyaka pointed out an improved version:

newtype Password = Password (IO ByteString) fromText :: Text -> Password

fromText x = Password $ do

salt <- getEntropy 64 :: IO ByteString

let params = Parameters

{ iterCounts = 10000

, outputLength = 64

}

return $ generate (prfHMAC SHA512) params (encodeUtf8 x) salt hash :: Password -> IO ByteString

hash (Password x) = x