sel-0.0.1.0: src/Sel/Scrypt.hs
{-# LANGUAGE TypeApplications #-}
-- |
--
-- Module: Sel.Scrypt
-- Description: Hashing with the Scrypt algorithm.
-- Copyright: (C) Seth Paul Hubbard 2023
-- License: BSD-3-Clause
-- Maintainer: The Haskell Cryptography Group
-- Stability: Stable
-- Portability: GHC only
module Sel.Scrypt
( -- ** Introduction
-- $introduction
ScryptHash
-- ** Password Hashing and Verifying.
, scryptHashPassword
, scryptVerifyPassword
-- *** Conversion
, scryptHashToByteString
, scryptHashToText
, asciiTextToScryptHash
, asciiByteStringToScryptHash
)
where
import Control.Monad (void)
import Data.ByteString (StrictByteString)
import qualified Data.ByteString.Internal as BS
import Data.ByteString.Unsafe (unsafeUseAsCStringLen)
import Data.Text as Text
import Data.Text.Display
import qualified Data.Text.Encoding as Text
import qualified Data.Text.Lazy.Builder as Builder
import Foreign hiding (void)
import Foreign.C
import LibSodium.Bindings.Scrypt
import Sel.Internal
import System.IO.Unsafe (unsafeDupablePerformIO)
-- $introduction
--
-- This API is used for hashing and verifying passwords using the Scrypt algorithm.
-- This module is provided for interoperability with other applications. If you do
-- not need to use Scrypt specifically, use "Sel.Hashing.Password".
-- | A hashed password from the Scrypt algorithm.
--
-- @since 0.0.1.0
newtype ScryptHash = ScryptHash (ForeignPtr CChar)
-- | @since 0.0.1.0
instance Eq ScryptHash where
(ScryptHash sh1) == (ScryptHash sh2) =
unsafeDupablePerformIO $
foreignPtrEq sh1 sh2 cryptoPWHashScryptSalsa208SHA256StrBytes
-- | @since 0.0.1.0
instance Ord ScryptHash where
compare (ScryptHash sh1) (ScryptHash sh2) =
unsafeDupablePerformIO $
foreignPtrOrd sh1 sh2 cryptoPWHashScryptSalsa208SHA256StrBytes
-- | @since 0.0.1.0
instance Show ScryptHash where
show = Text.unpack . scryptHashToText
-- | @since 0.0.1.0
instance Display ScryptHash where
displayBuilder = Builder.fromText . scryptHashToText
-- | Hash the password with the Scrypt algorithm and a set of pre-defined parameters.
--
-- The hash is encoded in a human-readable format that includes:
--
-- * The result of a memory-hard, CPU-intensive hash function applied to the password;
-- * The automatically generated salt used for the previous computation;
-- * The other parameters required to verify the password, including the algorithm
-- identifier, its version, opslimit, and memlimit.
--
-- Example output: "$7$C6..../....dLONLMz8YfO/.EKvzwOeqWVVLmXg62MC.hL1m1sYtO/$X9eNjVxdD4jHAhOVid3OLzNkpv6ADJSAXygOxXqGHg7\NUL"
--
-- @since 0.0.1.0
scryptHashPassword :: StrictByteString -> IO ScryptHash
scryptHashPassword bytestring = do
unsafeUseAsCStringLen bytestring $ \(cString, cStringLen) -> do
hashForeignPtr <- mallocForeignPtrBytes (fromIntegral cryptoPWHashScryptSalsa208SHA256StrBytes)
withForeignPtr hashForeignPtr $ \hashPtr ->
void $
cryptoPWHashScryptSalsa208SHA256Str
hashPtr
cString
(fromIntegral cStringLen)
(fromIntegral cryptoPWHashScryptSalsa208SHA256OpsLimitInteractive)
cryptoPWHashScryptSalsa208SHA256MemLimitInteractive
pure $ ScryptHash hashForeignPtr
-- | Verify a hashed password against a password verification string.
-- This returns True if successful.
--
-- @since 0.0.1.0
scryptVerifyPassword :: StrictByteString -> ScryptHash -> IO Bool
scryptVerifyPassword bytestring (ScryptHash sh) = do
unsafeUseAsCStringLen bytestring $ \(cString, cStringLen) -> do
withForeignPtr sh $ \scryptHash -> do
result <-
cryptoPWHashScryptSalsa208SHA256StrVerify
scryptHash
cString
(fromIntegral cStringLen)
return (result == 0)
-- | Convert a 'ScryptHash' to a binary 'StrictByteString'.
--
-- @since 0.0.1.0
scryptHashToByteString :: ScryptHash -> StrictByteString
scryptHashToByteString (ScryptHash fPtr) =
BS.fromForeignPtr0 (Foreign.castForeignPtr fPtr) (fromIntegral @CSize @Int cryptoPWHashScryptSalsa208SHA256StrBytes)
-- | Convert a 'ScryptHash' to a hexadecimal-encoded 'Text'.
--
-- @since 0.0.1.0
scryptHashToText :: ScryptHash -> Text
scryptHashToText = Text.decodeASCII . scryptHashToByteString
-- | Convert an ASCII-encoded password hash to a 'ScryptHash'
--
-- This function does not perform ASCII validation.
--
-- @since 0.0.1.0
asciiByteStringToScryptHash :: StrictByteString -> ScryptHash
asciiByteStringToScryptHash textualHash =
let (fPtr, _length) = BS.toForeignPtr0 textualHash
in ScryptHash (castForeignPtr @Word8 @CChar fPtr)
-- | Convert an ASCII-encoded password hash to a 'ScryptHash'
--
-- This function does not perform ASCII validation.
--
-- @since 0.0.1.0
asciiTextToScryptHash :: Text -> ScryptHash
asciiTextToScryptHash = asciiByteStringToScryptHash . Text.encodeUtf8