github.com/jcmturner/gokrb5/v8@v8.4.4/crypto/rfc8009/keyDerivation.go (about)

     1  package rfc8009
     2  
     3  import (
     4  	"crypto/hmac"
     5  	"encoding/binary"
     6  	"encoding/hex"
     7  	"errors"
     8  
     9  	"github.com/jcmturner/gokrb5/v8/crypto/etype"
    10  	"github.com/jcmturner/gokrb5/v8/iana/etypeID"
    11  	"golang.org/x/crypto/pbkdf2"
    12  )
    13  
    14  const (
    15  	s2kParamsZero = 32768
    16  )
    17  
    18  // DeriveRandom for key derivation as defined in RFC 8009
    19  func DeriveRandom(protocolKey, usage []byte, e etype.EType) ([]byte, error) {
    20  	h := e.GetHashFunc()()
    21  	return KDF_HMAC_SHA2(protocolKey, []byte("prf"), usage, h.Size(), e), nil
    22  }
    23  
    24  // DeriveKey derives a key from the protocol key based on the usage and the etype's specific methods.
    25  //
    26  // https://tools.ietf.org/html/rfc8009#section-5
    27  func DeriveKey(protocolKey, label []byte, e etype.EType) []byte {
    28  	var context []byte
    29  	var kl int
    30  	// Key length is longer for aes256-cts-hmac-sha384-192 is it is a Ke or from StringToKey (where label is "kerberos")
    31  	if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 {
    32  	Swtch:
    33  		switch label[len(label)-1] {
    34  		case 0x73:
    35  			// 0x73 is "s" so label could be kerberos meaning StringToKey so now check if the label is "kerberos"
    36  			kerblabel := []byte("kerberos")
    37  			if len(label) != len(kerblabel) {
    38  				break
    39  			}
    40  			for i, b := range label {
    41  				if b != kerblabel[i] {
    42  					kl = e.GetKeySeedBitLength()
    43  					break Swtch
    44  				}
    45  			}
    46  			if kl == 0 {
    47  				// This is StringToKey
    48  				kl = 256
    49  			}
    50  		case 0xAA:
    51  			// This is a Ke
    52  			kl = 256
    53  		}
    54  	}
    55  	if kl == 0 {
    56  		kl = e.GetKeySeedBitLength()
    57  	}
    58  	return e.RandomToKey(KDF_HMAC_SHA2(protocolKey, label, context, kl, e))
    59  }
    60  
    61  // RandomToKey returns a key from the bytes provided according to the definition in RFC 8009.
    62  func RandomToKey(b []byte) []byte {
    63  	return b
    64  }
    65  
    66  // StringToKey returns a key derived from the string provided according to the definition in RFC 8009.
    67  func StringToKey(secret, salt, s2kparams string, e etype.EType) ([]byte, error) {
    68  	i, err := S2KparamsToItertions(s2kparams)
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  	return StringToKeyIter(secret, salt, i, e)
    73  }
    74  
    75  // StringToKeyIter returns a key derived from the string provided according to the definition in RFC 8009.
    76  func StringToKeyIter(secret, salt string, iterations int, e etype.EType) ([]byte, error) {
    77  	tkey := e.RandomToKey(StringToPBKDF2(secret, salt, iterations, e))
    78  	return e.DeriveKey(tkey, []byte("kerberos"))
    79  }
    80  
    81  // StringToPBKDF2 generates an encryption key from a pass phrase and salt string using the PBKDF2 function from PKCS #5 v2.0
    82  func StringToPBKDF2(secret, salt string, iterations int, e etype.EType) []byte {
    83  	kl := e.GetKeyByteSize()
    84  	if e.GetETypeID() == etypeID.AES256_CTS_HMAC_SHA384_192 {
    85  		kl = 32
    86  	}
    87  	return pbkdf2.Key([]byte(secret), []byte(salt), iterations, kl, e.GetHashFunc())
    88  }
    89  
    90  // KDF_HMAC_SHA2 key derivation: https://tools.ietf.org/html/rfc8009#section-3
    91  func KDF_HMAC_SHA2(protocolKey, label, context []byte, kl int, e etype.EType) []byte {
    92  	//k: Length in bits of the key to be outputted, expressed in big-endian binary representation in 4 bytes.
    93  	k := make([]byte, 4, 4)
    94  	binary.BigEndian.PutUint32(k, uint32(kl))
    95  
    96  	c := make([]byte, 4, 4)
    97  	binary.BigEndian.PutUint32(c, uint32(1))
    98  	c = append(c, label...)
    99  	c = append(c, byte(0))
   100  	if len(context) > 0 {
   101  		c = append(c, context...)
   102  	}
   103  	c = append(c, k...)
   104  
   105  	mac := hmac.New(e.GetHashFunc(), protocolKey)
   106  	mac.Write(c)
   107  	return mac.Sum(nil)[:(kl / 8)]
   108  }
   109  
   110  // GetSaltP returns the salt value based on the etype name: https://tools.ietf.org/html/rfc8009#section-4
   111  func GetSaltP(salt, ename string) string {
   112  	b := []byte(ename)
   113  	b = append(b, byte(0))
   114  	b = append(b, []byte(salt)...)
   115  	return string(b)
   116  }
   117  
   118  // S2KparamsToItertions converts the string representation of iterations to an integer for RFC 8009.
   119  func S2KparamsToItertions(s2kparams string) (int, error) {
   120  	var i uint32
   121  	if len(s2kparams) != 8 {
   122  		return s2kParamsZero, errors.New("Invalid s2kparams length")
   123  	}
   124  	b, err := hex.DecodeString(s2kparams)
   125  	if err != nil {
   126  		return s2kParamsZero, errors.New("Invalid s2kparams, cannot decode string to bytes")
   127  	}
   128  	i = binary.BigEndian.Uint32(b)
   129  	//buf := bytes.NewBuffer(b)
   130  	//err = binary.Read(buf, binary.BigEndian, &i)
   131  	if err != nil {
   132  		return s2kParamsZero, errors.New("Invalid s2kparams, cannot convert to big endian int32")
   133  	}
   134  	return int(i), nil
   135  }