SSL/TLS & Certificate Management for Kubernetes Engineers

Hashing, HMACs & Digital Signatures

You downloaded a binary from the internet — the latest version of kubectl. The download page shows a SHA-256 checksum next to the link. You run sha256sum kubectl and compare. But what exactly are you verifying? And what if the attacker modified both the binary and the checksum on the download page?

Hashing tells you whether data was modified. HMACs tell you whether data was modified by someone without the secret key. Digital signatures tell you whether data was created by a specific entity — and that is what TLS certificates, container image signing, and Kubernetes admission webhooks all rely on.

This lesson completes the cryptographic foundation. After this, you will understand every primitive used in TLS.


Part 1: Hash Functions — The One-Way Fingerprint

A cryptographic hash function takes an input of any size and produces a fixed-size output (the hash or digest). It has three critical properties:

  1. One-way: given the hash, you cannot reconstruct the input
  2. Deterministic: the same input always produces the same hash
  3. Collision-resistant: it is computationally infeasible to find two different inputs that produce the same hash
# SHA-256 produces a 256-bit (32-byte) hash, displayed as 64 hex characters
echo -n "hello" | sha256sum
# 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

# Change one character: completely different hash (avalanche effect)
echo -n "Hello" | sha256sum
# 185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969

# Hash a large file — same 256-bit output regardless of input size
sha256sum /usr/bin/kubectl
# a1b2c3d4... kubectl
KEY CONCEPT

A hash function is a fingerprint for data. Any change to the input — even a single bit — produces a completely different hash. This avalanche effect is what makes hashes useful for integrity verification. If the hash matches, the data has not been modified. If it does not match, something changed.

Hash Functions You Will Encounter

AlgorithmOutput SizeStatusWhere You See It
MD5128-bitBrokenLegacy systems (never use for security)
SHA-1160-bitBrokenGit commits (collision found in 2017)
SHA-256256-bitSecureTLS, Docker digests, etcd, Kubernetes
SHA-384384-bitSecureTLS cipher suites (SHA384 variant)
SHA-512512-bitSecureSome certificate signing
BLAKE3256-bitSecureNewer tools, very fast
WARNING

MD5 and SHA-1 are cryptographically broken — practical collision attacks exist. In 2017, Google demonstrated a SHA-1 collision (two different PDF files with the same SHA-1 hash) with the SHAttered attack. Git still uses SHA-1 for commit hashes, but it is transitioning to SHA-256. For any security purpose — TLS, certificate signing, integrity verification — use SHA-256 or SHA-384 exclusively.

Where Hashes Appear in Kubernetes

Hashes are everywhere in your infrastructure:

# Docker image digests are SHA-256 hashes
docker pull nginx@sha256:a1b2c3d4e5f6...
# The digest uniquely identifies the exact image content
# Tags (like "latest") can change; digests cannot

# Kubernetes uses image digests for admission control
kubectl get pod -o jsonpath='{.status.containerStatuses[0].imageID}'
# docker-pullable://nginx@sha256:a1b2c3d4e5f6...

# Helm chart integrity verification
helm verify mychart-0.1.0.tgz
# Uses SHA-256 hash + signature to verify the chart

# etcd stores data with revision hashes
etcdctl get /registry/secrets/default/my-secret --print-value-only | sha256sum
# Pin container images by digest instead of tag for immutability
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
        - name: app
          # BAD: tag can be overwritten
          # image: myapp:v1.2.3
          # GOOD: digest is immutable
          image: myapp@sha256:a1b2c3d4e5f6789...
PRO TIP

Always pin production container images by digest, not tag. A tag like v1.2.3 can be re-pushed to point to different content. A digest like sha256:a1b2c3... is a hash of the image content — if anyone modifies the image, the digest changes. This is the foundation of supply chain security in Kubernetes, and tools like Sigstore/cosign build on it.


Part 2: HMACs — Hash-Based Message Authentication

A plain hash verifies integrity (was the data modified?) but not authenticity (who sent it?). If an attacker intercepts a message and its hash, they can modify both — the receiver has no way to know.

HMAC (Hash-based Message Authentication Code) solves this by incorporating a secret key into the hash computation:

HMAC(key, message) = Hash((key XOR opad) || Hash((key XOR ipad) || message))

Where:
  key   = shared secret between sender and receiver
  ipad  = inner padding (0x36 repeated)
  opad  = outer padding (0x5c repeated)

The receiver computes the HMAC using the same key and message. If the results match, the message is both unmodified (integrity) and from someone who knows the key (authenticity).

Hash vs HMAC

Plain Hash (SHA-256)

Integrity only — no authentication

InputMessage only
VerifiesData was not modified
Attacker canModify message AND recompute hash
Secret key requiredNo
Use caseFile checksums, Docker digests
SecurityCannot detect if attacker replaced both data and hash
HMAC-SHA256

Integrity + authentication

InputMessage + secret key
VerifiesData was not modified AND sender knows the key
Attacker canNothing — without the key, cannot produce valid HMAC
Secret key requiredYes — shared between sender and receiver
Use caseWebhook verification, API authentication, TLS record integrity
SecurityAttacker without key cannot forge valid HMAC

