# mailrs-dkim
[](https://crates.io/crates/mailrs-dkim)
[](https://docs.rs/mailrs-dkim)
[](#license)
RFC 6376 DKIM signature verifier. Pairs with
[`mailrs-spf`](https://crates.io/crates/mailrs-spf) and
[`mailrs-rfc5322`](https://crates.io/crates/mailrs-rfc5322) to give
mailrs full ownership of the inbound email-auth stack — together
they replace the `mail-auth` umbrella crate with shape we control.
## Quickstart
```rust,ignore
use mailrs_dkim::{verify, HickoryDkimResolver};
use hickory_resolver::TokioResolver;
# async fn run(raw_message: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
let inner = TokioResolver::builder_tokio()?.build();
let resolver = HickoryDkimResolver::new(inner);
let result = verify(&resolver, raw_message).await;
// Returns mailrs_dkim::DkimResult: Pass / Fail / Neutral / TempError /
// PermError / None / Policy.
# Ok(())
# }
```
## What this crate does (1.0)
- Parse `DKIM-Signature:` headers (all tags: v, a, b, bh, c, d, h,
l, q, s, t, x, i, z).
- Canonicalization: **simple/simple, simple/relaxed, relaxed/simple,
relaxed/relaxed** — all four combinations per RFC 6376 §3.4.
- Body hash computation (SHA-256) with optional length limit (`l=`).
- Header hash computation over the signed-header list in order,
with the DKIM-Signature value itself appended with the `b=` tag
cleared (RFC 6376 §3.7).
- Public-key fetch: TXT at `<selector>._domainkey.<domain>`.
- Signature verification: **`a=rsa-sha256`** (covers ~99% of
real-world DKIM in 2026).
- Returns the seven RFC 8601 vocabulary values:
`none / pass / fail / neutral / temperror / permerror / policy`.
- Pluggable [`DkimResolver`] async trait — bring your own DNS or
use the bundled hickory-backed adapter.
## What this crate does not (yet)
- **`a=ed25519-sha256`** (RFC 8463) — modern but rare. Deferred to
1.1. The header parser rejects with `UnsupportedAlgorithm`; treat
this as `PermError`.
- **Multiple signatures**: `verify` finds the **first**
DKIM-Signature header. Most legitimate mail signs once; some
ARC-bridged mail signs multiple times. The trait shape allows a
future `verify_all` without breaking changes.
- **DSN bounce signature verification** — same protocol shape, but
the DKIM-Signature is on a different header anchor; future addition.
- **Author Domain Signing Practices** — historical (RFC 5617),
deprecated.
## Why a new crate?
`mail-auth` includes DKIM but bundles it with SPF + DMARC + ARC. By
the project's DEPS_AUDIT we want each in a focused stone so:
- the perf of each step is measurable independently
- the API shape is tunable per-RFC
- the crate's transitive deps stay tight per use case
`mailrs-spf` shipped first (DEPS_AUDIT #1). This is its sibling.
## Performance
Measured (criterion, M-series Mac, release):
| `DkimHeader::parse` (minimal header) | **147 ns** |
| `DkimHeader::parse` (realistic 11-tag header) | **405 ns** |
| `canonicalize_body` (simple) | **70 ns** |
| `canonicalize_body` (relaxed) | **140 ns** |
| `canonicalize_header` (relaxed, one header) | **85 ns** |
### Vs. `mail-auth` 0.9 (the de-facto Rust competitor)
| minimal (7 tags) | **147 ns** | 167 ns | **mailrs +12%** ✅ |
| realistic (folded, 11 tags) | **405 ns** | 423 ns | **mailrs +4%** ✅ |
We **beat mail-auth** on both inputs. The 4.6× / 3.5× cumulative speedup
since 1.1.1 came from: (1) single-pass byte scanner replaces HashMap +
unfold pre-pass; (2) byte-level tag dispatch via `match name.as_bytes()`
(lowercase fast path + case-insensitive fallback); (3) `h=` signed-
headers parsed via byte-iter with `from_utf8_unchecked` (safe because
only ASCII-lowercased bytes are pushed). Reproduce via
`cargo bench -p mailrs-dkim --bench compare_mail_auth`.
Production `verify` is dominated by:
- DNS lookup for the selector key (5-50 ms)
- RSA-SHA256 signature verification (~1-2 ms for 2048-bit keys)
The bench numbers above are the pure CPU pieces. Reproduce:
`cargo bench -p mailrs-dkim --bench dkim`.
## License
Apache-2.0 OR MIT.