kdf.go

v1.0.0
Doc Versions Source
1
package crypto
2
3
import (
4
	"crypto/hmac"
5
	"crypto/sha256"
6
	"fmt"
7
	"strings"
8
9
	"golang.org/x/crypto/argon2"
10
	"golang.org/x/crypto/pbkdf2"
11
)
12
13
// KdfType represents the key derivation function type.
14
type KdfType int
15
16
const (
17
	KdfPBKDF2   KdfType = 0
18
	KdfArgon2id KdfType = 1
19
)
20
21
// DeriveKeyConfig holds KDF parameters returned by prelogin.
22
type DeriveKeyConfig struct {
23
	KdfType        KdfType
24
	KdfIterations  int
25
	KdfMemory      int // Argon2id only, in MB
26
	KdfParallelism int // Argon2id only
27
}
28
29
// DeriveMasterKey derives the master key from the master password and email.
30
func DeriveMasterKey(password, email string, cfg DeriveKeyConfig) ([]byte, error) {
31
	salt := []byte(strings.ToLower(email))
32
33
	switch cfg.KdfType {
34
	case KdfPBKDF2:
35
		return pbkdf2.Key([]byte(password), salt, cfg.KdfIterations, 32, sha256.New), nil
36
	case KdfArgon2id:
37
		if cfg.KdfMemory <= 0 || cfg.KdfParallelism <= 0 || cfg.KdfIterations <= 0 {
38
			return nil, fmt.Errorf("invalid argon2id parameters: memory=%d parallelism=%d iterations=%d",
39
				cfg.KdfMemory, cfg.KdfParallelism, cfg.KdfIterations)
40
		}
41
		// Argon2id uses a SHA256 hash of the email as salt
42
		saltHash := sha256.Sum256(salt)
43
		return argon2.IDKey([]byte(password), saltHash[:], uint32(cfg.KdfIterations), uint32(cfg.KdfMemory*1024), uint8(cfg.KdfParallelism), 32), nil
44
	default:
45
		return nil, fmt.Errorf("unsupported KDF type: %d", cfg.KdfType)
46
	}
47
}
48
49
// HashMasterPassword hashes the master key with the password for server auth.
50
// This is what gets sent to the server as the "master password hash".
51
func HashMasterPassword(masterKey []byte, password string) []byte {
52
	return pbkdf2.Key(masterKey, []byte(password), 1, 32, sha256.New)
53
}
54
55
// StretchMasterKey expands the 32-byte master key to 64 bytes (32 enc + 32 mac)
56
// using HKDF-expand with SHA256.
57
func StretchMasterKey(masterKey []byte) (encKey, macKey []byte) {
58
	encKey = hkdfExpand(masterKey, []byte("enc"), 32)
59
	macKey = hkdfExpand(masterKey, []byte("mac"), 32)
60
	return
61
}
62
63
// HKDFExpandSlice performs HKDF-expand using HMAC-SHA256 to derive a key of the given length.
64
// Supports multi-block expansion for lengths > 32 bytes.
65
func HKDFExpandSlice(key, info []byte, length int) []byte {
66
	hashLen := sha256.Size
67
	n := (length + hashLen - 1) / hashLen
68
	out := make([]byte, 0, n*hashLen)
69
	var prev []byte
70
	for i := 1; i <= n; i++ {
71
		h := hmac.New(sha256.New, key)
72
		h.Write(prev)
73
		h.Write(info)
74
		h.Write([]byte{byte(i)})
75
		prev = h.Sum(nil)
76
		out = append(out, prev...)
77
	}
78
	return out[:length]
79
}
80
81
// hkdfExpand performs a single-step HKDF-expand using HMAC-SHA256.
82
func hkdfExpand(key, info []byte, length int) []byte {
83
	return HKDFExpandSlice(key, info, length)
84
}
85

Source Files