packages feed

sha256-0.1.0.0: lib/Crypto/Sha256/Pbkdf2.hs

{-# LANGUAGE BangPatterns #-}

-------------------------------------------------------------------------------
-- |
-- Module:      Crypto.Sha256.Pbkdf2
-- Copyright:   (c) 2024 Auth Global
-- License:     Apache2
--
-- An implementation of PBKDF2-HMAC-SHA256
--
-------------------------------------------------------------------------------

module Crypto.Sha256.Pbkdf2
     ( pbkdf2
     , pbkdf2_index
     , Pbkdf2Ctx()
     , pbkdf2Ctx_init
     , pbkdf2Ctx_feed, pbkdf2Ctx_feeds
     , pbkdf2Ctx_update, pbkdf2Ctx_updates
     , pbkdf2Ctx_finalize
     , Pbkdf2Gen()
     , pbkdf2Gen_iterate
     , pbkdf2Gen_finalize
     )
     where

import           Data.ByteString(ByteString)
import qualified Data.ByteString.Short as SB
import           Data.Function((&))
import           Data.Word
import           Crypto.HashString ( HashString )
import qualified Crypto.HashString as HS
import           Crypto.Sha256
import           Crypto.Sha256.Hmac
import           Crypto.Sha256.Hmac.Subtle
import           Crypto.Sha256.Pbkdf2.Subtle
import qualified Network.ByteOrder as NB

takeHS :: Int -> [ HashString ] -> [ HashString ]
takeHS = go
  where
    len = SB.length . HS.toShort
    go _ [] = []
    go n (b:bs)
      | n <= 0 = []
      | len b < n = b : go (n - len b) bs
      | otherwise = [HS.fromShort (SB.take n (HS.toShort b))]

-- | Simple interface to PBKDF2. Reusing computations via partial application is
--   not (yet!) supported. TODO: write pbkdf2 and pbkdf2_index functions in a
--   point-free style.

pbkdf2
  :: ByteString -- ^ nominally the "password"
  -> ByteString -- ^ nominally the "salt"
  -> Word64 -- ^ number of rounds
  -> Int -- ^ desired length of output
  -> HashString
pbkdf2 password0 salt rounds len = out
  where
     password = hmacKeyHashed password0
     saltCtx =
       pbkdf2Ctx_init password &
       pbkdf2Ctx_feed salt
     gen index =
       pbkdf2Ctx_finalize index saltCtx &
       pbkdf2Gen_iterate ((max rounds 1) - 1) &
       pbkdf2Gen_finalize
     out = mconcat (takeHS len (map gen [1..maxBound]))

pbkdf2_index
  :: ByteString -- ^ nominally the "password"
  -> ByteString -- ^ nominally the "salt"
  -> Word32 -- ^ the "index", returns the i-th block of output. The first index is 1, thus the result consists of bytes starting at 32*(i-1) and ending before 32*i.  This is appended as 4 more bytes after the salt.
  -> Word64 -- ^ number of rounds
  -> HashString -- ^ 32-byte output"
pbkdf2_index password0 salt index rounds = out
  where
     password = hmacKeyHashed password0
     saltCtx =
       pbkdf2Ctx_init password &
       pbkdf2Ctx_feed salt
     out =
       pbkdf2Ctx_finalize index saltCtx &
       pbkdf2Gen_iterate ((max rounds 1) - 1) &
       pbkdf2Gen_finalize

pbkdf2Ctx_init :: HmacKeyHashed -> Pbkdf2Ctx
pbkdf2Ctx_init password = Pbkdf2Ctx
    { pbkdf2Ctx_password = password
    , pbkdf2Ctx_ipadCtx = hmacKeyHashed_ipadCtx password
    }

-- | Append some bytes to the end of the salt. Flipped version of 'pbkdf2Ctx_feed'.

pbkdf2Ctx_update :: Pbkdf2Ctx -> ByteString -> Pbkdf2Ctx
pbkdf2Ctx_update ctx bs = ctx { pbkdf2Ctx_ipadCtx = sha256_update (pbkdf2Ctx_ipadCtx ctx) bs }

-- | Append zero or more bytestrings to the end of the salt. Flipped version of 'pbkdf2Ctx_feeds'

pbkdf2Ctx_updates :: Foldable f => Pbkdf2Ctx -> f ByteString -> Pbkdf2Ctx
pbkdf2Ctx_updates ctx bs = ctx { pbkdf2Ctx_ipadCtx = sha256_updates (pbkdf2Ctx_ipadCtx ctx) bs }

-- | Append some bytes to the end of the salt. Flipped version of 'pbkdf2Ctx_update'.

pbkdf2Ctx_feed :: ByteString -> Pbkdf2Ctx -> Pbkdf2Ctx
pbkdf2Ctx_feed = flip pbkdf2Ctx_update

-- | Append zero or more bytestrings to the end of the salt. Flipped version of 'pbkdf2Ctx_updates'.

pbkdf2Ctx_feeds :: Foldable f => f ByteString -> Pbkdf2Ctx ->  Pbkdf2Ctx
pbkdf2Ctx_feeds = flip pbkdf2Ctx_updates

-- | Append the index to the end of the salt, and then initialize a 'Pbkdf2Gen' with
--   one round applied.

pbkdf2Ctx_finalize
  :: Word32 -- ^ index of output block
  -> Pbkdf2Ctx
  -> Pbkdf2Gen
pbkdf2Ctx_finalize index ctx = Pbkdf2Gen
  { pbkdf2Gen_password = password
  , pbkdf2Gen_finalize = state
  , pbkdf2Gen_state = state
  }
  where
    password = pbkdf2Ctx_password ctx
    ipad = pbkdf2Ctx_ipadCtx ctx &
           sha256_finalizeBytes_toByteString (NB.bytestring32 index)
    state = hmacKeyHashed_opadCtx password &
            sha256_finalizeBytes ipad

-- | Apply zero or more rounds to a pbkdf2 computation.

pbkdf2Gen_iterate
  :: Word64  -- ^ number of key-stretching rounds to perform
  -> Pbkdf2Gen
  -> Pbkdf2Gen
pbkdf2Gen_iterate n0 ctx = go n0 xorSum0 state0
  where
    password = pbkdf2Gen_password ctx
    xorSum0 = pbkdf2Gen_finalize ctx
    state0 = pbkdf2Gen_state ctx
    go n xorSum state
      | n <= 0 =
        Pbkdf2Gen
          { pbkdf2Gen_password = password
          , pbkdf2Gen_finalize = xorSum
          , pbkdf2Gen_state = state
          }
      | otherwise =
        let !state' = hmacKeyHashed_run password &
                      hmacCtx_finalizeBytes (HS.toByteString state)
            !xorSum' = HS.xorLeft state' xorSum
         in go (n-1) xorSum' state'