File: HKDF.hs

package info (click to toggle)
haskell-crypton 1.0.4-3
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 3,548 kB
  • sloc: haskell: 26,764; ansic: 22,294; makefile: 6
file content (105 lines) | stat: -rw-r--r-- 2,886 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
{-# LANGUAGE BangPatterns #-}

-- |
-- Module      : Crypto.KDF.HKDF
-- License     : BSD-style
-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
-- Stability   : experimental
-- Portability : unknown
--
-- Key Derivation Function based on HMAC
--
-- See RFC5869
module Crypto.KDF.HKDF (
    PRK,
    extract,
    extractSkip,
    expand,
    toPRK,
) where

import Crypto.Hash
import Crypto.Internal.ByteArray (
    ByteArray,
    ByteArrayAccess,
    ScrubbedBytes,
 )
import qualified Crypto.Internal.ByteArray as B
import Crypto.MAC.HMAC
import Data.Word

-- | Pseudo Random Key
data PRK a = PRK (HMAC a) | PRK_NoExpand ScrubbedBytes
    deriving (Eq)

instance ByteArrayAccess (PRK a) where
    length (PRK hm) = B.length hm
    length (PRK_NoExpand sb) = B.length sb
    withByteArray (PRK hm) = B.withByteArray hm
    withByteArray (PRK_NoExpand sb) = B.withByteArray sb

-- | Extract a Pseudo Random Key using the parameter and the underlaying hash mechanism
extract
    :: (HashAlgorithm a, ByteArrayAccess salt, ByteArrayAccess ikm)
    => salt
    -- ^ Salt
    -> ikm
    -- ^ Input Keying Material
    -> PRK a
    -- ^ Pseudo random key
extract salt ikm = PRK $ hmac salt ikm

-- | Create a PRK directly from the input key material.
--
-- Only use when guaranteed to have a good quality and random data to use directly as key.
-- This effectively skip a HMAC with key=salt and data=key.
extractSkip
    :: ByteArrayAccess ikm
    => ikm
    -> PRK a
extractSkip ikm = PRK_NoExpand $ B.convert ikm

-- | Expand key material of specific length out of the parameters
expand
    :: (HashAlgorithm a, ByteArrayAccess info, ByteArray out)
    => PRK a
    -- ^ Pseudo Random Key
    -> info
    -- ^ Optional context and application specific information
    -> Int
    -- ^ Output length in bytes
    -> out
    -- ^ Output data
expand prkAt infoAt outputLength =
    let hF = hFGet prkAt
     in B.concat $ loop hF B.empty outputLength 1
  where
    hFGet :: (HashAlgorithm a, ByteArrayAccess b) => PRK a -> (b -> HMAC a)
    hFGet prk = case prk of
        PRK hmacKey -> hmac hmacKey
        PRK_NoExpand ikm -> hmac ikm

    info :: ScrubbedBytes
    info = B.convert infoAt

    loop
        :: HashAlgorithm a
        => (ScrubbedBytes -> HMAC a)
        -> ScrubbedBytes
        -> Int
        -> Word8
        -> [ScrubbedBytes]
    loop hF tim1 n i
        | n <= 0 = []
        | otherwise =
            let input = B.concat [tim1, info, B.singleton i] :: ScrubbedBytes
                ti = B.convert $ hF input
                hashLen = B.length ti
                r = n - hashLen
             in (if n >= hashLen then ti else B.take n ti)
                    : loop hF ti r (i + 1)

toPRK :: (HashAlgorithm a, ByteArrayAccess ba) => ba -> Maybe (PRK a)
toPRK bs = case digestFromByteString bs of
    Nothing -> Nothing
    Just digest -> Just $ PRK $ HMAC digest