HMAC in Practice: Webhook Verification

Every webhook system in your infrastructure uses HMAC to verify authenticity:

# GitHub webhook signature verification
# GitHub sends: X-Hub-Signature-256: sha256=<HMAC>
# You verify by computing HMAC with your webhook secret

# Stripe webhook verification uses the same pattern
# Stripe sends: Stripe-Signature: t=<timestamp>,v1=<HMAC>

# Example: verify a webhook signature in bash
WEBHOOK_SECRET="whsec_abc123..."
PAYLOAD='{"event":"payment.completed"}'
EXPECTED_SIGNATURE="sha256=a1b2c3d4..."

COMPUTED=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | awk '{print $2}')
echo "sha256=$COMPUTED"
# Compare with EXPECTED_SIGNATURE
# Kubernetes webhook admission controller
# The API server sends requests to your webhook endpoint
# The TLS certificate authenticates the connection
# But HMAC concepts apply in the token review API and service account tokens
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: my-webhook
webhooks:
  - name: validate.myapp.io
    clientConfig:
      caBundle: <base64-CA-cert>  # Verifies webhook server identity
      service:
        name: my-webhook
        namespace: default
WAR STORY

A team deployed a LemonSqueezy payment webhook without verifying the HMAC signature. An attacker discovered the webhook endpoint and sent forged payment events, granting free access to paid content. The fix took five minutes — add signature verification. The damage took weeks to assess. Always verify webhook signatures. The HMAC secret exists for a reason.


Part 3: Digital Signatures — Proving Identity at Scale

HMAC requires a shared secret between sender and receiver. This works for two parties but does not scale. If a server needs to prove its identity to millions of clients, it cannot share a secret with each one.

Digital signatures solve this using asymmetric cryptography:

  1. The signer hashes the message (SHA-256)
  2. The signer encrypts the hash with their private key — this is the signature
  3. Anyone with the signer's public key can decrypt the signature and compare the hash
  4. If the hashes match, the message is authentic (only the private key holder could have created the signature) and unmodified (the hash verifies integrity)
Signing:
  1. hash = SHA-256(message)
  2. signature = Encrypt(hash, private_key)
  3. Send: message + signature

Verification:
  1. hash1 = SHA-256(message)           # hash the received message
  2. hash2 = Decrypt(signature, public_key)  # decrypt the signature
  3. If hash1 == hash2: valid signature
     Else: message was modified or signer is fake

Digital Signature: Sign, Send, Verify

Click each step to explore

KEY CONCEPT

Digital signatures combine hashing (integrity) with asymmetric cryptography (authenticity) to provide non-repudiation — proof that a specific entity signed the data, verifiable by anyone with the public key, without needing a shared secret. This is the foundation of TLS certificates, code signing, container image signing, and the entire Public Key Infrastructure (PKI).

How TLS Certificates Use Digital Signatures

A TLS certificate is essentially a document that says "this public key belongs to devopsbeast.com," signed by a Certificate Authority (CA):

Certificate contents:
  Subject: CN=devopsbeast.com
  Public Key: (EC P-256 public key)
  Issuer: CN=Let's Encrypt Authority X3
  Valid: 2024-01-15 to 2024-04-15
  Serial Number: 03:a1:b2:c3...
  Signature Algorithm: SHA256withRSA

The CA signed this certificate by:
  1. Hashing all the certificate fields above with SHA-256
  2. Signing the hash with the CA's private key

Your browser verifies by:
  1. Hashing the certificate fields with SHA-256
  2. Decrypting the signature with the CA's public key (from the trusted root store)
  3. Comparing the hashes
  4. If they match: the certificate is legitimate
# View a certificate's signature details
openssl x509 -in cert.pem -noout -text | grep -A 2 "Signature Algorithm"
# Signature Algorithm: ecdsa-with-SHA256
#     30:45:02:21:00:b3:c4:d5... (the actual signature bytes)

# Verify a certificate against its issuer
openssl verify -CAfile ca-cert.pem server-cert.pem
# server-cert.pem: OK
PRO TIP

