DKIMpy: DKIM, ARC, and TLSRPT email signing and verification
DKIMpy is a Python library for creating and verifying DKIM (DomainKeys Identified Mail), ARC (Authenticated Receive Chain), and TLSRPT (TLS Report) signatures on email messages. It provides a robust implementation for email authentication, relying on cryptographic operations and DNS lookups. The current version is 1.1.8, and it maintains a stable release cadence with updates addressing security and compatibility.
Common errors
-
ImportError: cannot import name 'DKIM' from 'dkim'
cause Attempting to import from the wrong module path. The `dkim` module is nested within the `dkimpy` package.fixChange the import statement to `from dkimpy.dkim import DKIM`. -
dkimpy.exceptions.DKIMException: No private key for signing
cause The `privkey` parameter in the `DKIM` constructor was missing, `None`, or an empty byte string.fixProvide a valid private key (as bytes, in PEM format) to the `privkey` argument when initializing `DKIM` for signing. -
dkimpy.exceptions.DKIMException: No DKIM-Signature header found
cause The email message passed to `dkimpy.dkim.verify` does not contain a 'DKIM-Signature' header, or the header is malformed.fixEnsure you are passing a complete email message (including headers) that has been previously signed with DKIM. -
TypeError: argument 'data' must be bytes, not str
cause Attempting to pass a unicode string (`str`) to a function or method expecting bytes (`bytes`), such as email content, domain, selector, or private key.fixEncode the string to bytes, e.g., `my_string.encode('utf-8')` or use byte literals `b'my_string'`.
Warnings
- breaking Version 1.0.0 introduced a major rewrite, making it incompatible with Python 2. Code written for `dkimpy` prior to 1.0.0 will likely break when upgrading to Python 3 with `dkimpy>=1.0.0`.
- gotcha All email content, keys, domain, and selector parameters must be byte strings (`bytes`), not unicode strings (`str`). Passing `str` will lead to `TypeError` or unexpected encoding issues.
- gotcha DKIM verification heavily relies on successful DNS lookups to retrieve the public key. Network issues, misconfigured DNS records (TXT records), or DNSSEC failures can cause verification to fail.
- gotcha The private key must be in PEM format. Other formats (e.g., DER) are not directly supported and will result in `cryptography.exceptions.InvalidKey` or other decryption errors.
Install
-
pip install dkimpy
Imports
- DKIM
from dkim import DKIM
from dkimpy.dkim import DKIM
- verify
import dkim; dkim.verify(...)
from dkimpy.dkim import verify
- DKIMException
from dkimpy.dkim import DKIMException
Quickstart
import os
from dkimpy.dkim import DKIM, verify, DKIMException
# In a real application, load your actual private key and use your domain/selector.
# Example key generation (using openssl):
# openssl genrsa -out dkim.private 1024
# openssl rsa -in dkim.private -pubout -out dkim.public
# Then load: `with open('dkim.private', 'rb') as f: private_key = f.read()`
private_key = os.environ.get('DKIM_PRIVATE_KEY', b"") # Should be bytes
domain = os.environ.get('DKIM_DOMAIN', 'example.com')
selector = os.environ.get('DKIM_SELECTOR', 's1')
# Sample email content (bytes) - DKIMpy works with byte strings
email_message_bytes = b"""From: sender@example.com\r\nTo: recipient@example.com\r\nSubject: Test DKIM Signature\r\n\r\nThis is the body of the email.\r\n"""
# --- 1. Sign an email ---
print("--- Signing an Email ---")
if not private_key:
print("Warning: DKIM_PRIVATE_KEY environment variable not set. Signing will likely fail.")
print("Please provide a valid private key for real signing.")
try:
# Initialize the DKIM signer
signer = DKIM(
message=email_message_bytes,
selector=selector.encode(), # Selector must be bytes
domain=domain.encode(), # Domain must be bytes
privkey=private_key
)
# Sign the message
signed_email_bytes = signer.sign()
print("Email signed successfully. First 500 bytes of signed email:")
print(signed_email_bytes[:500].decode(errors='ignore'))
print("...")
except DKIMException as e:
print(f"Error signing email: {e}")
except Exception as e:
print(f"An unexpected error occurred during signing: {e}")
# --- 2. Verify a received email ---
# For actual verification, the signed email needs to be received, and
# dkimpy will perform DNS lookups for the public key (TXT record).
print("\n--- Verification Example (requires real signed email and DNS) ---")
received_signed_email_bytes = signed_email_bytes # Use the just-signed email for demonstration
try:
# The `verify` function is a module-level function
# It returns a list of (dkim_domain, dkim_selector, ...) tuples for each valid signature.
result = verify(received_signed_email_bytes)
if result:
print(f"Verification successful. Found {len(result)} valid DKIM signatures.")
# print(f"Result details: {result}") # Uncomment for verbose output
else:
print("Verification failed or no valid DKIM-Signature found.")
except DKIMException as e:
print(f"Verification encountered an error: {e}")
except Exception as e:
print(f"An unexpected error occurred during verification: {e}")