packages feed

crypton-2.2.0: Crypto/Tutorial.hs

-- | Examples of how to use @crypton@.
--
-- Every code block here is extracted and compiled against this version of
-- the library by @tests\/tutorial\/run.sh@, so what is written below builds.
module Crypto.Tutorial (
    -- * API design
    -- $api_design

    -- * Hash algorithms
    -- $hash_algorithms

    -- * Authenticated encryption
    -- $authenticated_encryption

    -- * Comparing secrets
    -- $comparing_secrets

    -- * Password storage
    -- $password_storage

    -- * Key derivation
    -- $key_derivation

    -- * Digital signatures
    -- $digital_signatures

    -- * Combining primitives
    -- $combining_primitives
) where

-- $api_design
--
-- APIs in crypton are often based on type classes from package
-- <https://hackage.haskell.org/package/ram ram>, notably
-- 'Data.ByteArray.ByteArrayAccess' and 'Data.ByteArray.ByteArray'.
-- Module "Data.ByteArray" provides many primitives that are useful to
-- work with crypton types.  For example function 'Data.ByteArray.convert'
-- can transform one 'Data.ByteArray.ByteArrayAccess' concrete type like
-- 'Crypto.Hash.Digest' to a 'Data.ByteString.ByteString'.
--
-- Algorithms and functions needing random bytes are based on type class
-- 'Crypto.Random.Types.MonadRandom'.  Implementation 'IO' uses a system source
-- of entropy.  It is also possible to use a 'Crypto.Random.Types.DRG' with
-- 'Crypto.Random.Types.MonadPseudoRandom'
--
-- Error conditions are returned with data type 'Crypto.Error.CryptoFailable'.
-- Functions in module "Crypto.Error" can convert those values to runtime
-- exceptions, 'Maybe' or 'Either' values.
--
-- Types that hold a secret do not print it.  A private key's 'Show' renders
-- whatever is public and @\<secret\>@ or @\<scrubbed-bytes\>@ for the rest,
-- because 'Show' is what @print@, @error@, an exception and a failing test
-- all reach for, and a key arriving in a log that way is an accident nobody
-- asked for.  "Crypto.Debug" is how one is printed when printing it is what
-- was meant.  Several of those types keep their bytes in
-- 'Data.ByteArray.ScrubbedBytes', which is wiped when it is collected.

