Knowledge base

A suppression list that cannot be read

Addresses in the list are hashes, not addresses

The task. Suppress a client’s recipients on the node so that neither the node itself nor anyone who reaches its management interface holds a readable list of addresses.

What it is made of:

  • hashed-address — a <address-list> list entry: the address in lowercase, hashed with SHA-256 and encoded as BASE64URL; /config/address-lists accepts the same entries;
  • pmtahashedAddress — a utility from the distribution that computes this hash for a known address;
  • privacy-policy — hides the local part of addresses in accounting: with a placeholder, or with a SipHash or HMAC-SHA256 hash with a key;
  • digest — computes the keyed hash the same way, to find a known address’s records in accounting.

The parts have different scopes. Hashed entries live in the address list, the policy in the <acct-file> block, and the hashes are computed anywhere.

How they interact. These are two halves, and they do not match. The list hash is keyless and of the full address: the list cannot be read by eye, but a familiar address is checked against it instantly. The accounting hash is keyed and of the local part only: without the key, records cannot be linked either to an address or to the list. The price is the key: without it, support will not find an address’s records, and a lost key makes earlier records unlinkable forever.

The main trap is considering a keyless hash a secret. SHA-256 of an address is computed without a secret: the list cannot be read by eye, but a familiar address — or a whole address database — is checked against it instantly.
What the Hub does. It does not apply this: there are no suppression lists on the node. The Hub keeps warm-up unsubscribes in its own database and reconciles them with the mailing platform’s suppression, and in accounting recipient addresses go as is — privacy-policy is not set.
Recommended values

Two halves and their hashes:

Half What is hashed How Who computes it
suppression list the full address in lowercase SHA-256 without a key, BASE64URL pmtahashedAddress
accounting the local part of the address in lowercase SipHash or HMAC-SHA256 with a key digest
The hashed entry format, the utility, the accounting policy and the hash command come from the manual (§ 3.3.5.2, § 3.3.11.16, § 6.3.32, § 8.3.2). The analysis is ours; the scheme is a hypothesis.
Example configuration
<address-list suppressed>
    hashed-address iZ0zh6XFp1VdUdLwoLe1Djl_5IH9mlj6-zibfclOEfY~</address-list>
<acct-file /var/log/pmta/acct.csv>
    privacy-policy siphash "secret"
</acct-file>
The hash is from the manual’s example for test@foo.com; the accounting key is for illustration, the node needs its own.
yellow — — directive names, blue — blocks. They are highlighted the same way in the text of the card.
Compatibility
PowerMTA 6.0
described in the manual of this version · platform: any
The vendor does not state the first version for every directive — there is no note here. The name also appears in the 5.0 edition of the manual.
How to check on the node
Compute the hash of a known address for the list:
/usr/sbin/pmtahashedAddress test@FOO.com
The result iZ0zh6XFp1VdUdLwoLe1Djl_5IH9mlj6-zibfclOEfY~ matches the manual’s example: the address case does not affect the hash.