Re: DKIM: Show Your Privates
I recently read Ryan Castellucci’s blog post, “DKIM: Show Your Privates”. The problem Ryan points out is that DKIM, which signs outgoing emails as a way to to reduce spam, has a negative unintended consequence: it’s harder to deny that you sent an email if it gets leaked. As Ryan points out, saner messaging protocols like OTR and the Double Ratchet Algorithm do implement cryptographic deniability of messages.
There is a way to mitigate the loss of cryptographic deniability in email. You simply rotate DKIM keys, invalidating the old one and publishing its private part. The point of publishing the private part is that any leaked emails which were signed with that key could be forged. Thus, one can deny past emails signed with that key.
As Ryan notes in their blog post though, email deniability probably won’t protect you from the law:
“I alluded to it earlier, but I want to be clear — publishing DKIM private keys like this mainly addresses leaks as a threat model. In a legal dispute, mail server logs and/or stored mail can be subpoenaed if the authenticity of messages is in question. Even in my case, where I have my own mail server on dedicated hardware with full disk encryption at an undisclosed location, most mail I send will be delivered to a server operated by a third party with no incentive to alter logs at the behest of the recipient.”
The Session team’s blog post, “Session Protocol: Technical implementation details”, says more or less the same in the context of their own private messaging protocol:
“As previously mentioned, cryptographic deniability is often something that is largely ignored by the court system and the media. If contextual information can be provided around screenshots, this is often enough to lead to a conviction or personal damages, regardless of the presence or absence of cryptographic deniability.
Instead of designing a cryptographic protection, Session will add the ability to edit other users’ messages locally, thus providing a way to completely forge conversations. Since signatures are deleted after messages are received, there will be no way to prove whether a screenshot of a conversation is real or edited, diminishing the value of screenshots as evidence.”
Programmers could still change the Session source code to save the message signatures anyways, but I highly doubt anyone is doing this. By contrast, email servers do retain email signatures even after emails are already validated. So there’s more of a concern for email being cryptographically undeniable than Session Private Messenger.
So, in my opinion, all email providers should publish expired DKIM keys. Especially the big ones that handle lots of mail like AOL, Gmail, iCloud, Outlook, Yahoo, Yandex, etc. I’ll quickly debunk some of the main objections.
Publishing expired keys doesn’t make spam harder to combat since the key gets revoked anyways. Since email accounts rely on the security of both client and the server and many people use weak passwords and fall victim to phishing attacks, determining whether someone sent a particular outgoing email depends more on context (server logs, IP addresses, other info) than DKIM signatures. So publishing expired keys probably doesn’t make computer forensics harder.
This practice could make valuable corporate/government email leaks less credible, but CEOs and politicians aren’t the only ones who use email. Everybody uses it and making everybody less safe to gain slight confidence that occasional, albeit important email leaks are legitimate doesn’t seem worth it.
Practicing what I preach, I’ve published my old DKIM keys in the DNS system using some of Ryan’s code. Since my DKIM key uses more bits, I tweaked their dkim-private.py to support recovery of 2048+ bit RSA signing keys:
#!/usr/bin/env python3 import gmpy2, sys, dns.resolver from Crypto.PublicKey import RSA from base64 import b64decode as b64d def decode_dkim_private(txt): params = dict() # Parse the DKIM selector record. for key, _, val in map(lambda x: x.partition('='), txt.split(';')): if key == 'n': for k, v in map(lambda x: x.split(':'), val.split(',')): params[k] = int.from_bytes(b64d(v), 'big') # Compute rest of RSA keypair parameters (if possible). if all (k in params for k in ('e', 'p', 'q')): params['n'] = params['p'] * params['q'] phi = (params['p'] - 1) * (params['q'] - 1) params['d'] = int(gmpy2.invert(params['e'], phi)) rsa = map(lambda x: params[x], 'nedpq') return RSA.construct(tuple(rsa)) else: return None if __name__ == '__main__' and len(sys.argv) == 3: domain = sys.argv selector = sys.argv for answer in dns.resolver.resolve(selector + '._domainkey.' + domain, 'TXT'): txt = str(answer).replace('"', '').replace(' ', '') print(decode_dkim_private(txt).exportKey().decode())
Here’s an example run retrieving my old key:
$ ./dkim-private.py 'nicholasjohnson.ch' 'mail' -----BEGIN RSA PRIVATE KEY----- MIIEoAIBAAKCAQEAu1rjJVpWjzgrBBYaWhgRdwGbjW7utaIDXf/te4MjWO4XNskM DfTUBrI3O2LZYQNdvwP4zu8O+VtT5P7az1yNBmuseQtTb+/qGdvxEZhb+zSrKgKC Vrvac4f09JFOsTvc4VAH537QCRh7whpc625R93NVsGFAuQhIOVpbOWLRbUB+Deys CbiEyQm9H1PYa+x/M2cMHkVw8xVgmeET7q2jSiy9E3QyIwmWVl5qKqeXYbHPKotU NTSRfSC5au53UEr7PRrA60osLSqav/hVme0QvydpxLqaoYQz0ntQ4VgNIeReEVjl UaDUjbCNjzrZYgAp4e9lNDAXBJAYZuT8kyxorQIDAQABAoH/NLFbMSY3MhCaCIwu 3SfnwZCyLxUEDhGC4O0Z3aMETf6oiMACo+o3t6pn3kvu11obA54aXBjgHUGSVtVW tPtSrnuaBnEpBlJzhNJW+pvfQseNXENYZQxwZA3Y1vAHMdGkTbo0fucrm2NHa0/+ 0jg01WOatgCkM2Yg6gB/p6QjQpZkraPGOlW5P5Yc5HWDJFvTnpVMOGrN7iPAxNVT +SvBCJR0Z0mqUdNTE9dcjt2ZfWdpYNJbjBdRtUMs4PHICy+octx/qVrP53bbk1XX XSfbXsMrghpJAg+Tsk1jsA3IdrkOD06abqUxEouzOe6qLY2T3Ws1kuEnrnB4h+WX ogrhAoGBAPiyrYb/JF7Kh2jPz0nTR2CjEWTyvhDe87NkX8KpbhPixcMuzR9S2lIN r3A42J2vaZzh3/ZqQGY+eqYNDFoUIguXDhXfDsBV1M6oPblSepvRbmj2kWVuaGC5 QlhgChRlrwFcVSxE2uyX7K0QD6qH363V6xcOLgAK/w9lDlVRLxynAoGBAMDbIRdK MMProQt/Qx48qMlO4J0zp0mCjXgza6O8XPlX/iEQPCmK4k9Lpk3bqtv876hkSMAJ W8FKzZn55uDVKf8rUGkwTxIIIU+BtZKukavyob/X27hci4W9NRwYnhNW2qEJD5Zr 47ATJK+Hljyur5NzJAqelNT7AkWOwOHRgpaLAoGAO3AAtwXdfGYtDKi14vAC3B68 9oJpWIDgf5xaopx5uXj2SNqznWvgz8GDj2+WncEhnaQCMdNdBtKh33O44wJyzJBS hnmj/eXFkYp2DgefVAQuvhlH0JUdjxhXueViI1PWCp41oPnn1KnOn+H5zIjitZot sHnsFoiKQvSlA1D+0HsCgYAxiNcSBVNLLz1ZF6HkpU3xDtpwZjEEl4zAn+x4zMvQ m4JBecsKHIsONO8NNmvHP0tLJB7vfDfeCNmQP/jGLNoxmS15JxhYGFB9/GHnwADY emSDQu1DiDmp6zQ1+Di53OggzpP7XdDIi9IzZ472HSQpqjxKofq2TZaCySzPk6GI CwKBgG8D4LB0WryU+Jg0aV9+jBHheAFXs7UfvwShfh6F15IVp61AIROnyLRYByLg 7XAtm7jpuIPtce2etnCtjiM3Tbp0qallLAV2XlWXqwMXhy85ox6qlkG9VmrjHwyb SX+8PTuQN1wQH9qocI1gXOEzU0mV74Joywc6t6UL+4xIwsmQ -----END RSA PRIVATE KEY-----
I’ll continue to rotate my keys on a regular basis. If you have the skill to administer your own email server, then you’re probably capable of figuring out how to rotate DKIM keys. If you need some inspiration, Ryan wrote an experimental Python script which fully automates the procedure.
I know it sounds like a lot of effort, and it’s absurd to me too that email in its present incarnation is such a disaster of a protocol that it requires doing this sort of thing. This is yet another reason we desperately need to replace the existing network stack.