Certifiably cheap
When I first announced built-in authentication components for Databinder, I made much of the care they take to store password hashes only and no actual passwords in the database. It’s one of those things we know we know we’re supposed to, but don’t always get around to doing when we build things from scratch.
The Internet in the middle
Storing the hash only, and preventing a disastrous theft of all passwords, is crucial. But it’s also nice to avoid ever sending a user’s actual password in the clear. Usually, we intend to avoid that by submitting password-bearing forms over a secure connection, but those intentions aren’t always carried out.
Effective use of secure HTTP connections unfortunately requires parting with a fair bit of cash. Certificate authorities must work to verify one’s identity before putting it on the cert, and they aren’t likely to work for free anytime soon.
You can always self-sign a certificate, or use a free authority, but don’t expect your users to be pleased with the threatening dialog boxes that pop up as they try to log in. Worse, some users will be unable to use those certificates at all, thanks to their overzealous IT departments. (Yes, overzealous—warnings are as good as exclusion. Phishing sites will always prefer inconspicuous non-signing over fire-alarm self-signing.)
We understand the threat of man-in-the-middle attacks, but the certificate authority defense tragically leads to more passwords being sent in the clear than otherwise would be. We’d rather our non-revenue-generating sites be exposed to an elite DNS hacker-in-the-middle than the entire dumb-hacking internet, thank you very much.
So I’ve begun to look at Databinder’s authentication components without the obviously-bunk assumption that they will be used over a secure connection. To be reasonably well protected, we must encrypt password data ourselves, using technology that used to be rocket science but now yields to any browser’s JavaScript interpreter: RSA public key encryption.
I’m not breaking ground here, just wrapping up the technology for easy use. This kind of password encryption doesn’t get a lot of respect, and the source code I dredged up was housed in sites looking either dated or downmarket, shall we say. (I had to give an e-mail address to get some of the code–the things I do for you guys.) As you might expect, the server-side implementations I came across were either ASP or “JSP.” This scrappy technique, unassuming but effective, deserved to be wrapped up in Wicket component.
Key generation
Since we’re sending passwords from the client to the server and never the other way around, we generate the encryption key pair on the server. We don’t want to generate a new key for each component instance, as it takes a significant amount of processor time. Instead, we can generate the 1024 bit key pair (still considered unfactorable) and use it for as long as the JVM is up:
private static KeyPair keypair;
static {
try {
keypair = KeyPairGenerator.getInstance("RSA").genKeyPair();
} catch (NoSuchAlgorithmException e) { ... }
}
We’ll send the public key to the client in spliced-together JavaScript. With that, it can encrypt the entered password and no observer with reasonably limited computation resources would be to determine the actual password. But! A clever observer could use the same encrypted string to log himself in, at least until the keys change.
To thwart that attack, we generate a random challenge string that is specific to a component instance and export it to JavaScript. This challenge string must be encrypted along with the password, and verified on the server, so that the encrypted string as a whole is valid only for one component instance (and therefore Web session).
Mathy JavaScript
RSA has been implemented in JavaScript a few times. After frustrating myself trying to get different of these to work with Java’s RSA implementation, I eventually landed on this tutorial promising to work with .NET, thinking that if it worked with at least one implementation besides itself it might work with Java’s. It did!
The meaty scripts provided in the tutorial bundle (which you can see in my svn repo without giving an e-mail address) are slightly altered versions of other scripts, and better-adhere the PKCS#1 v1.5 padding standard, or something. Anyway, did I mention it works?
The component adds an onsubmit
handler to the form that encrypts the password with a challenge string before sending, which won’t take more than a second or two on most computers. The scripts work with strings of hexadecimal digits, and it seemed to finicky to change the keys and encrypted strings to Base64, so hex it is for now.
Back at on the homestead
Decrypting the password in a Wicket component is easy if you can navigate the exception minefield:
protected Object convertValue(String[] value) throws ConversionException {
String enc = (String) super.convertValue(value);
if (enc == null)
return null;
try {
Cipher rsa = Cipher.getInstance("RSA");
rsa.init(Cipher.DECRYPT_MODE, keypair.getPrivate());
String dec = new String(rsa.doFinal(hex2data(enc)));
String[] toks = dec.split("\\|", 2);
if (toks.length != 2 || !toks[0].equals(challenge))
throw new ConversionException("incorrect or empy challenge value")
.setResourceKey("RSAPasswordTextField.failed.challenge");
return toks[1];
} catch (GeneralSecurityException e) { ... }
}
The decrypted value becomes the component’s converted input, and is later processed as plaintext (until SHA hashed for the database). This works perfectly, except with the EqualPasswordInputValidator
, which inherits its parent’s bad habit of comparing pre-conversion values. EqualPasswordConvertedInputValidator
comes to rescue, bearing ninety percent of its source code in its class name.
And that is the story of how RSAPasswordTextField
came to be. Never again, out of expediency or laziness, will I send a password in the clear.
At least, not through any Databinder application.
Add a comment