When cert-manager creates a Certificate in your cluster, it generates a private key, creates a Certificate Signing Request (CSR), sends it to the CA (Let's Encrypt, Vault, or a self-signed CA), and the CA signs the certificate. The signed certificate is stored in a Kubernetes Secret. Understanding this flow — private key generation, CSR creation, CA signing — is essential for debugging cert-manager issues.


Part 4: Container Image Signing with Cosign

Digital signatures extend beyond TLS. Cosign (part of the Sigstore project) signs container images so you can verify they were built by your CI pipeline and not tampered with:

# Sign a container image with cosign
cosign sign --key cosign.key myregistry.io/myapp:v1.2.3

# Verify a container image
cosign verify --key cosign.pub myregistry.io/myapp:v1.2.3
# Verification for myregistry.io/myapp:v1.2.3 --
# The following checks were performed on each of these signatures:
#   - The cosign claims were validated
#   - The signatures were verified against the specified public key

# Keyless signing with Sigstore (uses OIDC identity, not long-lived keys)
cosign sign myregistry.io/myapp:v1.2.3
# Generates an ephemeral key pair, signs with your OIDC identity
# Signature and certificate recorded in the Rekor transparency log
# Kubernetes admission policy to require signed images
# Using Kyverno
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: verify-image-signatures
spec:
  validatingAdmissionPolicy: true
  rules:
    - name: check-image-signature
      match:
        any:
          - resources:
              kinds:
                - Pod
      verifyImages:
        - imageReferences:
            - "myregistry.io/*"
          attestors:
            - entries:
                - keys:
                    publicKeys: |-
                      -----BEGIN PUBLIC KEY-----
                      MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE...
                      -----END PUBLIC KEY-----
WAR STORY

In the SolarWinds attack (2020), attackers compromised the build pipeline and injected malicious code into signed software updates. The updates had valid signatures because the build system itself was compromised. This is why modern supply chain security uses transparency logs (Sigstore Rekor) and identity-based signing — even if a key is compromised, the signing events are publicly auditable. Defense in depth matters.


Part 5: How All Four Primitives Work Together in TLS

The four cryptographic primitives from this module combine in TLS:

Four Cryptographic Primitives in a TLS Connection

TLS Connection
Hashing (SHA-256): Certificate fingerprints, handshake transcript hash, key derivation (HKDF)
Digital Signatures (ECDSA/RSA): Server proves identity by signing handshake with private key, CA signs certificates
Key Exchange (ECDHE/X25519): Client and server agree on shared secret without transmitting it, forward secrecy
Symmetric Encryption (AES-256-GCM): All application data encrypted at hardware speed after handshake completes
Result: Authenticated, integrity-verified, forward-secret, encrypted communication

Hover components for details

PhasePrimitivePurpose
Certificate verificationHashing + Digital signaturesProve the server is who it claims to be
Key exchangeECDHE + Hashing (HKDF)Agree on a shared secret with forward secrecy
Data encryptionSymmetric (AES-GCM)Encrypt all application data at wire speed
Record integrityHMAC / GCM auth tagVerify each TLS record was not tampered with
KEY CONCEPT

Every TLS connection uses all four cryptographic primitives from this module: hashing for fingerprints and key derivation, digital signatures for authentication, Diffie-Hellman for key exchange, and symmetric encryption for data. Understanding each primitive individually — and how they compose — is what separates engineers who can debug TLS issues from those who just restart pods and hope.

Putting It All Together: Kubernetes Certificate Chain

When your browser connects to an application behind a Kubernetes ingress, the full chain of cryptographic operations looks like this:

# View the full certificate chain
openssl s_client -connect app.example.com:443 -showcerts 2>/dev/null | \
  openssl x509 -noout -text | head -20
# Certificate:
#     Data:
#         Version: 3 (0x2)
#         Serial Number: 04:a1:b2:c3:d4:e5
#         Signature Algorithm: ecdsa-with-SHA256    ← digital signature
#         Issuer: C=US, O=Let's Encrypt, CN=R3     ← CA that signed it
#         Validity
#             Not Before: Jan 15 00:00:00 2024 GMT
#             Not After : Apr 15 00:00:00 2024 GMT
#         Subject: CN=app.example.com
#         Subject Public Key Info:
#             Public Key Algorithm: id-ecPublicKey  ← asymmetric key
#                 Public-Key: (256 bit)             ← P-256 curve
#                 ASN1 OID: prime256v1

Key Concepts Summary

  • Hash functions produce a fixed-size fingerprint of arbitrary data — one-way, deterministic, collision-resistant
  • SHA-256 is the standard hash for security — 256-bit output, used in TLS, Docker digests, git (moving to), and Kubernetes
  • MD5 and SHA-1 are broken — never use for security; practical collision attacks exist
  • HMAC adds a secret key to hashing — proves both integrity (unmodified) and authenticity (from someone with the key)
  • Digital signatures use asymmetric crypto (sign with private key, verify with public key) — provide non-repudiation at scale without shared secrets
  • TLS certificates are digitally signed documents — the CA signs "this public key belongs to this domain" and anyone can verify with the CA's public key
  • Container image signing (cosign) applies the same principle to supply chain security — sign images in CI, verify before deployment
  • All four primitives compose in TLS: hashing for fingerprints, signatures for authentication, ECDHE for key exchange, AES-GCM for data encryption

Common Mistakes

  • Using MD5 or SHA-1 for any security-critical purpose — both have practical collision attacks
  • Confusing hashing with encryption — hashing is one-way (you cannot recover the input), encryption is two-way (you can decrypt with the key)
  • Not verifying webhook HMAC signatures — allows attackers to forge webhook events
  • Assuming a hash checksum on a download page proves authenticity — if an attacker controls the page, they can replace both the file and the checksum; only digital signatures (GPG/cosign) prove authenticity
  • Confusing signing with encryption — signing uses the private key and anyone can verify; encryption uses the public key and only the private key holder can decrypt
  • Deploying container images by tag instead of digest — tags are mutable, digests (SHA-256 hashes) are not

KNOWLEDGE CHECK

A Certificate Authority (CA) signs a TLS certificate. What cryptographic operation does this involve?