Finally, a PHP5 class for not just signing, but verifying DKIM signatures.
PHP 5.3 or greater (namespaces), with the openssl and hash extensions. CI covers PHP 7.4 through 8.4.
A legacy code path will use phpseclib 1.x if its
global Crypt_RSA/Crypt_Hash classes happen to be loaded. That path is untested and
not recommended; phpseclib 2.x and later are namespaced and never trigger it.
$verifier = new angrychimp\DKIM\Verify($rawMessage);
foreach ($verifier->validate() as $num => $results) {
foreach ($results as $result) {
echo $result['status'], ': ', $result['reason'], "\n"; // pass | permfail
}
}Each DKIM-Signature header in the message gets its own result set. The public key is
fetched from DNS; a message is only verified if both the body hash and the header hash
pass.
$signer = new angrychimp\DKIM\Sign($rawMessage, array(
'domain' => 'example.com',
'selector' => 'mail',
'private_key' => file_get_contents('/path/to/private.pem'), // or 'file:///path/to/private.pem'
));
echo $signer->sign(); // the message with DKIM-Signature prepended
echo $signer->getSignatureHeader(); // just the headerOptional parameters:
| Parameter | Default | Notes |
|---|---|---|
passphrase |
none | for an encrypted private key |
hash |
sha256 |
sha1 or sha256 |
canonicalization |
relaxed/relaxed |
simple survives transit far less often |
headers_to_sign |
a recommended set | array of header names; From is mandatory |
Signatures produced here are checked against dkimpy in CI, in both directions, so they interoperate rather than merely round-tripping.
The pre-0.4.0 global class names (DKIM_Sign, DKIM_Verify, DKIM_Exception) still work
as aliases when the package is loaded through Composer. They will be removed in a future
major release.
v0.4.0
Breaking release. Classes are namespaced and the signing half of the library finally exists.
- Breaking: moved to PSR-4 under
angrychimp\DKIM\.DKIM_Sign→Sign,DKIM_Verify→Verify,DKIM_Exception→Exception. The old global names remain as aliases via Composer and will be dropped in a future major release. - Signing implemented, open since 2012. Supports relaxed/simple canonicalization, sha1/sha256, configurable signed-header sets, and RFC 6376 5.4.2 bottom-to-top ordering for repeated headers.
- Security:
openssl_verify()returns-1on internal error, which is truthy — an errored verification was being reported as a pass. It now requires an explicit success. - Security:
a=was never validated, so a signature naming any algorithm (e.g.ed25519-sha256) was still verified as RSA. Restricted torsa-sha1/rsa-sha256, and checked before the DNS lookup so a bogusa=costs no network traffic. - Fixed header lookup matching on a bare prefix:
Message-IDalso matchedMessage-ID-Hash(which Mailman 3 adds), silently producing signatures no other implementation accepts. - Fixed relaxed canonicalization of an empty body, which emitted
CRLFinstead of a null input. This affected verification too, rejecting valid signatures on empty-bodied mail. - Fixed
h=handling for a header name listed more than once; it was deduplicated, so messages with repeated headers could never verify. - Fixed signature expiration:
x=was never enforced, and readingt=unguarded errored whenx=appeared alone. c=now defaults tosimple/simpleand tolerates a lone algorithm, per RFC 6376 3.5.- Hardened parsing against malformed input: signature tags and key records with no
=or a trailing;,q=with no/, headers with no colon, failed DNS lookups, and messages with no blank line (whose body was previously read from the middle of a header). - Added test suites covering signing, verification, and tag handling, plus GitHub Actions running them on PHP 7.4–8.4 and cross-checking signatures against dkimpy in both directions.
v0.2.1 11:28 AM 3/3/2016
- Fixed index variable issue (#7)
- Addressed validation issue when public key record did not have public-key data (#7)
- Minor version numbering corrections
- Dropped old copyright info for as-yet-still-empty Sign code
- Fixed new-line trimming issue (potentially causing verification problems?) (#7)
v0.2 5:36 PM 1/2/2013
- Splitting TODOs into separate file.
- Finally got the header hash to match my expected value, based on debugging output from Mail::DKIM::Validate.
- Removed var_dump() calls
- Still doesn't verify signatures properly - not sure where to go from here.
v0.1 10:55 AM 12/31/2012 Initial commit. Most of the structure is in place, and the body hashes are validating, but I haven't been able to get the signature validation correct just yet. I must have some whitespace issue or some random public key problem.