<<< Date Index >>>     <<< Thread Index >>>

a Message-ID proposal



In filtering e-mail it can be quite useful to recognise one's own
Message-IDs. For example, you can filter out the failure report for a
message you actually sent from the hundreds of bounce messages caused
by a spammer using your address as the envelope sender; or you can
automatically whitelist people who put one of your Message-IDs in the
In-Reply-To field. (If you're very cautious you'll use a different
form of Message-ID in messages to public mailing lists.)

Recognising the format of a Mutt Message-ID is good enough in most
cases, but spammers have been known to mention "mutt" in the
User-Agent field, so it's not impossible that one day a spammer will
use Message-IDs that look like Mutt ones.

You could make it much harder to fake your Message-IDs by using a
cryptographic checksum. If you're using crypto you might as well at
the same time disguise the process ID and counter, which are
potentially a privacy problem; in a 2001 posting to mutt-dev it was
rather dramatically described as "spyware".

So, here's a proposal:

Message-ID = timestamp . uniquifier . signature

uniquifier = encrypt(secret1 . timestamp, counter. pid);

signature = checksum(secret2 . timestamp . uniqifier)

encrypt(x, y) = checksum(x) ^ y

If secret2 comes from a Mutt variable you could probably use hooks to
modify it when you're sending to a mailing list. You can generate
secret1 randomly as you don't ever need to decode the uniquifier.

Does anyone think this might be useful?

Note: If an attacker got hold of one of your Message-IDs then they
could use it for spamming you, but they couldn't easily generate any
other Message-IDs from it, so you could either blacklist the stolen
Message-ID or just wait for it to expire: the Message-ID contains a
date. Possible extension: specify an expiry date in the Message-ID ...

See below for a prototype Perl implementation. The uniquifier is
encoded in base32 because apparently some people compare Message-IDs
with a case-insensitive comparison, but the signature is encoded in
base64 to facilitate computing it with standard library functions as
the spam filter has to do this same computation.

Edmund

-----------------------------------------------------------------------

#!/usr/bin/perl -w

# Generate some Message-IDs.
# Give the SECRET as argument.

use Digest::MD5 qw(md5), qw(md5_base64);

$secret = shift(@ARGV) || "";

$Base32 = "0123456789abcdefghijklmnopqrstuvwxyz";

$rand = "";
foreach (1..5) {
    $rand .= rand(2**32) . ".";
}

$count = 0;

print &msgid(), "\n";
print &msgid(), "\n";
print &msgid(), "\n";

sub msgid {
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = gmtime(time);
    my $time = sprintf("%04d%02d%02d%02d%02d%02d",
                       $year + 1900, $mon + 1, $mday, $hour, $min, $sec);
    my $pid = $$;

    my $pad = md5("$rand$time." . int($pid / 2**32));
    my ($countx, $pidx) = unpack('CN', $pad);
    $countx ^= $count;
    $pidx ^= $pid;
    my $uniq = &base32(($countx << 2) | (($pidx >> 30) & 3));
    foreach my $i (1..6) {
        $uniq .= &base32($pidx >> (30 - $i * 5));
    }
    my $sig = substr(md5_base64("$secret.$time.$uniq"), 0, 4);
    $sig =~ tr!/!$!;
    ++$count;
    "$time.$uniq.$sig";
}

sub base32 {
    my ($x) = @_;
    substr($Base32, $x & 31, 1);
}

-----------------------------------------------------------------------

#!/usr/bin/perl -w

# Check a Message-ID.
# Usage: checkid SECRET Message-ID

use Digest::MD5 qw(md5), qw(md5_base64);

($secret, $msgid) = @ARGV;

$msgid =~ /^([0-9]{14}\.[0-9a-x]{7})\.([A-Za-z0-9\+\$]{4})$/ ||
    die "Bad Message-ID\n";

($x, $sigx) = ($1, $2);

$sig = substr(md5_base64("$secret.$x"), 0, 4);
$sig =~ tr!/!$!;

if ($sig eq $sigx) {
    print "Good Message-ID\n";
}
else {
    print "Bad signature; shoud be: $x.$sig\n";
}