github.com/hashicorp/vault/sdk@v0.11.0/helper/kdf/kdf.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  // This package is used to implement Key Derivation Functions (KDF)
     5  // based on the recommendations of NIST SP 800-108. These are useful
     6  // for generating unique-per-transaction keys, or situations in which
     7  // a key hierarchy may be useful.
     8  package kdf
     9  
    10  import (
    11  	"crypto/hmac"
    12  	"crypto/sha256"
    13  	"encoding/binary"
    14  	"fmt"
    15  	"math"
    16  )
    17  
    18  // PRF is a pseudo-random function that takes a key or seed,
    19  // as well as additional binary data and generates output that is
    20  // indistinguishable from random. Examples are cryptographic hash
    21  // functions or block ciphers.
    22  type PRF func([]byte, []byte) ([]byte, error)
    23  
    24  // CounterMode implements the counter mode KDF that uses a pseudo-random-function (PRF)
    25  // along with a counter to generate derived keys. The KDF takes a base key
    26  // a derivation context, and the required number of output bits.
    27  func CounterMode(prf PRF, prfLen uint32, key []byte, context []byte, bits uint32) ([]byte, error) {
    28  	// Ensure the PRF is byte aligned
    29  	if prfLen%8 != 0 {
    30  		return nil, fmt.Errorf("PRF must be byte aligned")
    31  	}
    32  
    33  	// Ensure the bits required are byte aligned
    34  	if bits%8 != 0 {
    35  		return nil, fmt.Errorf("bits required must be byte aligned")
    36  	}
    37  
    38  	// Determine the number of rounds required
    39  	rounds := bits / prfLen
    40  	if bits%prfLen != 0 {
    41  		rounds++
    42  	}
    43  
    44  	if len(context) > math.MaxInt-8 {
    45  		return nil, fmt.Errorf("too much context specified; would overflow: %d bytes", len(context))
    46  	}
    47  
    48  	// Allocate and setup the input
    49  	input := make([]byte, 4+len(context)+4)
    50  	copy(input[4:], context)
    51  	binary.BigEndian.PutUint32(input[4+len(context):], bits)
    52  
    53  	// Iteratively generate more key material
    54  	var out []byte
    55  	var i uint32
    56  	for i = 0; i < rounds; i++ {
    57  		// Update the counter in the input string
    58  		binary.BigEndian.PutUint32(input[:4], i)
    59  
    60  		// Compute more key material
    61  		part, err := prf(key, input)
    62  		if err != nil {
    63  			return nil, err
    64  		}
    65  		if uint32(len(part)*8) != prfLen {
    66  			return nil, fmt.Errorf("PRF length mis-match (%d vs %d)", len(part)*8, prfLen)
    67  		}
    68  		out = append(out, part...)
    69  	}
    70  
    71  	// Return the desired number of output bytes
    72  	return out[:bits/8], nil
    73  }
    74  
    75  const (
    76  	// HMACSHA256PRFLen is the length of output from HMACSHA256PRF
    77  	HMACSHA256PRFLen uint32 = 256
    78  )
    79  
    80  // HMACSHA256PRF is a pseudo-random-function (PRF) that uses an HMAC-SHA256
    81  func HMACSHA256PRF(key []byte, data []byte) ([]byte, error) {
    82  	hash := hmac.New(sha256.New, key)
    83  	hash.Write(data)
    84  	return hash.Sum(nil), nil
    85  }