Project 5: DKIM Signer for Outbound Mail

Build a signing pipeline that adds DKIM signatures to outbound messages using a private key.

Quick Reference

Attribute Value
Difficulty Expert
Time Estimate 2-3 weeks
Language Python (Alternatives: Go, Rust, C)
Prerequisites DKIM verification, RSA keys, MIME headers
Key Topics Header selection, canonicalization, signing

1. Learning Objectives

  1. Build a DKIM-Signature header from message data.
  2. Canonicalize headers and body for signing.
  3. Sign with a private key and inject the header correctly.
  4. Validate the resulting signature with your verifier.

2. Theoretical Foundation

2.1 Core Concepts

  • Signing vs verifying: Signing chooses which headers to include and generates the b= value.
  • Selector and domain: The d= domain and s= selector define which public key is used for verification.
  • Header list (h=): Defines exactly which headers are covered by the signature.
  • Body canonicalization: Must match what verifiers will apply.

2.2 Why This Matters

Outbound DKIM signing is required for deliverability. A small error in canonicalization will silently break signatures and harm reputation.

2.3 Historical Context / Background

DKIM signing was designed to be deployable by domains without changing SMTP. It uses headers to carry signature metadata without changing message body formats.

2.4 Common Misconceptions

  • Misconception: You sign all headers. Reality: You sign a selected list.
  • Misconception: DKIM signs the From address. Reality: It signs headers listed in h=.

3. Project Specification

3.1 What You Will Build

A library or CLI that accepts a raw RFC 5322 message and signs it with a domain key, returning a message with a valid DKIM-Signature header.

3.2 Functional Requirements

  1. Select header fields to sign (from, to, subject, date, message-id).
  2. Canonicalize body and compute bh=.
  3. Build DKIM-Signature tags and canonicalize header set.
  4. Sign with RSA (rsa-sha256).
  5. Insert the DKIM-Signature header at the top of the header list.

3.3 Non-Functional Requirements

  • Security: Private key must not be logged.
  • Reliability: Deterministic output for the same input.
  • Usability: CLI accepts key path, domain, and selector.

3.4 Example Usage / Output

$ ./dkim-sign --domain example.com --selector s1 --key key.pem message.eml > signed.eml
Signed: DKIM-Signature added

3.5 Real World Outcome

Your signed emails pass DKIM verification at major providers, and the output can be tested with any DKIM verifier.


4. Solution Architecture

4.1 High-Level Design

Message Parser
  -> Canonicalizer
  -> DKIM Header Builder
  -> RSA Signer
  -> Message Rewriter

4.2 Key Components

Component Responsibility Key Decisions
Parser Extract headers/body Preserve raw header order
Canonicalizer Apply relaxed/simple rules Match verifier behavior
Signer RSA SHA256 signature Use strong crypto library
Injector Add header Insert before other headers

4.3 Data Structures

class SignConfig:
    def __init__(self, domain, selector, key_path, canon):
        self.domain = domain
        self.selector = selector
        self.key_path = key_path
        self.canon = canon

4.4 Algorithm Overview

Key Algorithm: DKIM Header Generation

  1. Canonicalize body and compute bh=.
  2. Build DKIM-Signature header with empty b=.
  3. Canonicalize selected headers plus DKIM header.
  4. Sign canonicalized header string.
  5. Insert b= value and output message.

Complexity Analysis:

  • Time: O(n) for message length
  • Space: O(n)

5. Implementation Guide

5.1 Development Environment Setup

python -m venv .venv
source .venv/bin/activate
python -m pip install cryptography

5.2 Project Structure

dkim-signer/
├── message_parser.py
├── canonicalize.py
├── dkim_builder.py
├── sign.py
└── README.md

5.3 The Core Question You’re Answering

“How do I create a DKIM signature that another server will accept as valid?”

5.4 Concepts You Must Understand First

Stop and research these before coding:

  1. Header selection
    • Why some headers are signed and others are not
  2. Canonicalization
    • Matching relaxed/simple to expected verification
  3. RSA signature
    • SHA-256 hashing and signing
  4. DKIM tags
    • v, a, d, s, h, bh, b

5.5 Questions to Guide Your Design

  1. Which headers should be included in h= and why?
  2. How will you handle repeated headers like Received?
  3. Will you default to relaxed or simple canonicalization?

5.6 Thinking Exercise

If you sign with relaxed canonicalization but the verifier expects simple, what breaks first: body hash or header signature?

