Key Expansion and Stretching through Key Derivation Function
Introduction
When handling multiple crypto keys I wondered: “More keys to manage means more to defend,” and “How can we prevent users from using very simple keys that make cracking easy?” There’s a dedicated algorithms for these problems: Key Derivation Functions (KDFs).
Key Derivation Function (KDF)
Transform a secret (for example a user-entered password or a single master key) into one or more cryptographically strong keys
Key Expansion
Avoid large-scale key management by deriving many keys from a single key via key expansion
By using a master key plus different “info”, you can dynamically derive independent subkeys for each purpose. Even if one subkey is leaked, that does not imply the master key or other-purpose keys can be recovered. This addresses the following problems caused by having many keys:
- Complex management costs (key rotation, permissions, deployment synchronization)
- Increased risk of leakage (maintaining consistency in practice)
package main
import ( "crypto/sha256" "fmt" "io"
"golang.org/x/crypto/hkdf")
func deriveKey(masterKey []byte, info string) ([]byte, error) { hkdfReader := hkdf.New( sha256.New, masterKey, nil, []byte(info), // domain separation, to prevent keys with the same purpose from being generated in different scenarios.
)
key := make([]byte, 32)
_, err := io.ReadFull(hkdfReader, key) if err != nil { return nil, err }
return key, nil}
func main() { masterKey := []byte("super-secret-master-key")
jwtKey, _ := deriveKey(masterKey, "jwt-sign") aesKey, _ := deriveKey(masterKey, "aes-encryption")
fmt.Printf("JWT Key: %x\n", jwtKey) fmt.Printf("AES Key: %x\n", aesKey)}Key Stretching
Prevent weak keys (such as user passwords) from being easily cracked by increasing the cost of cracking through key stretching
Add a “salt” during derivation and make the computation to be very time-consuming or highly memory-intensive. Even if an attacker steals the database, they cannot easily use rainbow tables or brute-force attacks.
package main
import ( "crypto/rand" "encoding/hex" "fmt" "log"
"golang.org/x/crypto/argon2")
// GenerateSalt generates a random salt of the specified length// The salt value does not need to be kept secret, but it should be unique for each user or for each derivative.func GenerateSalt(size int) ([]byte, error) { salt := make([]byte, size) _, err := rand.Read(salt) if err != nil { return nil, err } return salt, nil}
func main() { // Scenario: The user set a very weak password userPassword := "iloveyou123" fmt.Printf("Original password entered: %s\n", userPassword)
// 1. Generate a 16-byte random Salt // In practical case, this Salt will be stored in the database along with the encrypted data or hash. salt, err := GenerateSalt(16) if err != nil { log.Fatalf("Failed to generate Salt: %v", err) }
// 2. Setting Argon2id parameters (determines cracking difficulty) // These parameters directly affect computation time and memory consumption, and can be adjusted according to server performance. time := uint32(1) // Time cost memory := uint32(64 * 1024) // Memory cost, 64MB threads := uint8(4) // Degree of parallelism keyLength := uint32(32) // We expect the output key length to be 32 bytes = 256 bits (suitable for AES-256).
// 3. Execution key derivation derivedKey := argon2.IDKey([]byte(userPassword), salt, time, memory, threads, keyLength)
// 4. Output Results fmt.Println("--------------------------------------------------") fmt.Printf("Generated Salt (Hex) : %s\n", hex.EncodeToString(salt)) fmt.Printf("Derived 256-bit Key: %s\n", hex.EncodeToString(derivedKey)) fmt.Println("--------------------------------------------------")}Note that increasing the cost of cracking does not mean a password becomes absolutely secure; a weak password is still a weak password — only the cost of guessing it can be controlled and raised.
Conclusion
KDFs mainly mitigate human shortcomings in key handling: “weak memory” and “not following best practices,” by using algorithms to reduce those risks.
- Key Derivation Functions
- Key Expansion: derive multiple child keys from an already-strong master key
- Key Stretching: turn an easily brute-forced weak password into a high-cost key that significantly raises cracking effort
In practice, it is possible and often necessary to use two rounds of key derivation: first to convert a weak password into a high-cost master key, and then to expand that master key into multiple purpose-specific high-cost keys.