Go
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions and Methods
-
Section 4: Concurrency
-
Section 5: Packages and Tooling
-
Section 6: More Standard Library
-
Section 7: Building Services
-
Section 8: Advanced Go
-
Section 9: Go in the Cloud-Native Ecosystem
-
Section 10: Data Structures and Algorithms in Go
-
Section 11: Testing and Deployment
-
Section 12: Practical Projects
-
Section 13: More Standard Library Practice
-
Section 14: More Practice Projects
-
Section 15: Design Patterns in Go
-
Section 16: Interview Practice
-
Section 17: Package fmt In Depth
-
Section 18: Package strings and strconv
-
Section 19: Package os and io
-
Section 20: Package time
-
Section 21: Package sort and container
-
Section 22: Package encoding
-
Section 23: Package net/http In Depth
-
Section 24: Package context
-
Section 25: Package regexp and bytes
-
Section 26: Package errors In Depth
-
Section 27: Package crypto and hash
-
Section 28: Package flag and log
-
Section 29: Package sync In Depth
-
Section 30: More Practice Exercises
-
Section 31: Go Modules and Workspaces In Depth
-
Section 32: Generics Deep Dive (Go 1.18+)
-
Section 33: Testing Package In Depth
-
Section 34: More Interview and Whiteboard Practice
-
Section 35: Package math and unicode
-
Section 36: Package path and filepath
-
Section 37: Package database/sql
-
Section 38: Package text/template and html/template
-
Section 39: Package archive and compress
-
Section 40: Lower-Level net Package
-
Section 41: Package runtime and reflect
-
Section 42: Package embed and io/fs
-
Section 43: Go Assembly and CGO Basics
-
Section 44: Building CLIs and TUIs
-
Section 45: Go Performance Tuning
-
Section 46: More Real-World Projects
-
Section 47: Go in Production
-
Section 48: Go Design Patterns
-
Section 49: Go Interfaces Deep Dive
-
Section 50: Final Practice and Review
153: HMAC with crypto/hmac
Let's say we're building a system where our server sends a webhook notification to a partner's API whenever a payment is processed. We can't just send the payment ID and amount in plain JSON; otherwise, anyone who finds that endpoint could spoof a "Payment Successful" request to the partner's server and trick them into shipping a product for free.
The problem with a simple hash
My first instinct might be to just hash the payload using SHA-256 and send that hash along as a signature. I'll try that out quickly.
package main
import (
"crypto/sha256"
"fmt"
)
func main() {
payload := "order_id=123&amount=49.99"
hash := sha256.Sum256([]byte(payload))
fmt.Printf("Signature: %x\n", hash)
}
Okay, that gives me a fingerprint. But here's the flaw: if an attacker intercepts the request, they can change the amount to 0.01, calculate the SHA-256 of that new string, and replace the signature. The receiver would see a valid hash and assume everything is fine. A hash is for integrity, but it isn't for authenticity. I need a shared secret.
Trying to glue a key onto the hash
I could just prepend a secret key to the data before hashing it. It seems intuitive: sha256(secret + data). Let's see how that looks.
secret := "my-super-secret-key"
payload := "order_id=123&amount=49.99"
combined := secret + payload
hash := sha256.Sum256([]byte(combined))
This feels better, but this is actually a classic security mistake. Doing a simple concatenation makes the hash vulnerable to something called a "length extension attack." Depending on the hashing algorithm, an attacker can sometimes append more data to the end of the message and calculate a new valid hash without ever knowing the secret key. I don't want to be the guy who introduces a vulnerability because I tried to "roll my own" security logic.
Let's do this the right way with crypto/hmac
This is exactly why crypto/hmac exists. HMAC (Hash-based Message Authentication Code) is a specific construction that hashes the key and the message in a way that prevents those length extension attacks. It's the industry standard for this exact scenario.
Let's rewrite this. I'll use SHA-256 as the underlying hash function, but wrapped in the HMAC package.
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
)
func main() {
secret := []byte("my-super-secret-key")
payload := []byte("order_id=123&amount=49.99")
// hmac.New takes the hash function constructor and the key
h := hmac.New(sha256.New, secret)
// We write the data just like we do with a normal hash.Hash
h.Write(payload)
// h.Sum(nil) returns the final HMAC slice
signature := h.Sum(nil)
fmt.Printf("HMAC Signature: %s\n", hex.EncodeToString(signature))
}
Notice how hmac.New doesn't take a hash value, but the sha256.New function itself. This allows the HMAC package to manage the internal padding and double-hashing process that makes it secure. I just treat it like any other hash.Hash object: write the data, then call Sum.
The subtle danger of string comparison
Now, when the partner server receives this, they'll generate their own HMAC using the same secret and compare it to the one I sent. You might be tempted to just use if sigA == sigB or bytes.Equal(sigA, sigB). Stop right there.
Standard comparison operators return false as soon as they find the first differing byte. This means a request with a signature that is "almost" correct takes slightly longer to process than one that is completely wrong. An attacker can use a high-resolution timer to guess the signature byte-by-byte. This is a "timing attack."
To stop this, Go provides hmac.Equal, which performs a constant-time comparison regardless of whether the values match.
receivedSig, _ := hex.DecodeString(incomingHeader)
expectedSig := h.Sum(nil)
if hmac.Equal(receivedSig, expectedSig) {
fmt.Println("Authenticity verified!")
} else {
fmt.Println("Invalid signature!")
}
Always use hmac.Equal for cryptographic keys or signatures. It's a small change that closes a huge security hole.
📋 Practical Task
Build a Secure API Request Signer
You are tasked with creating a utility that signs API requests to prevent tampering. Create a program that does the following:
- Defines a secret key (as a byte slice) and a message containing a JSON-like string (e.g.,
{"user_id": 42, "action": "delete_account"}). - Implements a function
GenerateSignature(message, key []byte) stringthat returns the HMAC-SHA256 signature encoded as a hexadecimal string. - Implements a function
VerifySignature(message, key []byte, signature string) boolthat decodes the provided hex signature and useshmac.Equalto verify if it matches the generated signature for that message and key. - In
main, demonstrate a successful verification and a failed verification (by altering one character in the message).
There are no comments for now.