go
37 lines · 5 steps
Signing and verifying payloads with HMAC in Go
A small Signer type that produces and checks HMAC-SHA256 signatures using constant-time comparison.
Explained by
highlit
1package security
2
3import (
4 "crypto/hmac"
5 "crypto/sha256"
6 "encoding/hex"
7 "errors"
8 "fmt"
9)
10
11type Signer struct {
12 secret []byte
13}
14
15func NewSigner(secret string) *Signer {
16 return &Signer{secret: []byte(secret)}
17}
18
19func (s *Signer) Sign(payload []byte) string {
20 mac := hmac.New(sha256.New, s.secret)
21 mac.Write(payload)
22 return hex.EncodeToString(mac.Sum(nil))
23}
24
25var ErrInvalidSignature = errors.New("security: invalid signature")
26
27func (s *Signer) Verify(payload []byte, signature string) error {
28 expected := s.Sign(payload)
29 if !hmac.Equal([]byte(expected), []byte(signature)) {
30 return ErrInvalidSignature
31 }
32 return nil
33}
34
35func (s *Signer) SignHeader(payload []byte) string {
36 return fmt.Sprintf("sha256=%s", s.Sign(payload))
37}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1HMAC binds a message to a secret key so recipients can detect tampering.
- 2Signature comparison must be constant-time to avoid leaking bytes through timing.
- 3Wrapping a secret in a type keeps key handling and signing logic in one place.
Related explainers
go
package streaming import ( "bufio"
Streaming NDJSON logs over HTTP in Go
http-streaming
channels
select
Advanced
10 steps
java
@Component @Converter public class EncryptedStringConverter implements AttributeConverter<String, String> {
Transparent column encryption in Spring & JPA
encryption
aes-gcm
jpa-converter
Advanced
10 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
go
package api import ( "crypto/sha256"
ETag conditional requests in Gin
http-caching
etag
conditional-requests
Intermediate
6 steps
go
func (w *Watcher) resetDebounce(d time.Duration) { if !w.timer.Stop() { select { case <-w.timer.C:
Debouncing a stream of events in Go
debounce
timers
channels
Advanced
7 steps
go
package logging import ( "context"
Deduplicating log attributes in Go's slog
decorator-pattern
structured-logging
immutability
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/signing-and-verifying-payloads-with-hmac-in-go-explained-go-1e38/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.