mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
crypto/hkdf: init package
This commit imports the x/crypto/hkdf package as a public crypto package based on the linked proposal. Since we've already implemented this internal to the FIPS boundary (mod some small changes based on the proposal discussion) this largely defers to that implementation. Updates #61477 Change-Id: Ie3dcee75314dfbe22eec8b31c43c926fe80637bb Reviewed-on: https://go-review.googlesource.com/c/go/+/630296 Reviewed-by: Filippo Valsorda <filippo@golang.org> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> Reviewed-by: Russ Cox <rsc@golang.org> Auto-Submit: Filippo Valsorda <filippo@golang.org>
This commit is contained in:
parent
97ae1817fb
commit
fab2b8b0fa
3
api/next/61477.txt
Normal file
3
api/next/61477.txt
Normal file
@ -0,0 +1,3 @@
|
||||
pkg crypto/hkdf, func Expand[$0 hash.Hash](func() $0, []uint8, string, int) ([]uint8, error) #61477
|
||||
pkg crypto/hkdf, func Extract[$0 hash.Hash](func() $0, []uint8, []uint8) ([]uint8, error) #61477
|
||||
pkg crypto/hkdf, func Key[$0 hash.Hash](func() $0, []uint8, []uint8, string, int) ([]uint8, error) #61477
|
2
doc/next/6-stdlib/3-hkdf.md
Normal file
2
doc/next/6-stdlib/3-hkdf.md
Normal file
@ -0,0 +1,2 @@
|
||||
A new `crypto/hkdf` package was added based on the pre-existing
|
||||
`golang.org/x/crypto/hkdf` package. <!-- go.dev/issue/61477 -->
|
1
doc/next/6-stdlib/99-minor/crypto/hkdf/61477.md
Normal file
1
doc/next/6-stdlib/99-minor/crypto/hkdf/61477.md
Normal file
@ -0,0 +1 @@
|
||||
<!-- This is a new package; covered in 6-stdlib/3-hkdf.md. -->
|
53
src/crypto/hkdf/example_test.go
Normal file
53
src/crypto/hkdf/example_test.go
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2014 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_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hkdf"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Usage example that expands one master secret into three other
|
||||
// cryptographically secure keys.
|
||||
func Example_usage() {
|
||||
// Underlying hash function for HMAC.
|
||||
hash := sha256.New
|
||||
keyLen := hash().Size()
|
||||
|
||||
// Cryptographically secure master secret.
|
||||
secret := []byte{0x00, 0x01, 0x02, 0x03} // i.e. NOT this.
|
||||
|
||||
// Non-secret salt, optional (can be nil).
|
||||
// Recommended: hash-length random value.
|
||||
salt := make([]byte, hash().Size())
|
||||
if _, err := rand.Read(salt); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Non-secret context info, optional (can be nil).
|
||||
info := "hkdf example"
|
||||
|
||||
// Generate three 128-bit derived keys.
|
||||
var keys [][]byte
|
||||
for i := 0; i < 3; i++ {
|
||||
key, err := hkdf.Key(hash, secret, salt, info, keyLen)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
for i := range keys {
|
||||
fmt.Printf("Key #%d: %v\n", i+1, !bytes.Equal(keys[i], make([]byte, 16)))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// Key #1: true
|
||||
// Key #2: true
|
||||
// Key #3: true
|
||||
}
|
49
src/crypto/hkdf/hkdf.go
Normal file
49
src/crypto/hkdf/hkdf.go
Normal file
@ -0,0 +1,49 @@
|
||||
// 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
|
||||
}
|
@ -2,15 +2,12 @@
|
||||
// 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"
|
||||
@ -301,19 +298,30 @@ var hkdfTests = []hkdfTest{
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
if !bytes.Equal(key, tt.out) {
|
||||
t.Errorf("test %d: incorrect output: have %v, need %v.", i, key, 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -321,19 +329,52 @@ func TestHKDF(t *testing.T) {
|
||||
func TestHKDFLimit(t *testing.T) {
|
||||
hash := sha1.New
|
||||
master := []byte{0x00, 0x01, 0x02, 0x03}
|
||||
info := []byte{}
|
||||
info := ""
|
||||
limit := hash().Size() * 255
|
||||
|
||||
// The maximum output bytes should be extractable
|
||||
limit := hash().Size() * 255
|
||||
hkdf.Key(hash, master, nil, info, limit)
|
||||
out, err := Key(hash, master, nil, info, limit)
|
||||
if err != nil || len(out) != limit {
|
||||
t.Errorf("key derivation failed: %v", err)
|
||||
}
|
||||
|
||||
// Reading one more should panic
|
||||
defer func() {
|
||||
if err := recover(); err == nil {
|
||||
t.Error("expected panic")
|
||||
// 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) {
|
||||
benchmarkHKDF(md5.New, 16, b)
|
||||
}
|
||||
|
||||
func Benchmark20ByteSHA1Single(b *testing.B) {
|
||||
benchmarkHKDF(sha1.New, 20, b)
|
||||
}
|
||||
|
||||
func Benchmark32ByteSHA256Single(b *testing.B) {
|
||||
benchmarkHKDF(sha256.New, 32, b)
|
||||
}
|
||||
|
||||
func Benchmark64ByteSHA512Single(b *testing.B) {
|
||||
benchmarkHKDF(sha512.New, 64, 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 := string([]byte{0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27})
|
||||
|
||||
b.SetBytes(int64(block))
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := Key(hasher, master, salt, info, hasher().Size())
|
||||
if err != nil {
|
||||
b.Errorf("failed to derive key: %v", err)
|
||||
}
|
||||
}()
|
||||
hkdf.Key(hash, master, nil, info, limit+1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFIPSServiceIndicator(t *testing.T) {
|
||||
@ -342,51 +383,28 @@ func TestFIPSServiceIndicator(t *testing.T) {
|
||||
}
|
||||
|
||||
fips140.ResetServiceIndicator()
|
||||
hkdf.Key(sha256.New, []byte("YELLOW SUBMARINE"), nil, nil, 32)
|
||||
_, 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()
|
||||
hkdf.Key(sha256.New, []byte("key"), nil, nil, 32)
|
||||
_, 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()
|
||||
hkdf.Key(sha256.New, []byte("YELLOW SUBMARINE"), []byte("salt"), []byte("info"), 32)
|
||||
_, err = Key(sha256.New, []byte("YELLOW SUBMARINE"), []byte("salt"), "info", 32)
|
||||
if !fips140.ServiceIndicator() {
|
||||
t.Error("FIPS service indicator should be set")
|
||||
}
|
||||
}
|
||||
|
||||
func Benchmark16ByteMD5Single(b *testing.B) {
|
||||
benchmarkHKDFSingle(md5.New, 16, b)
|
||||
}
|
||||
|
||||
func Benchmark20ByteSHA1Single(b *testing.B) {
|
||||
benchmarkHKDFSingle(sha1.New, 20, b)
|
||||
}
|
||||
|
||||
func Benchmark32ByteSHA256Single(b *testing.B) {
|
||||
benchmarkHKDFSingle(sha256.New, 32, b)
|
||||
}
|
||||
|
||||
func Benchmark64ByteSHA512Single(b *testing.B) {
|
||||
benchmarkHKDFSingle(sha512.New, 64, b)
|
||||
}
|
||||
|
||||
func benchmarkHKDFSingle(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}
|
||||
|
||||
b.SetBytes(int64(block))
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
hkdf.Key(hasher, master, salt, info, block)
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ func init() {
|
||||
0xa6, 0xc1, 0xde, 0x42, 0x4f, 0x2c, 0x99, 0x60,
|
||||
0x64, 0xdb, 0x66, 0x3e, 0xec, 0xa6, 0x37, 0xff,
|
||||
}
|
||||
got := Key(sha256.New, input, input, input, len(want))
|
||||
got := Key(sha256.New, input, input, string(input), len(want))
|
||||
if !bytes.Equal(got, want) {
|
||||
return errors.New("unexpected result")
|
||||
}
|
||||
|
@ -19,10 +19,11 @@ func Extract[H fips140.Hash](h func() H, secret, salt []byte) []byte {
|
||||
extractor := hmac.New(h, salt)
|
||||
hmac.MarkAsUsedInHKDF(extractor)
|
||||
extractor.Write(secret)
|
||||
|
||||
return extractor.Sum(nil)
|
||||
}
|
||||
|
||||
func Expand[H fips140.Hash](h func() H, pseudorandomKey, info []byte, keyLen int) []byte {
|
||||
func Expand[H fips140.Hash](h func() H, pseudorandomKey []byte, info string, keyLen int) []byte {
|
||||
out := make([]byte, 0, keyLen)
|
||||
expander := hmac.New(h, pseudorandomKey)
|
||||
hmac.MarkAsUsedInHKDF(expander)
|
||||
@ -38,7 +39,7 @@ func Expand[H fips140.Hash](h func() H, pseudorandomKey, info []byte, keyLen int
|
||||
expander.Reset()
|
||||
}
|
||||
expander.Write(buf)
|
||||
expander.Write(info)
|
||||
expander.Write([]byte(info))
|
||||
expander.Write([]byte{counter})
|
||||
buf = expander.Sum(buf[:0])
|
||||
remain := keyLen - len(out)
|
||||
@ -49,7 +50,7 @@ func Expand[H fips140.Hash](h func() H, pseudorandomKey, info []byte, keyLen int
|
||||
return out
|
||||
}
|
||||
|
||||
func Key[H fips140.Hash](h func() H, secret, salt, info []byte, keyLen int) []byte {
|
||||
func Key[H fips140.Hash](h func() H, secret, salt []byte, info string, keyLen int) []byte {
|
||||
prk := Extract(h, secret, salt)
|
||||
return Expand(h, prk, info, keyLen)
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ func ExpandLabel[H fips140.Hash](hash func() H, secret []byte, label string, con
|
||||
hkdfLabel = append(hkdfLabel, label...)
|
||||
hkdfLabel = append(hkdfLabel, byte(len(context)))
|
||||
hkdfLabel = append(hkdfLabel, context...)
|
||||
return hkdf.Expand(hash, secret, hkdfLabel, length)
|
||||
return hkdf.Expand(hash, secret, string(hkdfLabel), length)
|
||||
}
|
||||
|
||||
func extract[H fips140.Hash](hash func() H, newSecret, currentSecret []byte) []byte {
|
||||
|
@ -42,7 +42,7 @@ func (kdf *hkdfKDF) LabeledExpand(suiteID []byte, randomKey []byte, label string
|
||||
labeledInfo = append(labeledInfo, suiteID...)
|
||||
labeledInfo = append(labeledInfo, label...)
|
||||
labeledInfo = append(labeledInfo, info...)
|
||||
return hkdf.Expand(kdf.hash.New, randomKey, labeledInfo, int(length))
|
||||
return hkdf.Expand(kdf.hash.New, randomKey, string(labeledInfo), int(length))
|
||||
}
|
||||
|
||||
// dhKEM implements the KEM specified in RFC 9180, Section 4.1.
|
||||
|
@ -511,7 +511,7 @@ var depsRules = `
|
||||
|
||||
crypto/boring
|
||||
< crypto/aes, crypto/des, crypto/hmac, crypto/md5, crypto/rc4,
|
||||
crypto/sha1, crypto/sha256, crypto/sha512;
|
||||
crypto/sha1, crypto/sha256, crypto/sha512, crypto/hkdf;
|
||||
|
||||
crypto/boring, crypto/internal/fips140/edwards25519/field
|
||||
< crypto/ecdh;
|
||||
@ -530,7 +530,8 @@ var depsRules = `
|
||||
crypto/sha1,
|
||||
crypto/sha256,
|
||||
crypto/sha512,
|
||||
golang.org/x/crypto/sha3
|
||||
golang.org/x/crypto/sha3,
|
||||
crypto/hkdf
|
||||
< CRYPTO;
|
||||
|
||||
CGO, fmt, net !< CRYPTO;
|
||||
|
Loading…
x
Reference in New Issue
Block a user