5.7 The Interview Questions They’ll Ask

  1. “What is the difference between signing and verifying DKIM?”
  2. “Why is the DKIM-Signature header included in the signed data?”
  3. “How do you choose which headers to sign?”

5.8 Hints in Layers

Hint 1: Start with a fixed header list

  • Use a minimal set and expand later.

Hint 2: Build the signature header in two passes

  • First with empty b=, then fill it in.

Hint 3: Validate with your verifier

  • Test on local messages before real sends.

5.9 Books That Will Help

Topic Book Chapter
DKIM spec RFC 6376 Sections 3-6
Cryptography Serious Cryptography Ch. 6
Message format RFC 5322 Sections 2-3

5.10 Implementation Phases

Phase 1: Foundation (4-5 days)

Goals:

  • Parse message and compute body hash

Tasks:

  1. Parse headers and body.
  2. Compute relaxed/simple body hash.

Checkpoint: Correct bh= for known test message.

Phase 2: Core Functionality (1 week)

Goals:

  • Build DKIM header and sign

Tasks:

  1. Create DKIM header with empty b=.
  2. Canonicalize headers and sign.

Checkpoint: Signature verifies with your verifier.

Phase 3: Polish and Edge Cases (4-5 days)

Goals:

  • Multiple headers and key handling

Tasks:

  1. Handle duplicate headers.
  2. Add config for selector/domain.

Checkpoint: Signs different messages reliably.

5.11 Key Implementation Decisions

Decision Options Recommendation Rationale
Canonicalization relaxed vs simple relaxed Most common in practice
Header list minimal vs broad minimal to start Easier to debug
Key format PEM vs DER PEM Common and easy to load

6. Testing Strategy

6.1 Test Categories

Category Purpose Examples
Unit Tests Canonicalization whitespace, folding cases
Integration Tests End-to-end sign + verify local verifier
Edge Case Tests Multiple DKIM headers confirm order

6.2 Critical Test Cases

  1. Body hash matches verifier output.
  2. Signature validates for a known key.
  3. Header selection matches h= list.

6.3 Test Data

From: a@example.com
To: b@example.net
Subject: Test

7. Common Pitfalls and Debugging

7.1 Frequent Mistakes

Pitfall Symptom Solution
Incorrect header order Signature fail Preserve original order
Wrong b= placement Verification fail Add b= last
Mismatched canonicalization Fail at receiver Match c= tag

7.2 Debugging Strategies

  • Compare canonicalized strings with verifier output.
  • Use opendkim-testmsg as a reference.

7.3 Performance Traps

  • Re-reading key for each message. Cache key in memory.

8. Extensions and Challenges

8.1 Beginner Extensions

  • Support custom header lists.
  • Add CLI option for canonicalization.

8.2 Intermediate Extensions

  • Implement ed25519 signatures.
  • Support l= body length tag.

8.3 Advanced Extensions

  • Add key rotation support with multiple selectors.
  • Build a signing service with HTTP API.

9. Real-World Connections

9.1 Industry Applications

  • Outbound MTAs sign for deliverability and trust.
  • Email marketing platforms rely on DKIM alignment.
  • OpenDKIM: https://github.com/trusteddomainproject/OpenDKIM
  • dkimpy: https://github.com/kjd/idc-dkimpy

9.3 Interview Relevance

  • DKIM signing pipeline and header selection are common in email infrastructure roles.

10. Resources

10.1 Essential Reading

  • RFC 6376 - DKIM spec
  • RFC 5322 - Message format

10.2 Video Resources

  • DKIM signing walkthroughs

10.3 Tools and Documentation

  • opendkim-genkey for key generation
  • opendkim-testmsg for verification

11. Self-Assessment Checklist

11.1 Understanding

  • I can explain the DKIM b= and bh= tags
  • I understand relaxed vs simple canonicalization
  • I can explain selector usage

11.2 Implementation

  • Signs messages that verify correctly
  • Handles header order and duplicates
  • Configures domain and selector

11.3 Growth

  • I can debug signing failures by comparing canonicalized strings
  • I can explain DKIM alignment requirements

12. Submission / Completion Criteria

Minimum Viable Completion:

  • Create DKIM header with valid bh=

Full Completion:

  • Signature verifies with your verifier

Excellence (Going Above and Beyond):

  • Support multiple algorithms and key rotation
  • Provide signing as a service

This guide was generated from EMAIL_SYSTEMS_DEEP_DIVE_PROJECTS.md. For the complete learning path, see the parent directory.