Back to Checks
DKIM record exists with valid public key
zone_record_dkim_exists
Service: zoneSeverity: medium
OfficialBuilt-inVerified
Categories:email-security
Resource Type:Zone
Cloudflare zones are assessed for DKIM (DomainKeys Identified Mail) records by checking if TXT records exist at *._domainkey subdomains containing a cryptographically valid public key in the p= parameter used to verify email signatures.
Risk
Without DKIM or with a revoked/empty public key, recipients cannot verify messages were sent by authorized servers. Attackers can forge emails from your domain, and content integrity cannot be proven. DMARC policies relying on DKIM will fail, affecting deliverability. A DKIM record with empty p= indicates a revoked key.
Run this check with Prowler
Run in Prowler CloudRun
prowler cloudflare --checks zone_record_dkim_existsRecommendation
Configure DKIM records with valid public keys from your email provider.
- Each email service requires its own DKIM selector (e.g., google._domainkey for Google Workspace)
- The
p=parameter must contain the actual public key, not be empty - An empty
p=value indicates a revoked key and will fail this check - DKIM works alongside SPF and DMARC for comprehensive email authentication
- Rotate DKIM keys periodically for enhanced security
- Use 2048-bit keys for stronger cryptographic protection
Remediation
Terraform
# Create DKIM record for email authentication
resource "cloudflare_record" "dkim" {
zone_id = "<ZONE_ID>"
name = "google._domainkey" # Selector varies by email provider
type = "TXT"
content = "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4..." # Must contain valid public key
ttl = 3600
}Other
1Generate DKIM keys using your email provider or mail server
2Log in to the Cloudflare dashboard and select your account and domain
3Go to DNS > Records
4Click Add record
5Select TXT as the record type
6Enter selector._domainkey for the Name field (e.g., google._domainkey)
7Enter the DKIM public key record provided by your email service (must include p= with actual key)
8Click Save
Check Code
import base64
import re
from cryptography.hazmat.primitives.serialization import load_der_public_key
from prowler.lib.check.models import Check, CheckReportCloudflare
from prowler.providers.cloudflare.services.dns.dns_client import dns_client
from prowler.providers.cloudflare.services.zone.zone_client import zone_client
class zone_record_dkim_exists(Check):
"""Ensure that DKIM record exists with valid public key for Cloudflare zones.
DKIM (DomainKeys Identified Mail) is an email authentication method that allows
the receiver to verify that an email was sent by the domain owner and was not
modified in transit. This check verifies that DKIM records exist at *._domainkey
subdomains containing "v=DKIM1" with a cryptographically valid public key in the
p= parameter that can be used to verify email signatures.
"""
def execute(self) -> list[CheckReportCloudflare]:
"""Execute the DKIM record exists check.
Iterates through all Cloudflare zones and verifies DKIM configuration.
The check validates three conditions:
1. A DKIM record exists (TXT record at *._domainkey with "v=DKIM1")
2. The record contains a p= parameter with a public key
3. The public key is cryptographically valid (valid Base64 and DER format)
Returns:
A list of CheckReportCloudflare objects with PASS status if a DKIM
record with valid public key exists, or FAIL status if no DKIM record
is found or the public key is invalid/missing.
"""
findings = []
for zone in zone_client.zones.values():
report = CheckReportCloudflare(
metadata=self.metadata(),
resource=zone,
)
# DKIM records are TXT records at *._domainkey subdomain containing "v=DKIM1"
dkim_records = [
record
for record in dns_client.records
if record.zone_id == zone.id
and record.type == "TXT"
and record.name
and "_domainkey" in record.name
and "V=DKIM1"
in record.content.replace('" "', "").replace('"', "").upper()
]
if not dkim_records:
report.status = "FAIL"
report.status_extended = f"No DKIM record found for zone {zone.name}."
else:
# Check if DKIM records have a valid public key
valid_key_records = [
record
for record in dkim_records
if self._has_valid_public_key(record.content)
]
records_str = ", ".join(record.name for record in dkim_records)
if valid_key_records:
report.status = "PASS"
report.status_extended = f"DKIM record with valid public key exists for zone {zone.name}: {records_str}."
else:
report.status = "FAIL"
report.status_extended = f"DKIM record exists for zone {zone.name} but has invalid or missing public key: {records_str}."
findings.append(report)
return findings
def _has_valid_public_key(self, content: str) -> bool:
"""Check if DKIM record has a valid public key.
Validates that:
1. The p= parameter exists and is not empty
2. The key is valid Base64
3. The key can be loaded as a valid DER-encoded public key
"""
# Cloudflare API may return TXT records with quotes, and long records
# may be split into multiple quoted strings like: "part1" "part2"
# First remove '" "' to join split parts, then remove remaining quotes
content = content.replace('" "', "").replace('"', "")
# Extract the public key value from p= parameter
match = re.search(r"p\s*=\s*([^;\s]*)", content, re.IGNORECASE)
if not match:
return False
key_value = match.group(1)
# Empty key means revoked
if not key_value:
return False
try:
# Add padding if necessary for Base64 decoding
padding = 4 - (len(key_value) % 4)
if padding != 4:
key_value += "=" * padding
# Decode Base64 to get DER-encoded key
der_key = base64.b64decode(key_value, validate=True)
# Try to load as a public key using cryptography library
load_der_public_key(der_key)
return True
except Exception:
return False