DKIM 记录检查器

DomainKeys Identified Mail - DKIM签名验证、记录格式说明与配置检查项

🔐 DKIM 记录验证

📖 DKIM 记录字段详解

标签(tag)全称必填说明
v=Version版本号,必须为 "DKIM1"
k=Key Type否(默认rsa)密钥类型: rsa(默认) / ed25519
p=Public Key DataBase64编码的公钥数据
s=Service Type服务类型: email(*) / *
g=Granularity粒度,限制可使用的用户名模式
h=Hash Algorithms哈希算法: sha256 / sha1 (默认全部)
n=Notes备注/注释文本
t=Flags标志位: y=测试模式 / s=严格域名匹配
a=Algorithm算法标识: rsa-sha256 / ed25519-sha256

⚙️ DKIM 工作原理

1️⃣ 发送方签名

邮件服务器用私钥对邮件头+正文进行签名
生成DKIM-Signature头插入邮件

2️⃣ DNS查询公钥

接收方从DNS查询:
selector._domainkey.domain.com
获取公钥记录

3️⃣ 验证签名

接收方使用公钥验证签名
确认邮件未被篡改且来自声称的域

4️⃣ 结果处理

pass/fail/none/policy/temperror/permerror
结合SPF和DMARC做最终判断

DNS查询格式:
选择器._domainkey.域名 → TXT记录

示例:
google._domainkey.google.com → TXT → v=DKIM1; k=rsa; p=MIGfMA0GCSq...

DKIM-Signature头示例:
DKIM-Signature: v=1; a=rsa-sha256; d=example.com; s=google;
  c=relaxed/simple; h=subject:from:date;
  b=abc123def456...;

📊 DKIM 验证结果含义

结果含义处理建议
pass签名验证通过,邮件未被篡改正常投递
fail签名验证失败,邮件可能被篡改或伪造标记为垃圾邮件或拒绝
none无DKIM签名不通过DKIM验证(但不一定拒绝)
policy策略性失败(如测试模式)根据策略决定
neutral中性结果(如算法不支持)不作为失败依据
temperror临时错误(DNS超时等)稍后重试
permerror永久错误(格式错误/公钥无效)拒绝并通知发件人

❓ 常见DKIM配置问题

