--- /dev/null
+// Copyright 2024 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package hkdf
+
+import (
+ "crypto/internal/fips140/hkdf"
+ "errors"
+ "hash"
+)
+
+// Extract generates a pseudorandom key for use with [Expand] from an input
+// secret and an optional independent salt.
+//
+// Only use this function if you need to reuse the extracted key with multiple
+// Expand invocations and different context values. Most common scenarios,
+// including the generation of multiple keys, should use [Key] instead.
+func Extract[H hash.Hash](h func() H, secret, salt []byte) ([]byte, error) {
+ return hkdf.Extract(h, secret, salt), nil
+}
+
+// Expand derives a key from the given hash, key, and optional context info,
+// returning a []byte of length keyLength that can be used as cryptographic key.
+// The extraction step is skipped.
+//
+// The key should have been generated by [Extract], or be a uniformly
+// random or pseudorandom cryptographically strong key. See RFC 5869, Section
+// 3.3. Most common scenarios will want to use [Key] instead.
+func Expand[H hash.Hash](h func() H, pseudorandomKey []byte, info string, keyLength int) ([]byte, error) {
+ limit := h().Size() * 255
+ if keyLength > limit {
+ return nil, errors.New("hkdf: requested key length too large")
+ }
+
+ return hkdf.Expand(h, pseudorandomKey, info, keyLength), nil
+}
+
+// Key derives a key from the given hash, secret, salt and context info,
+// returning a []byte of length keyLength that can be used as cryptographic key.
+// Salt and info can be nil.
+func Key[Hash hash.Hash](h func() Hash, secret, salt []byte, info string, keyLength int) ([]byte, error) {
+ limit := h().Size() * 255
+ if keyLength > limit {
+ return nil, errors.New("hkdf: requested key length too large")
+ }
+
+ return hkdf.Key(h, secret, salt, info, keyLength), nil
+}
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
-package fipstest_test
-
-// TODO(fips, #61477): move this to crypto/hkdf once it exists.
+package hkdf
import (
"bytes"
"crypto/internal/boring"
"crypto/internal/fips140"
- "crypto/internal/fips140/hkdf"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
func TestHKDF(t *testing.T) {
for i, tt := range hkdfTests {
- prk := hkdf.Extract(tt.hash, tt.master, tt.salt)
+ prk, err := Extract(tt.hash, tt.master, tt.salt)
+ if err != nil {
+ t.Errorf("test %d: PRK extraction failed: %v", i, err)
+ }
if !bytes.Equal(prk, tt.prk) {
t.Errorf("test %d: incorrect PRK: have %v, need %v.", i, prk, tt.prk)
}
- out := hkdf.Key(tt.hash, tt.master, tt.salt, tt.info, len(tt.out))
- if !bytes.Equal(out, tt.out) {
- t.Errorf("test %d: incorrect output: have %v, need %v.", i, out, tt.out)
+ key, err := Key(tt.hash, tt.master, tt.salt, string(tt.info), len(tt.out))
+ if err != nil {
+ t.Errorf("test %d: key derivation failed: %v", i, err)
+ }
+
+ if !bytes.Equal(key, tt.out) {
+ t.Errorf("test %d: incorrect output: have %v, need %v.", i, key, tt.out)
}
- out = hkdf.Expand(tt.hash, prk, tt.info, len(tt.out))
- if !bytes.Equal(out, tt.out) {
- t.Errorf("test %d: incorrect output from Expand: have %v, need %v.", i, out, tt.out)
+ expanded, err := Expand(tt.hash, prk, string(tt.info), len(tt.out))
+ if err != nil {
+ t.Errorf("test %d: key expansion failed: %v", i, err)
+ }
+
+ if !bytes.Equal(expanded, tt.out) {
+ t.Errorf("test %d: incorrect output from Expand: have %v, need %v.", i, expanded, tt.out)
}
}
}
func TestHKDFLimit(t *testing.T) {
hash := sha1.New
master := []byte{0x00, 0x01, 0x02, 0x03}
- info := []byte{}
-
- // The maximum output bytes should be extractable
+ info := ""
limit := hash().Size() * 255
- hkdf.Key(hash, master, nil, info, limit)
-
- // Reading one more should panic
- defer func() {
- if err := recover(); err == nil {
- t.Error("expected panic")
- }
- }()
- hkdf.Key(hash, master, nil, info, limit+1)
-}
-
-func TestFIPSServiceIndicator(t *testing.T) {
- if boring.Enabled {
- t.Skip("in BoringCrypto mode HMAC is not from the Go FIPS module")
- }
-
- fips140.ResetServiceIndicator()
- hkdf.Key(sha256.New, []byte("YELLOW SUBMARINE"), nil, nil, 32)
- if !fips140.ServiceIndicator() {
- t.Error("FIPS service indicator should be set")
- }
- // Key too short.
- fips140.ResetServiceIndicator()
- hkdf.Key(sha256.New, []byte("key"), nil, nil, 32)
- if fips140.ServiceIndicator() {
- t.Error("FIPS service indicator should not be set")
+ // The maximum output bytes should be extractable
+ out, err := Key(hash, master, nil, info, limit)
+ if err != nil || len(out) != limit {
+ t.Errorf("key derivation failed: %v", err)
}
- // Salt and info are short, which is ok, but translates to a short HMAC key.
- fips140.ResetServiceIndicator()
- hkdf.Key(sha256.New, []byte("YELLOW SUBMARINE"), []byte("salt"), []byte("info"), 32)
- if !fips140.ServiceIndicator() {
- t.Error("FIPS service indicator should be set")
+ // Reading one more should return an error
+ _, err = Key(hash, master, nil, info, limit+1)
+ if err == nil {
+ t.Error("expected key derivation to fail, but it succeeded")
}
}
func Benchmark16ByteMD5Single(b *testing.B) {
- benchmarkHKDFSingle(md5.New, 16, b)
+ benchmarkHKDF(md5.New, 16, b)
}
func Benchmark20ByteSHA1Single(b *testing.B) {
- benchmarkHKDFSingle(sha1.New, 20, b)
+ benchmarkHKDF(sha1.New, 20, b)
}
func Benchmark32ByteSHA256Single(b *testing.B) {
- benchmarkHKDFSingle(sha256.New, 32, b)
+ benchmarkHKDF(sha256.New, 32, b)
}
func Benchmark64ByteSHA512Single(b *testing.B) {
- benchmarkHKDFSingle(sha512.New, 64, b)
+ benchmarkHKDF(sha512.New, 64, b)
}
-func benchmarkHKDFSingle(hasher func() hash.Hash, block int, b *testing.B) {
+func benchmarkHKDF(hasher func() hash.Hash, block int, b *testing.B) {
master := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}
salt := []byte{0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17}
- info := []byte{0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27}
+ info := string([]byte{0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27})
b.SetBytes(int64(block))
b.ResetTimer()
for i := 0; i < b.N; i++ {
- hkdf.Key(hasher, master, salt, info, block)
+ _, err := Key(hasher, master, salt, info, hasher().Size())
+ if err != nil {
+ b.Errorf("failed to derive key: %v", err)
+ }
+ }
+}
+
+func TestFIPSServiceIndicator(t *testing.T) {
+ if boring.Enabled {
+ t.Skip("in BoringCrypto mode HMAC is not from the Go FIPS module")
+ }
+
+ fips140.ResetServiceIndicator()
+ _, err := Key(sha256.New, []byte("YELLOW SUBMARINE"), nil, "", 32)
+ if err != nil {
+ panic(err)
+ }
+ if !fips140.ServiceIndicator() {
+ t.Error("FIPS service indicator should be set")
+ }
+
+ // Key too short.
+ fips140.ResetServiceIndicator()
+ _, err = Key(sha256.New, []byte("key"), nil, "", 32)
+ if err != nil {
+ panic(err)
+ }
+ if fips140.ServiceIndicator() {
+ t.Error("FIPS service indicator should not be set")
+ }
+
+ // Salt and info are short, which is ok, but translates to a short HMAC key.
+ fips140.ResetServiceIndicator()
+ _, err = Key(sha256.New, []byte("YELLOW SUBMARINE"), []byte("salt"), "info", 32)
+ if !fips140.ServiceIndicator() {
+ t.Error("FIPS service indicator should be set")
}
}