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
- Build a DKIM-Signature header from message data.
- Canonicalize headers and body for signing.
- Sign with a private key and inject the header correctly.
- 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 ands=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
- Select header fields to sign (
from,to,subject,date,message-id). - Canonicalize body and compute
bh=. - Build DKIM-Signature tags and canonicalize header set.
- Sign with RSA (rsa-sha256).
- 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
- Canonicalize body and compute
bh=. - Build DKIM-Signature header with empty
b=. - Canonicalize selected headers plus DKIM header.
- Sign canonicalized header string.
- 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:
- Header selection
- Why some headers are signed and others are not
- Canonicalization
- Matching relaxed/simple to expected verification
- RSA signature
- SHA-256 hashing and signing
- DKIM tags
- v, a, d, s, h, bh, b
5.5 Questions to Guide Your Design
- Which headers should be included in
h=and why? - How will you handle repeated headers like Received?
- 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
- “What is the difference between signing and verifying DKIM?”
- “Why is the DKIM-Signature header included in the signed data?”
- “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:
- Parse headers and body.
- 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:
- Create DKIM header with empty
b=. - 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:
- Handle duplicate headers.
- 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
- Body hash matches verifier output.
- Signature validates for a known key.
- 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.
9.2 Related Open Source Projects
- 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
10.4 Related Projects in This Series
11. Self-Assessment Checklist
11.1 Understanding
- I can explain the DKIM
b=andbh=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.