Wiki

Clone wiki

rsalogin / Home

Secure RSA login for JavaScript & PHP

This simple project offers web developers the option of using reasonably secure, encrypted logins without having a "real" SSL certificate and the corresponding server configuration.

Security considerations

This is not an SSL replacement! HTTPS (HTTP over SSL or TLS) does much more than what rsalogin can do: it encrypts every part of the client-server communication (not only chosen data such as login information) and it also offers a model of proving who the parties involved in communication are. This (though flawed and not nearly as secure as it should be) is usually good enough for sensitive applications.

How it works

The code is composed of two parts: the server part and the client part. The server part works with a RSA keypair and offers the public key from the pair to the client. The client encrypts login data (usually the username and password but actually can be any arbitrary data) and sends it to the server for verification.

The client side also stores the fingerprint of the server's public key and hostname (so you need to be especially careful if your site is available on different host names!) in HTML 5 storage, and verifies it each subsequent time the user visits the page. This security model is similar to that of SSH (which, should be noted, is trusted enough to protect the root access to a huge number of SSL-secured web servers).

Usage

At the server side, the application needs to create a RSALogin object with a keypair. The keypair may be created automatically (if the third argument to the RSALogin constructor is `true˙), but the whole process can be done externally using OpenSSL. The PHP session needs to be started before creating the RSALogin object, as it will hold a session-unique nonce value.

require_once("RSALogin.php");
require_once("RSALogin_KeyStore.php");

session_start();
RSALogin::clearSession();
$l = new RSALogin(new RSALogin_KeyStore("/tmp"), "test", true);

The clearSession() call is recommended for every top-level login form, as it causes the nonce and the stored client's IP address to be reset.

Next, the RSA public key needs to be passed to the client JavaScript code. This can be done simply by using the getJSPublicKeyCode() method:

echo $l->getJSPublicKeyCode("login");

This will result in the JavaScript code such as the following being output on the web page:

login = new RSALogin("dc83a...c58f", "010001", "rlfdHmfez6ayAiEI");

To make this work, the web page with the login form needs to include some JavaScript libraries, preferably in the HEAD section:

<script language="JavaScript" type="text/javascript" src="jsbn/jsbn_min.js"></script>
<script language="JavaScript" type="text/javascript" src="rsalogin.js"></script>

The client can check if the server's public key is stored and if the stored value matches the passed value, to guard against man-in-the-middle tampering:

if (login.checkStorage()) {
    if (!login.serverKeyStored())
        login.storeServerKey();
    else {
        if (!login.verifyServerKey())
            alert("Server key mismatch! This login may be unsecured!");
    }
} else
    alert("Your browser does not support HTML5 Storage, so we cannot validate the server key");

The actual login data can be encrypted and passed to the server by submitting a HTML form. In case of logins, it is very important to also include the "nonce" value as it helps avoid network traffic replay attacks.

function submit_login() {
    var username = document.forms["dummy"].elements["username"].value;
    var password = document.forms["dummy"].elements["password"].value;

    /* It is important to send back the nonce encrypted */
    var login_data = login.encryptWithPublicKey(username + "\n" + password + "\n" + login.nonce);

    document.forms["login_form"].elements["login_data"].value = login_data;
    document.forms["login_form"].submit();
}

The crucial part is the call to `encryptWithPublicKey()`. The usage is not limited to passing login information, but rather any data can be encrypted and passed back to the server.

The server can process the passed encrypted data in the following way:

if (isset($_POST['login_data'])) {
    $ld = $l->decryptWithPrivateKey($_POST['login_data']);
    list($username, $password, $nonce) = explode("\n", $ld);
    echo "<p>Attempted login with username=<b>{$username}</b>, password=<b>{$password}</b></p>\n";
    if (!$l->verifyNonce($nonce))
        echo "<p><b>Security problem! This login should be treated as invalid!</b></p>";
} else
    echo "<b>No login_data!</b>\n";

A complete example is given in the login_phase1.php and login_phase2.php files.

Miscellaneous

The default key size is 1024 bits, which is configurable in the RSALogin::$KEY_BITS static variable. The key size is directly related to how much data can be encrypted in the RSA operation; with a 1024-bit key, all the data (username, password, nonce) must fit in less then 128 bytes.

The RSALogin_KeyStore class implements a simple server-side key storage/management function, which stores the server's public and private keys in the specified directory. To ensure security, it is critical that this private key remains a secret (i.e. it must never be published). These keys are saved in files in the OpenSSL's PEM format.

Requirements

The PHP server code requires PHP >= 5.3 and the php's openssl extension module. The client side requires a modern JavaScript implementation with HTML5 localStorage support. All browsers released since 2010 support this.

Updated