PHP代码生成DKIM密钥时验证失败问题排查
DKIM验证失败:自定义PHP密钥生成脚本的问题
我在使用PHP生成DKIM公私钥后,发送的邮件始终显示dkim=perm_fail,但用第三方DKIM生成工具创建的密钥却能正常通过验证。以下是我的代码(基于PHPMailer的DKIM密钥生成示例修改):
<?php //Set these to match your domain and chosen DKIM selector $domain = 'deepak.co.in'; $selector = 'cst'; //Private key filename for this selector $privatekeyfile = $selector .'.' .$domain.'.pem'; //Public key filename for this selector $publickeyfile = $selector . '_dkim_public.pem'; if (file_exists($privatekeyfile)) { echo "Using existing keys - if you want to generate new keys, delete old key files first. "; $privatekey = file_get_contents($privatekeyfile); $publickey = file_get_contents($publickeyfile); } else { //Create a 2048-bit RSA key with an SHA256 digest $pk = openssl_pkey_new( [ 'digest_alg' => 'rsa', 'private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA, ] ); //Save private key var_dump($pk); //exit; openssl_pkey_export_to_file($pk, $privatekeyfile); //Save public key $pubKey = openssl_pkey_get_details($pk); $publickey = $pubKey['key']; file_put_contents($publickeyfile, $publickey); $privatekey = file_get_contents($privatekeyfile); } echo "<pre>"."Private key (keep this private!): " . $privatekey; echo " Public key: " . $publickey; //Prepare public key for DNS, e.g. //phpmailer._domainkey.example.com IN TXT "v=DKIM1; h=sha256; t=s; p=" "MIIBIjANBg...oXlwIDAQAB"... $dnskey = "$selector._domainkey.$domain IN TXT"; //$dnsvalue = '"v=DKIM1; h=sha256; t=s; p='; $dnsvalue = 'v=DKIM1; h=sha256; p='; //Some DNS servers don't like ;(semi colon) chars unless backslash-escaped //$dnsvalue2 = '"v=DKIM1\; h=sha256\; t=s\; p='; $dnsvalue2 = 'v=DKIM1\; h=sha256\; p='; //Strip and split the key into smaller parts and format for DNS //Many DNS systems don't like long TXT entries //but are OK if it's split into 255-char chunks //Remove PEM wrapper $publickey = preg_replace('/^-+.*?-+$/m', '', $publickey); //Strip line breaks $publickey = str_replace(["\r", "\n"], '', $publickey); //Split into chunks $keyparts = str_split($publickey, 253); //Becomes 255 when quotes are included //Quote each chunk foreach ($keyparts as $keypart) { //$dnsvalue .= '"' . trim($keypart) . '" '; //$dnsvalue2 .= '"' . trim($keypart) . '" '; $dnsvalue .=trim($keypart); $dnsvalue2 .= trim($keypart); } echo " DNS key: " . trim($dnskey); echo " DNS value: " . trim($dnsvalue); $first_part_str = substr($dnsvalue, 0,253); $brk_created_dkim_using_first_part_str = explode($first_part_str, $dnsvalue); echo " DKIM TO BE USE "; echo $to_use_dkim_2048_bit = '"' .$first_part_str. '"'.$brk_created_dkim_using_first_part_str[1].'"'; //echo " DNS value (with escaping): " . trim($dnsvalue2); ?>
上述代码基于PHPMailer的DKIM密钥生成示例修改,仅做了少量调整。使用第三方工具生成密钥时邮件验证正常,但用这个脚本生成的密钥就会失败,验证失败截图如下:
问题根源与修复方案
1. 密钥生成的核心参数错误
这是导致验证失败的最关键问题:你在openssl_pkey_new里把digest_alg设置成了'rsa'——这完全不对!digest_alg需要指定哈希算法(DKIM标准用SHA256),而RSA是密钥类型,已经通过private_key_type参数明确指定了。这个错误会让生成的密钥签名时使用错误的哈希逻辑,直接导致DKIM验证不通过。
把这部分代码改成:
$pk = openssl_pkey_new( [ 'digest_alg' => 'sha256', // 这里改成SHA256哈希算法 'private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA, ] );
2. DNS记录格式的规范问题
你的DNS值处理逻辑有些混乱,DKIM的TXT记录需要严格遵循格式:
- 整个记录值需要用双引号包裹,长密钥要拆分成多个255字符以内的分段,每个分段单独用双引号,分段之间用空格分隔
- 分号不需要额外转义(绝大多数现代DNS服务商都支持直接使用分号)
建议把DNS值的生成逻辑修正为标准格式:
// 初始化DNS值,包含DKIM基础参数 $dnsvalue = '"v=DKIM1; h=sha256; t=s; p='; // 拼接处理后的公钥 $dnsvalue .= $publickey; // 闭合双引号 $dnsvalue .= '"'; // 拆分成长度符合要求的分段(每个分段不超过255字符) $dns_segments = str_split($dnsvalue, 253); // 用" "连接各个分段,形成标准的DNS TXT记录格式 $final_dns_value = implode('" "', $dns_segments);
3. 密钥文件的权限检查
生成私钥后,确保文件权限设置正确(比如chmod 600),避免服务器进程无法读取私钥,导致签名失败。
验证步骤
修复完成后,按以下步骤重新测试:
- 删除服务器上旧的
.pem密钥文件 - 运行修改后的脚本生成新的公私钥
- 更新域名DNS中的DKIM TXT记录(使用脚本输出的标准格式)
- 发送测试邮件,检查邮件头中的DKIM状态
这样应该就能解决dkim=perm_fail的问题了。
内容的提问来源于stack exchange,提问作者Abhinav
相关产品推荐
相关产品推荐


