Understanding reason for Monad

Hello-- I am going to use the function 'hashPassword' in [1] and I am not able to understand why the result is MonadRandom ByteArray instead of simply being a ByteString (or, ByteArray, or similar). Looking at [2], the only useful thing I can do with a MonadRandom a is to convert it to IO a. Why would a result of this determistic computation be a IO? [1] - https://hackage.haskell.org/package/cryptonite-0.30/docs/Crypto-KDF-BCrypt.h... [2] - https://hackage.haskell.org/package/cryptonite-0.30/docs/Crypto-Random-Types...

Hi Pietro,
The hash algorithm you linked to is not a pure hash function.
Using pure hash functions for passwords make the resulting hashes
vulnerable to dictionary attacks.
The bcrypt algorithm incorporates random data—hence the use of
MonadRandom—called a salt, to generate a password hash resistant to
dictionary attacks.
(You’d probably get better answers to such questions on the cafe mailing
list. You seem to be well beyond the LYAH level, more power to you!)
Best, Kim-Ee
On Tue, Feb 21, 2023 at 8:17 PM Pietro Grandinetti
Hello--
I am going to use the function 'hashPassword' in [1] and I am not able to understand why the result is MonadRandom ByteArray instead of simply being a ByteString (or, ByteArray, or similar). Looking at [2], the only useful thing I can do with a MonadRandom a is to convert it to IO a. Why would a result of this determistic computation be a IO?
[1] - https://hackage.haskell.org/package/cryptonite-0.30/docs/Crypto-KDF-BCrypt.h... [2] - https://hackage.haskell.org/package/cryptonite-0.30/docs/Crypto-Random-Types... _______________________________________________ Beginners mailing list Beginners@haskell.org http://mail.haskell.org/cgi-bin/mailman/listinfo/beginners
-- -- Kim-Ee

Hi Pietro, Haskell type classes provide ad-hoc polymorphism. Essentially, they allow you to write a function that works with more than one concrete type. https://wiki.haskell.org/Polymorphism#Ad-hoc_polymorphism Function `hashPassword` has the following type: hashPassword :: (MonadRandom m, ByteArray password, ByteArray hash) => Int -> password -> m hash It has three constraints (listed before the double arrow): Constraint `MonadRandom m` means that the function works in any monad that has a `MonadRandom` instance and can therefore be used to generate random values. If you click `MonadRandom` in the documentation, you can see which instances are built in. There is an `IO` instance, so you can run `hashPassword` in the `IO` monad. Constraint `ByteArray password` means that the function accepts a password of any type that has a `ByteArray` instance. If you click `ByteArray` in the documentation, you can see which instances are built in. There is a `ByteString` instance, so `hashPassword` can handle `ByteString` password arguments. Constraint `ByteArray hash` means that the function can return a hash of any type that has a `ByteArray` instance. The concrete type may be determined by how the return type is used; if your code expects a `ByteString` hash, then that is what you get. Here is a simple demo that hashes the first argument, or `password` if no arguments are provided: {-# LANGUAGE OverloadedStrings #-} module Main (main) where -- https://hackage.haskell.org/package/base import System.Environment (getArgs) -- https://hackage.haskell.org/package/base16-bytestring import qualified Data.ByteString.Base16 as Base16 -- https://hackage.haskell.org/package/cryptonite import qualified Crypto.KDF.BCrypt as BCrypt -- https://hackage.haskell.org/package/text import qualified Data.Text as T import qualified Data.Text.Encoding as TE import qualified Data.Text.Encoding.Error as TEE import qualified Data.Text.IO as TIO main :: IO () main = do password <- T.pack . head . (++ ["password"]) <$> getArgs TIO.putStrLn $ "password: " <> password hashBS <- BCrypt.hashPassword 11 $ TE.encodeUtf8 password let hashHex = TE.decodeUtf8With TEE.lenientDecode $ Base16.encode hashBS TIO.putStrLn $ "hash: " <> hashHex This demo runs in the `IO` monad, the password is passed to `hashPassword` as a `ByteString`, and the hash returned by `hashPassword` is a `ByteString`. Note that the password is encoded as a `ByteString` via `Text` so that it supports UTF-8. $ hash-password パスワード password: パスワード hash: 24326224313124486e474157773750493667744b6b6270354451613275516863667a68702e6756727a414b474542716751755371314949613549314b Cheers, Travis

Hi Pietro, The reply I wrote yesterday was sent when I ran out of time, but I have since realized that I failed to get to the point. Sorry about that! The demo shows that you can use `hashPassword` with `IO` and `ByteString`, as if the type was the following: hashPassword :: Int -> ByteString -> IO ByteString The use of type classes allows the function to be used with other types as well, however. This makes the function more flexible for different people's needs. For example, a project may use this function in a user registration request handler. Perhaps that request handler needs to write to a database as well as perform logging. It can be implemented using a monad that has the context required for these features, so that the it can do all of these things without being concerned with configuration details. The monad may have the following constraints: * `MonadRandom m` so that `hashPassword` can be used * `MonadLogger m` so that it can log in the configured way * `MonadDatabase m` (using some application-defined monad) so that it has access to the application database If `hashPassword` had the above `IO` type, then it would not work in such an application monad directly. If there is a `MonadIO` instance, then one could use `liftIO`, but some application monads specifically do *not* have `MonadIO` instances to make it more difficult to simply run things in `IO`. I am out of time again, but I hope this two-part reply is helpful. Cheers, Travis
participants (3)
-
Kim-Ee Yeoh
-
Pietro Grandinetti
-
Travis Cardwell