I've been researching and prototyping for the past day or so, and all it's done is given me the desire to go out and murder some cryptographers.
Ok, ok, to be fair I did also get an excellent idea of why this isn't really widely used as an authentication strategy yet. The "UI" is ... intimidating. And that's coming from someone who isn't the least bit intimidated by learning a programming language or three in spare moments between actual programming projects. Something tells me the average user doesn't care enough about security to go through that kind of hoop.
It turns out that there are several distinct standards for public/private key storage, none of the popular Linux RSA-using applications use the same one by default, and it's possible but annoying to convert between them. Further, even though they're all RSA) keys, they don't all let you do the same thing. OpenSSH uses PEM for private keys, a custom format for public keys, and a slightly different custom format for the
known_hosts file; it doesn't provide facilities for anything other than SSH auth and key generation. GnuPG uses
ascii-armored format1 for exported private and public keys, though it would really prefer never to show you your private key; it lets you sign and encrypt, but is a bit awkward to import/export in other formats. [OpenSSL](http://www.openssl.org/) technically works with PEM and several binary formats, but its default is the X.509 certificate standard; OpenSSL lets you verify, encrypt, decrypt and convert between key formats, but is a bit snippy about the format in which it outputs/verifies signatures.
The various language facilities available aren't exactly complete either. Erlang's
public_key modules claim they can make and verify signatures, handle encryption/decryption with RSA, DSA and AES keys, as well as perform a number of cryptographic hashes. But they can't generate keys, and I've yet to get a working import of a 4096 bit RSA keypair, whether it's generated by GnuPG, OpenSSL or SSH. Python has RSA, native crypto, and OpenSSL-wrapper modules available. They sort of do enough things properly, if imperatively, enough that I could see putting together a half-way sane system with them.
I'm not even getting into signature formats, which are ... interesting. In a no-fun-at-all kind of way. To the point that I couldn't reliably verify an OpenSSL signature with anything other than, ostensibly, OpenSSL2.
Here's a list of things I've tried putting together that didn't work:
- Using GPG to generate keys, reading them with Erlang and verifying incoming GPG signatures.
- Using SSH to generate keys, reading them with Erlang and verifying incoming GPG/OpenSSL-generated signatures.
- Using SSH to generate keys, converting them to PEM format and calling OpenSSL to sign and verify.
- Using OpenSSL to generate keys, then using Erlang to sign and verify3.
- Using OpenSSL to generate keys, and calling OpenSSL to sign and verify4.
- Using OpenSSL to generate keys, using OpenSSL to sign messages and M2Crypto to verify5.
The options that did work:
- Using OpenSSL to generate keys, then using M2Crypto to sign and verify6.
- Using OpenSSH to generate keys, then using M2Crypto to sign and verify.
- Using M2Crypto to generate keys, sign and verify messages7.
- Using GPG to generate keys, sign and verify messages8.
Just as an example, here's how to use M2Crypto to make a round-trip with OpenSSL-generated PEM keys9.
>>> Cert = M2Crypto.RSA.gen_key(4096, 65537) >>> Cert.save_key("rsa.pem", "passphrase goes here") >>> Cert.save_pub_key("rsa.pub.pem")
>>> import M2Crypto >>> PrivKey = M2Crypto.RSA.load_key("rsa.pem") Enter passphrase: >>> Message = "Daring Do and the Quest for the Sapphire Stone" >>> Signature = PrivKey.sign_rsassa_pss(Message) >>> Sent = [Message, Signature.encode('base64')]
And at the other end
>>> [Msg, Sig] = Sent >>> RawSig = Sig.decode('base64') >>> PubKey = M2Crypto.RSA.load_pub_key("rsa.pub.pem") >>> PubKey.verify_rsassa_pss(Msg, RawSig) 1 ### 0 for "nope", 1 for "yup", and it might error under certain circumstances
In a real-world situation, we'd actually hash the message using one of the SHA-2 algorithms before sending, but that's the theory in its entirety.
gpg is more straightforward, though it does seem to insist on handling public key storage/management for you, so I'm not entirely sure how far this scales in terms of number of users supported. I covered generating gpg keys in a previous post, but here's the signing/verification round trip:
$ echo "Daring Do and the Griffon's Goblet" | gpg --output message.gpg --armor --sign $
That generates a file that looks something like
-----BEGIN PGP MESSAGE----- Version: GnuPG v1.4.12 (GNU/Linux) owGbwMvMwMSYv4V14srcc18YT6skMfjfqtwTnJ+bWpKRmZeuUAxnpSQWZSsUZ6ak cnUyyrAwMDIxsLEygRQzcHEKwEyY/Z/9f1FXwqMHqYdDRYxZ+NOZ3xeXhv023nYo cH+r3SzBNL6VJy0vd3AnSJ/MvPHg6bZZH+teiOyZxynAf74qXH4a87qw86tuPpsX WKTNordmnnSf1+XLiefz1n/a5rpui3rm0s8Sx8++aeje7jZ5tapGjkuzvee17b/c FX9MevzHrur1zcdH9XqNeT2Slj01uFC3Kip49s4a2ctW77++aeK7dk5yW8unnemz m+7My0ncpReYHTbvraFdy42/JjuK2Lp3ctp9NrjM7qz7lHVZ7Ie7kprlb6o3nDk4 aULn85p18Unxk2UcOn7t9Pzz01vXv3yZY+TftG9rmQPEKrcJNT5lklCt8zm9P+Ce ezOj09dCDwA= =6KId -----END PGP MESSAGE-----
If you want it sent to standard out for whatever reason, just omit
--output message.gpg. Once the server receives the message, it just calls
$ gpg --decrypt message.gpg Daring Do and the Griffon's Goblet gpg: Signature made [timestamp] using RSA key ID [GPG ID of the RSA key] gpg: Good signature from "inaimthi <[email@example.com](mailto:firstname.lastname@example.org)>" $
Note that these are actually slightly different operations.
gpg is doing a simultaneous encryption+signing, whereas the
M2Crypto code is merely signing a plaintext message.
That's that. I did gloss over the part where we generate messages to send to the client, but other than that, this lays the preliminary groundwork for an RSA-based authentication system with actual web users.
ascii-armoredis distinct from, but similar to PEM, to the point where you can convert one to the other just by changing the start/end tag, and stripping the info entries, blank lines and checksum.↩
- In practice, I couldn't verify them with OpenSSL either, but that's possibly me being dense.↩
- Which I'm kind of happy about, since this would involve getting users to install and use Erlang.↩
- Again, that seems odd, but I'm not sure why yet.↩
- Which surprised the shit out of me, since M2Crypto is actually just a Python wrapper for OpenSSL.↩
- Odd that this worked while the above didn't, but I'll take it.↩
- That's probably the best case scenario anyway, since it means I can make a multi-platform client fairly easily.↩
- I have a soft spot for GNU, so I'd like to support this if at all possible. The PGP format has pretty thorough metadata attached, so delegating to GnuPG for messages using it will be reasonably straightforward.↩
- If you want M2Crypto to generate keys too, you'd just need to do↩