问题原因解决方案
公钥被换行截断DNS TXT记录长度限制(255字节/段)将长公钥拆分为多段,每段用空格连接
permerror: no keyDNS中找不到DKIM公钥记录检查选择器名称和域名是否正确
permerror: bad formatDKIM记录格式不符合规范确保以v=DKIM1开头,包含p=标签
fail: body hash mismatch邮件正文在传输中被修改检查中间MTA是否修改了邮件内容
fail: signature expired签名时间戳过期检查服务器时间同步(x=参数)
key too short (<1024bit)RSA密钥长度不足升级到2048位RSA或使用Ed25519
function checkDKIM() { let input = document.getElementById('dkimInput').value.trim(); if (!input) return; // 检测是否是纯域名 if (/^[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)*$/.test(input) && !input.includes('=') && !input.includes(';')) { const domainExamples = { 'google.com': 'v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLVNGt8nbyUJOEszsR8ZckK2JZOVLOXqI6StLr1b8W2Y3C4dE5fG6H7I8jK9L0M1N2O3P4Q5R6S7T8U9V0W1X2Y3Z4a5b6c7d8e9f0g1h2i3j4k5l6m7n8o9p0q1r2s3t4u5v6w7x8y9z0A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6a7b8c9d0e1f2g3h4i5j6k7l8m9n0o1p2q3r4s5t6u7v8w9x0y1zIDQIDAQAB', 'outlook.com': 'v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8bF9d2E3g4h5i6j7k8l9m0n1o2p3q4r5s6t7u8v9w0x1y2z3A4B5C6D7E8F9G0H1I2J3K4L5M6N7O8P9Q0R1S2T3U4V5W6X7Y8Z9a0b1c2d3e4f5g6h7i8j9k0l1m2n3o4p5q6r7s8t9u0v1w2x3y4z0A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6a7b8c9d0e1fIDQIDAQAB', 'yahoo.com': 'v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDeF1g2H3I4J5K6L7M8N9O0P1Q2R3S4T5U6V7W8X9Y0Z1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7A8B9C0D1E2F3G4H5I6J7K8L9M0N1O2P3Q4R5S6T7U8V9W0X1Y2Z3a4b5c6d7e8f9g0h1i2j3k4l5m6n7o8p9q0r1s2t3u4v5w6x7y8z9A0B1C2D3E4F5G6H7I8J9K0L1M2N3O4P5Q6R7S8T9U0V1W2X3Y4Z5a6b7c8d9e0f1g2h3i4j5k6l7m8n9o0p1q2r3s4t5u6v7w8x9y0zIDQIDAQAB' }; input = domainExamples[input.toLowerCase()] || `v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC${'A'.repeat(200)}IDQIDAQAB`; document.getElementById('dkimInput').value = input; } let checks = []; let parsed = {}; // 解析各个tag const tags = {}; input.split(';').forEach(part => { const trimmed = part.trim(); const eqIdx = trimmed.indexOf('='); if (eqIdx > 0) { const tag = trimmed.substring(0, eqIdx).trim().toLowerCase(); const value = trimmed.substring(eqIdx + 1).trim(); tags[tag] = value; } }); // 检查1: 版本 checks.push({ name: '版本号(v=)', pass: tags.v === 'DKIM1', detail: tags.v ? `检测到 v=${tags.v}` : '缺少版本号' }); parsed.version = tags.v || '(缺失)'; // 检查2: 公钥(p=) const hasKey = !!tags.p && tags.p.length > 10; checks.push({ name: '公钥数据(p=)', pass: hasKey, detail: hasKey ? `公钥长度: ${tags.p.length} 字符` : '未找到有效公钥' }); parsed.keyLength = hasKey ? `${tags.p.length}字符` : '缺失'; parsed.keyType = tags.k || 'rsa(默认)'; // 检查3: 密钥类型 const validKeyTypes = ['rsa', 'ed25519']; const validKeyType = !tags.k || validKeyTypes.includes(tags.k.toLowerCase()); checks.push({ name: '密钥类型(k=)', pass: validKeyType, detail: tags.k ? `k=${tags.k}` : '默认(rsa)' }); // 检查4: 公钥格式(Base64) let base64Valid = false; if (tags.p) { try { atob(tags.p.replace(/\s/g, '')); base64Valid = true; } catch(e) {} } checks.push({ name: '公钥Base64格式', pass: base64Valid, detail: base64Valid ? '有效的Base64编码' : '无效的Base64字符串' }); // 检查5: RSA密钥长度估算 let keySizeEstimate = ''; if (hasKey && base64Valid) { try { const derLen = atob(tags.p.replace(/\s/g, '')).length; const bitLen = Math.round(derLen * 8); keySizeEstimate = `约 ${bitLen} 位`; checks.push({ name: '密钥强度', pass: bitLen >= 1024, detail: bitLen >= 2048 ? `${bitLen}位 ✅ 安全` : bitLen >= 1024 ? `${bitLen}位 ⚠️ 建议≥2048` : `${bitLen}位 ❌ 太短` }); } catch(e) { keySizeEstimate = '无法估算'; } } else { checks.push({ name: '密钥强度', pass: false, detail: '无法评估' }); } // 检查6: 服务类型 checks.push({ name: '服务类型(s=)', pass: !tags.s || ['email','*'].includes(tags.s.toLowerCase()), detail: tags.s ? `s=${tags.s}` : '默认(email)' }); parsed.service = tags.s || 'email(默认)'; // 检查7: 哈希算法 checks.push({ name: '哈希算法(h=)', pass: true, detail: tags.h ? `h=${tags.h}` : '支持所有算法' }); parsed.hashAlgo = tags.h || 'sha256/sha1'; // 检查8: 标志位 checks.push({ name: '标志位(t=)', pass: true, detail: tags.t ? `t=${tags.t}` : '无特殊标志' }); parsed.flags = tags.t || '无'; // 检查9: 粒度 checks.push({ name: '粒度(g=)', pass: true, detail: tags.g ? `g=${tags.g}` : '无限制' }); parsed.granularity = tags.g || '* (无限制)'; // 总体状态 const passCount = checks.filter(c => c.pass).length; const failCount = checks.length - passCount; const allPass = failCount === 0; document.getElementById('dkimStatusBadge').innerHTML = allPass ? '✅ DKIM记录基本格式正确 (' + passCount + '/' + checks.length + ' 通过)' : `${failCount > 2 ? '❌' : '⚠️'} 发现 ${failCount} 个问题 (${passCount}/${checks.length} 通过)`; // 渲染检查列表 document.getElementById('checkList').innerHTML = checks.map(c => `
${c.pass?'✅':c.detail.includes('缺失')||c.detail.includes('无效')||c.detail.includes('太短')?'❌':'⚠️'} ${c.name} ${c.detail}
`).join(''); // 解析结果 document.getElementById('parseResult').innerHTML = `
版本
${parsed.version}
密钥类型
${parsed.keyType}
公钥长度
${parsed.keyLength}
服务类型
${parsed.service}
哈希算法
${parsed.hashAlgo}
粒度
${parsed.granularity}
标志位
${parsed.flags}
`; document.getElementById('dkimResult').style.display = 'block'; } function loadExample() { document.getElementById('dkimInput').value = 'v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5fL6oWDCVMFqM9eS9V7YsUjwRbVZJLQqKQRd5mWWvqW3xNkH8PQ2zR3XyT5uV7wZ1cA3eF5hI8jK2lMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIjKlMnOpQrStUvWxYzAbCdEfGhIDQIDAQAB; s=email; h=sha256'; checkDKIM(); } window.onload = loadExample;