-- $hash_algorithms
--
-- Hashing a complete message:
--
-- > import Crypto.Hash
-- >
-- > import Data.ByteString (ByteString)
-- >
-- > exampleHashWith :: ByteString -> IO ()
-- > exampleHashWith msg = do
-- >     putStrLn $ "  sha1(" ++ show msg ++ ") = " ++ show (hashWith SHA1   msg)
-- >     putStrLn $ "sha256(" ++ show msg ++ ") = " ++ show (hashWith SHA256 msg)
--
-- Hashing incrementally, with intermediate context allocations:
--
-- > {-# LANGUAGE OverloadedStrings #-}
-- >
-- > import Crypto.Hash
-- >
-- > import Data.ByteString (ByteString)
-- >
-- > exampleIncrWithAllocs :: IO ()
-- > exampleIncrWithAllocs = do
-- >     let ctx0 = hashInitWith SHA3_512
-- >         ctx1 = hashUpdate ctx0 ("The "   :: ByteString)
-- >         ctx2 = hashUpdate ctx1 ("quick " :: ByteString)
-- >         ctx3 = hashUpdate ctx2 ("brown " :: ByteString)
-- >         ctx4 = hashUpdate ctx3 ("fox "   :: ByteString)
-- >         ctx5 = hashUpdate ctx4 ("jumps " :: ByteString)
-- >         ctx6 = hashUpdate ctx5 ("over "  :: ByteString)
-- >         ctx7 = hashUpdate ctx6 ("the "   :: ByteString)
-- >         ctx8 = hashUpdate ctx7 ("lazy "  :: ByteString)
-- >         ctx9 = hashUpdate ctx8 ("dog"    :: ByteString)
-- >     print (hashFinalize ctx9)
--
-- Hashing incrementally, updating context in place:
--
-- > {-# LANGUAGE OverloadedStrings #-}
-- >
-- > import Crypto.Hash.Algorithms
-- > import Crypto.Hash.IO
-- >
-- > import Data.ByteString (ByteString)
-- >
-- > exampleIncrInPlace :: IO ()
-- > exampleIncrInPlace = do
-- >     ctx <- hashMutableInitWith SHA3_512
-- >     hashMutableUpdate ctx ("The "   :: ByteString)
-- >     hashMutableUpdate ctx ("quick " :: ByteString)
-- >     hashMutableUpdate ctx ("brown " :: ByteString)
-- >     hashMutableUpdate ctx ("fox "   :: ByteString)
-- >     hashMutableUpdate ctx ("jumps " :: ByteString)
-- >     hashMutableUpdate ctx ("over "  :: ByteString)
-- >     hashMutableUpdate ctx ("the "   :: ByteString)
-- >     hashMutableUpdate ctx ("lazy "  :: ByteString)
-- >     hashMutableUpdate ctx ("dog"    :: ByteString)
-- >     hashMutableFinalize ctx >>= print

-- $authenticated_encryption
--
-- Encrypting hides a message; it does not stop anyone changing it.  Under a
-- counter or stream mode, flipping a bit of the ciphertext flips the same
-- bit of the plaintext, and the receiver has no way to tell.  An AEAD mode
-- binds the message, and anything else named as associated data, to a short
-- authentication tag, and decrypting something that does not match that tag
-- returns nothing at all.
--
-- That is the mode to use.  The unauthenticated ones are in this library
-- for protocols that authenticate separately, not as a starting point.
--
-- > {-# LANGUAGE OverloadedStrings #-}
-- >
-- > import           Crypto.Cipher.AES (AES256)
-- > import           Crypto.Cipher.Types
-- >                      ( AEADMode (AEAD_GCM)
-- >                      , AuthTag
-- >                      , BlockCipher (aeadInit)
-- >                      , Cipher (cipherInit, cipherKeySize)
-- >                      , KeySizeSpecifier (..)
-- >                      , aeadSimpleDecrypt
-- >                      , aeadSimpleEncrypt
-- >                      )
-- > import           Crypto.Error (CryptoError, eitherCryptoError)
-- > import qualified Crypto.Random.Types as CRT
-- >
-- > import           Data.ByteArray (ByteArray, ByteArrayAccess, ScrubbedBytes)
-- > import           Data.ByteString (ByteString)
-- >
-- > -- | A key of the length the cipher asks for, rather than a length
-- > -- written out here.  ScrubbedBytes rather than ByteString, so that it
-- > -- is wiped when it is collected and does not print.
-- > genSecretKey :: (Cipher c, CRT.MonadRandom m) => c -> m ScrubbedBytes
-- > genSecretKey c = CRT.getRandomBytes (longest (cipherKeySize c))
-- >   where
-- >     longest (KeySizeFixed n)   = n
-- >     longest (KeySizeRange _ n) = n
-- >     longest (KeySizeEnum ns)   = maximum ns
-- >
-- > -- | A fresh nonce for every message.  GCM must never see one twice
-- > -- under the same key: a repeat does not just expose those two messages,
-- > -- it hands over the key that authenticates all of them.  Twelve random
-- > -- bytes, sent along with the ciphertext.
-- > genNonce :: CRT.MonadRandom m => m ByteString
-- > genNonce = CRT.getRandomBytes 12
-- >
-- > -- | Encrypt and authenticate.  The associated data is authenticated but
-- > -- not encrypted: it is for what the receiver can already see and must
-- > -- not have had altered, such as a header or an address.
-- > encrypt
-- >     :: (ByteArray key, ByteArrayAccess nonce, ByteArrayAccess aad, ByteArray ba)
-- >     => key -> nonce -> aad -> ba -> Either CryptoError (AuthTag, ba)
-- > encrypt key nonce aad plaintext = do
-- >     cipher <- eitherCryptoError (cipherInit key) :: Either CryptoError AES256
-- >     aead <- eitherCryptoError (aeadInit AEAD_GCM cipher nonce)
-- >     return (aeadSimpleEncrypt aead aad plaintext 16)
-- >
-- > -- | And back, with two different failures.  Left is this code used
-- > -- wrongly -- a key of the wrong length, a mode the cipher has not got.
-- > -- Nothing is a message that is not the one that was sent; it carries no
-- > -- plaintext and says nothing about which byte was wrong, both of which
-- > -- are the point.
-- > decrypt
-- >     :: (ByteArray key, ByteArrayAccess nonce, ByteArrayAccess aad, ByteArray ba)
-- >     => key -> nonce -> aad -> AuthTag -> ba -> Either CryptoError (Maybe ba)
-- > decrypt key nonce aad tag ciphertext = do
-- >     cipher <- eitherCryptoError (cipherInit key) :: Either CryptoError AES256
-- >     aead <- eitherCryptoError (aeadInit AEAD_GCM cipher nonce)
-- >     return (aeadSimpleDecrypt aead aad ciphertext tag)
-- >
-- > exampleAES256GCM :: ByteString -> IO ()
-- > exampleAES256GCM msg = do
-- >     key <- genSecretKey (undefined :: AES256)
-- >     nonce <- genNonce
-- >     let aad = "to: alice" :: ByteString
-- >     case encrypt key nonce aad msg of
-- >         Left err -> error (show err)
-- >         Right (tag, ciphertext) -> do
-- >             putStrLn $ "ciphertext: " ++ show ciphertext
-- >             putStrLn $ "       tag: " ++ show tag
-- >             putStrLn $ " recovered: "
-- >                 ++ show (decrypt key nonce aad tag ciphertext)
-- >             -- The same bytes and the same tag, with one thing changed
-- >             -- that was never encrypted: Right Nothing.
-- >             putStrLn $ "redirected: "
-- >                 ++ show (decrypt key nonce ("to: eve" :: ByteString) tag ciphertext)
--
-- The two functions above work for any cipher that has an AEAD mode; what
-- changes is the mode given to 'Crypto.Cipher.Types.aeadInit'.
-- "Crypto.Cipher.ChaChaPoly1305" is the one to prefer on a machine with no
-- AES instructions, and "Crypto.Cipher.AESGCMSIV" is the one that survives
-- a repeated nonce, at the price of needing the whole message before it can
-- begin.

-- $comparing_secrets
--
-- Comparing two byte strings with '==' stops at the first byte that
-- differs, so how long it takes says where that byte was.  Against an
-- authentication tag that is the whole secret: someone who can send a guess
-- and time the answer finds the first byte in a few hundred tries, then the
-- second, and has a tag that was supposed to cost 2^128 in a few thousand.
--
-- crypton's own authentication types already compare in constant time, so
-- for those there is nothing to do: 'Crypto.MAC.HMAC.HMAC',
-- 'Crypto.MAC.CMAC.CMAC', 'Crypto.MAC.Poly1305.Auth' and
-- 'Crypto.Cipher.Types.AuthTag' have an 'Eq' that looks at every byte
-- whatever it finds.  What needs care is a tag that arrives as bytes, and
-- 'Data.ByteArray.constEq' is the comparison for it.
--
-- > {-# LANGUAGE OverloadedStrings #-}
-- >
-- > import           Crypto.Hash.Algorithms (SHA256)
-- > import           Crypto.MAC.HMAC (HMAC, hmac)
-- >
-- > import qualified Data.ByteArray as BA
-- > import           Data.ByteString (ByteString)
-- >
-- > -- | The tag came off the wire as bytes, so it is compared as bytes.
-- > authentic :: ByteString -> ByteString -> ByteString -> Bool
-- > authentic key message tag =
-- >     BA.constEq tag (hmac key message :: HMAC SHA256)
-- >
-- > -- | Once it has been parsed into the library's own type, (==) is
-- > -- already the constant-time comparison.
-- > authentic' :: ByteString -> ByteString -> HMAC SHA256 -> Bool
-- > authentic' key message tag = tag == hmac key message

-- $password_storage
--
-- A password is not a key.  It is short and it is guessable, and whoever
-- takes the database can try every likely one without being watched.  What
-- answers that is a function that is deliberately expensive to compute, and
-- crypton has four: "Crypto.KDF.BCrypt", "Crypto.KDF.Scrypt",
-- "Crypto.KDF.Argon2" and "Crypto.KDF.PBKDF2".  A plain hash is not one of
-- them, however many times it is applied by hand.
--
-- bcrypt leaves the least to get wrong, because the record it returns
-- carries the salt and the cost inside it:
--
-- > import Crypto.KDF.BCrypt (hashPassword, validatePassword)
-- >
-- > import Data.ByteString (ByteString)
-- >
-- > -- | What goes in the database.  The salt is drawn inside and ends up in
-- > -- the result, so two accounts with the same password do not look alike.
-- > register :: ByteString -> IO ByteString
-- > register password = hashPassword 12 password
-- >
-- > -- | And what is checked against it.  The cost comes out of the stored
-- > -- record, so raising it for new accounts leaves the old ones working.
-- > login :: ByteString -> ByteString -> Bool
-- > login password stored = validatePassword password stored
--
-- Argon2 is the stronger choice and the one to pick for something new: it
-- asks for memory as well as time, which is what takes the advantage away
-- from the hardware that bcrypt's small working set leaves room for.
-- Nothing is encoded for the caller, though -- the salt and the options are
-- theirs to store, and without all three the hash cannot be recomputed when
-- the user comes back.
--
-- > import           Crypto.Error (CryptoFailable)
-- > import qualified Crypto.KDF.Argon2 as Argon2
-- > import qualified Crypto.Random.Types as CRT
-- >
-- > import           Data.ByteString (ByteString)
-- >
-- > -- | Argon2id, which is the variant to prefer: it resists both a machine
-- > -- built to guess and a process watching the cache.
-- > options :: Argon2.Options
-- > options = Argon2.defaultOptions{Argon2.variant = Argon2.Argon2id}
-- >
-- > newSalt :: CRT.MonadRandom m => m ByteString
-- > newSalt = CRT.getRandomBytes 16
-- >
-- > derive :: ByteString -> ByteString -> CryptoFailable ByteString
-- > derive salt password = Argon2.hash options password salt 32

-- $key_derivation
--
-- HKDF turns one secret into as many keys as a protocol needs.  It is for
-- material that is already unguessable -- what comes out of a
-- Diffie-Hellman, or a key already agreed -- and it is deliberately cheap,
-- which is exactly what makes it the wrong thing for a password.  Those go
-- to the section above.
--
-- The info string is what keeps the outputs independent: the same secret
-- with a different info gives an unrelated key, so each use of a secret
-- names itself there.
--
-- > {-# LANGUAGE OverloadedStrings #-}
-- >
-- > import           Crypto.Hash.Algorithms (SHA256)
-- > import qualified Crypto.KDF.HKDF as HKDF
-- >
-- > import           Data.ByteArray (ScrubbedBytes)
-- > import           Data.ByteString (ByteString)
-- >
-- > -- | One shared secret in, two unrelated keys out.
-- > directionKeys :: ByteString -> ByteString -> (ScrubbedBytes, ScrubbedBytes)
-- > directionKeys salt shared = (keyFor "client write", keyFor "server write")
-- >   where
-- >     prk = HKDF.extract salt shared :: HKDF.PRK SHA256
-- >     keyFor info = HKDF.expand prk (info :: ByteString) 32

-- $digital_signatures
--
-- Ed25519 is the one to reach for.  The keys are thirty-two bytes, there is
-- nothing to choose and nothing to encode, and signing needs no randomness,
-- so it cannot be ruined by a bad source of it.  "Crypto.PubKey.Ed448" is
-- the same shape at a larger size, "Crypto.PubKey.ECDSA" and
-- "Crypto.PubKey.RSA.PSS" are there for protocols that ask for them, and
-- "Crypto.PubKey.MLDSA" is the post-quantum one.
--
-- > {-# LANGUAGE OverloadedStrings #-}
-- >
-- > import           Crypto.Error (throwCryptoError)
-- > import qualified Crypto.PubKey.Ed25519 as Ed25519
-- >
-- > import qualified Data.ByteArray as BA
-- > import           Data.ByteString (ByteString)
-- >
-- > exampleEd25519 :: ByteString -> IO ()
-- > exampleEd25519 msg = do
-- >     sk <- Ed25519.generateSecretKey
-- >     let pk = Ed25519.toPublic sk
-- >         sig = Ed25519.sign sk pk msg
-- >     print (Ed25519.verify pk msg sig)
-- >     -- The same signature against a message one byte longer: False.
-- >     print (Ed25519.verify pk (msg <> "!") sig)
-- >
-- > -- | A secret key on its way to storage.  Printing one does not reveal
-- > -- it -- see the first section -- so this is the way out.
-- > store :: Ed25519.SecretKey -> ByteString
-- > store = BA.convert
-- >
-- > -- | And the way back in, which is checked, because the bytes read from
-- > -- a file may be anything at all.
-- > load :: ByteString -> Ed25519.SecretKey
-- > load = throwCryptoError . Ed25519.secretKey

-- $combining_primitives
--
-- This example shows how to use Curve25519, XSalsa and Poly1305 primitives to
-- emulate NaCl's @crypto_box@ construct.
--
-- It is here to show how the pieces fit together, not as something to
-- deploy.  An authenticated encryption scheme assembled by hand is the kind
-- of code that is wrong in ways no test notices; for actual use,
-- "Crypto.Cipher.ChaChaPoly1305" does this job with the mistakes already
-- made.
--
-- > import qualified Data.ByteArray as BA
-- > import           Data.ByteString (ByteString)
-- > import qualified Data.ByteString as B
-- >
-- > import           Crypto.Error (throwCryptoError)
-- > import qualified Crypto.Cipher.XSalsa as XSalsa
-- > import qualified Crypto.MAC.Poly1305 as Poly1305
-- > import qualified Crypto.PubKey.Curve25519 as X25519
-- >
-- > -- | Build a @crypto_box@ packet encrypting the specified content with a
-- > -- 192-bit nonce, receiver public key and sender private key.
-- > crypto_box
-- >     :: ByteString -> ByteString -> X25519.PublicKey -> X25519.SecretKey
-- >     -> ByteString
-- > crypto_box content nonce pk sk = BA.convert tag `B.append` c
-- >   where
-- >     zero         = B.replicate 16 0
-- >     shared       = X25519.dh pk sk
-- >     (iv0, iv1)   = B.splitAt 8 nonce
-- >     state0       = XSalsa.initialize 20 shared (zero `B.append` iv0)
-- >     state1       = XSalsa.derive state0 iv1
-- >     (rs, state2) = XSalsa.generate state1 32
-- >     (c, _)       = XSalsa.combine state2 content
-- >     macKey       = throwCryptoError (Poly1305.key (rs :: ByteString))
-- >     tag          = Poly1305.auth macKey c
-- >
-- > -- | Try to open a @crypto_box@ packet and recover the content using the
-- > -- 192-bit nonce, sender public key and receiver private key.
-- > crypto_box_open
-- >     :: ByteString -> ByteString -> X25519.PublicKey -> X25519.SecretKey
-- >     -> Maybe ByteString
-- > crypto_box_open packet nonce pk sk
-- >     | B.length packet < 16 = Nothing
-- >     | BA.constEq tag' tag  = Just content
-- >     | otherwise            = Nothing
-- >   where
-- >     (tag', c)    = B.splitAt 16 packet
-- >     zero         = B.replicate 16 0
-- >     shared       = X25519.dh pk sk
-- >     (iv0, iv1)   = B.splitAt 8 nonce
-- >     state0       = XSalsa.initialize 20 shared (zero `B.append` iv0)
-- >     state1       = XSalsa.derive state0 iv1
-- >     (rs, state2) = XSalsa.generate state1 32
-- >     (content, _) = XSalsa.combine state2 c
-- >     macKey       = throwCryptoError (Poly1305.key (rs :: ByteString))
-- >     tag          = Poly1305.auth macKey c