5

I'm building a website and my payment methods will be Google Checkout and Paypal. There will be links/buttons which will redirect the user to the secure Google/Paypal sites for processing the payments. This means I do not need the $150/year added expense and complexity of installing SSL certificates for my site.

However I would like to encrypt user's passwords as they are logging in so that if they are on a network some malicious person running FireSheep etc can't read the user's actual password as it is being sent to the server. The rest of the site doesn't need encryption as it's not really sensitive data and would probably slow the user experience down significantly.

My thoughts are this could be implemented with public key cryptography. Lets say the process goes something like this:

  1. Public key is in the JavaScript external file, private key in PHP on the server
  2. User enters their username and password into the form and clicks submit
  3. The JavaScript runs and encrypts the password, storing it back in the text field
  4. Form is submitted to server and the password is decrypted with PHP
  5. Plain text password in PHP is salted & hashed then compared to hash in database.
  6. Possible similar process for the registration/change password functions.

I'm thinking something like RSA would do the trick. But I've hunted around the net for a working JavaScript library to do it but none seem to be compatible with the PHP libraries available. At any rate it needs to generate a set of keys that are compatible with the JavaScript and PHP.

Anyone know of an actual working solution for this? If not how about we write one then open source it. Unfortunately writing encryption/decryption code is pretty complex so I don't really know exactly what the existing libraries are doing and how to modify them to make it work. I already have protection for session fixation/hijacking so I'm not interested in that. Just interested in encrypting the data before it gets to the web server.

NB: Please don't post a bunch of links to standalone Javascript or PHP encryption libraries, I've found those already on Google. That's not actually useful. What I need is code for JavaScript encryption AND PHP decryption that actually works together harmoniously to produce the intended result outlined above.

Also if you could refrain from posting comments like "just use SSL". I'd actually like a solution to this exact problem even if it's not best practice, it would be interesting none the less.

Many thanks!

zuallauz
  • 4,328
  • 11
  • 43
  • 54
  • 2
    My advice would be to suck it up and pay the $150. If you're processing payments, the user is going to want to be using SSL, and by the time you implement a secure system (if indeed you do) you will have most likely blown way more than $150 no matter what your being paid/paying yourself per hour. – aubreyrhodes Apr 20 '11 at 01:46
  • 5
    This will in no way stop an attack. If I can sniff and forge a plaintext password, I can sniff and forge an encripted password. The encripted text *Becomes* the password. – Michael Jasper Apr 20 '11 at 15:39
  • Michael is absolutely right, I considered this approach myself then realised the shortfalls of it. – Nick May 20 '11 at 13:45
  • @Nick Jeffrey provided the solution [below](http://stackoverflow.com/questions/5724650/ssl-alternative-encrypt-password-with-javascript-submit-to-php-to-decrypt/5724699#5724699) and I have implemented it successfully. – zuallauz May 25 '11 at 08:49
  • @aubreyrhodes There are a number of payment providers like PayPal and even most merchant facilities that allow you to offload the secure processing of Credit Cards and other data for the payment onto their HTTPS site. The user goes to your site, adds the items they want to the cart and is redirected to the secure Payment site with HTTPS. An attacker has no useful data. There's no need for the expense of the SSL certificates unless you're wanting a complete integrated shopping cart and payment experience where the user never leaves your site. This also leaves you with some of the liability. – zuallauz May 25 '11 at 08:56
  • 1) An SSL certificate doesn't cost 150$ per year, it's usually below 10$ per year. 2) Any javascript crypto delivered without SSL can be broken by an active attacker. It is impossible to secure yourself against a MitM with JS since the MitM can simply replace your javascript or insert their own javascript. – CodesInChaos Aug 14 '14 at 15:09

6 Answers6

18

Only one problem: An attacker does not need to know the actual password. All he needs to see is the value that is sent to the server. This value allows the user to log in. It does not matter what that value is; whether it's plaintext, encrypted text or a picture of a cat. It's just a token that authenticates the user. If an attacker can see this token and repeat the same request and that same request allows him to log in, you gained nothing.

deceze
  • 510,633
  • 85
  • 743
  • 889
  • 1
    @deceze: Not really. See my answer for a replay-resistant cleartext login protocol. – Jeffrey Hantin Apr 20 '11 at 01:49
  • 1
    But if it is PKI and the public key is on the client side, how will they have the key to decrypt? –  Apr 20 '11 at 01:49
  • 2
    @0A0D That's the point, they don't need to actually decrypt it. – deceze Apr 20 '11 at 01:53
  • 1
    @Jeffrey Indeed, some sort of nonce needs to be used. Upvoted your answer. Without SSL this may all still be susceptible to simple session hijacking, if the session token is not discarded immediately as well. – deceze Apr 20 '11 at 01:54
  • 2
    @deceze Point taken. SSL certificate pricing is finally getting within hailing distance of domain registration pricing now, so there's really little excuse. However, I do rather wish that browsers would support anonymous SSL without throwing up a big scary warning AND without displaying any "secure connection" chrome, so it looks just like a regular unsecured connection. If nothing else, that would make life somewhat more difficult for hijackers -- they'd have to MITM instead of just sniff. – Jeffrey Hantin Apr 20 '11 at 02:05
  • @deceze I already have protection in place for session hijacking, fixation etc. So even if they sniff the Session ID and cookie being sent it won't be usable for the attacker. I'm interested in protecting the actual plaintext password before it's sent. – zuallauz Apr 20 '11 at 02:41
  • 1
    @deceze: I'm sorry, I'm not following you. That's the point of asymmetric encryption, right? I can send you my public key encrypted data in the clear and it does not matter if you do not have the private key to decrypt. –  Apr 20 '11 at 11:15
  • 4
    @0A0D And I'm saying I'm *not even interested* in decrypting your encrypted password. :) I'll just sniff the encrypted password you're sending to the server and forge the exact same request, sending the same encrypted password to the server, which will allow me to log in the same way you do. Without a nonce this replay attack is trivial and I don't care whether or how or how often you encrypt your password. – deceze Apr 20 '11 at 12:09
  • 1
    @deceze: Right, now I see what you are getting at. –  Apr 20 '11 at 12:37
  • @deceze That would work if you only ever generate one key. Have the server send a new public key to the client before each request, invalidating that key after the next request and sending a new one. Whamo, copypasta of the RSA encrypted password string doesn't work anymore. – user2867288 Oct 09 '14 at 02:53
7

RSA is overkill; what you probably need is a simple challenge-response protocol. For example:

  • Generate a random nonce value; this helps prevent replay attacks.
  • Send that nonce value and the password salt to the browser along with the rest of the login form.
    • You are storing passwords in salted and hashed form, right?
  • When the user enters a password, have the script on the form compute and send back hash(hash(password, salt), nonce) instead.
  • When the server receives the form submission, have it compute hash(storedSaltedPassword, nonce) and verify that it equals the submitted value.
    • Retain the nonce value at the server; don't trust the client to echo it back to you, or your replay protection is gone.

The weakness of this scheme is that the password hashes in the database are in some sense password-equivalent; while it's likely infeasible to extract the original password used to produce those hashes, knowledge of the stored hash is sufficient to impersonate the user on your site.

SSL certificates serve an entirely different purpose: the purpose of an SSL certificate is to make it difficult for a third-party rogue server to claim to be your server, because it doesn't have a certificate signed by some mutually trusted third party that it belongs on your domain. On the other hand, if you can't stop a rogue server from impersonating yours, you can't protect your users from giving their password to that rogue server, cryptography notwithstanding.

Kobi
  • 135,331
  • 41
  • 252
  • 292
Jeffrey Hantin
  • 35,734
  • 7
  • 75
  • 94
  • @Jeffrey wouldn't the attacker be able to sniff the nonce and salt as the page got sent to the client. Wouldn't they be able to recreate the hash? Also with the GoDaddy SSL certs I think you have to be hosting your site with them. Also you have to renew each year so the price could go up. – zuallauz Apr 20 '11 at 02:58
  • 1
    @zoszsoz You certainly don't have to host with them to use their certificates. The verification process is faster if you registered the domain with them, though. As for the protocol: `hash` must be a one-way function - given `y` and `z` such that `z = hash(x, y)`, it must not be feasible to compute `x`. In order to compute the response value, the client has to know `hash(password, salt)` -- the server supplies `salt` so the client can derive it from `password`. `nonce` is just to prevent reuse of a response. – Jeffrey Hantin Apr 20 '11 at 03:54
  • @Jeffrey Thanks yes I see how this could work and I may implement it like this. I would however still be interested in a JavaScript encryption/PHP decryption solution in case I want to encrypt other fields and use the decrypted plaintext at the server end. Would be educational at least. – zuallauz Apr 20 '11 at 05:40
  • @zoszsoz: Why not use self-signed SSL certificates if you are concerned about the cost? –  Apr 20 '11 at 13:03
  • @Jeffrey How long do I need to keep the nonce for on the server? The duration of the session? Or in the database permanently? @0A0D Potentially I could use self signed certs, but don't they throw up a nasty warning page in the browser before you can accept them? Kinda don't want potential customers seeing that. – zuallauz Apr 21 '11 at 00:39
  • @Jeffrey Regarding the second step of 'sending the salt to the browser with the form'. Each salt should be unique to each user account and stored in a separate database field along with another field for the hashed password. When the user enters a username on the form I'd have to use AJAX to check the database for that username then send the salt corresponding to that user account back to the page. If their browser remembers the username/pw and they submit the form immediately the AJAX wont have returned the salt in time so will not work. – zuallauz Apr 21 '11 at 02:28
  • 1
    @zoszsoz If you're not doing it as two separate pages (username on one, password on the other) then you probably can't use a submittable form -- instead, the whole thing needs to be AJAX. When the login button is clicked or enter is pressed, fire off the AJAX request with the username to retrieve a challenge, then send the response in a second call. – Jeffrey Hantin Apr 21 '11 at 22:33
  • @Jeffrey Many thanks, I actually got it working! And it works quite well! [Check out the implementation](http://zoszsoz.blogspot.com/2011/04/secure-log-on-form-hashing-passwords-in.html). Does it look alright? I'm unsure how I'd get it to work for the register or edit-password pages though. These pages would need to store the hashed (password + salt) in the database. So if the page is sending through the hashed (salt+password+nonce), I won't be able to extract just the salt+password hash. Any ideas? – zuallauz Apr 23 '11 at 05:03
  • 2
    @zoszsoz This protocol only works for password verification, not for account creation or password change. Account creation is a tough one, since you don't have an existing trust anchor, but Diffie-Hellman key agreement, then using the resulting key to encipher the password, would suffice to stop a passive sniffer. – Jeffrey Hantin Apr 25 '11 at 22:52
3

First, I don't think this is a good idea. I found some examples using Google that may be useful for you (I have not tested these, however):

GPL JavaScript Public Key Encryption

RSA Public Key Encryption Test in JavaScript

PGP Encryption in JavaScript

RSA Algorithm Example in JavaScript

You should establish some salting mechanism to salt every encrypted value otherwise the key could get compromised.

  • Agree about the salt. However I need the PHP libraries and code that work with this JavaScript to actually decrypt it. That's the hard part, finding the JavaScript and PHP that actually work together. I've got two bits of wood but no glue to stick them together. – zuallauz Apr 20 '11 at 02:44
  • @zoszsoz: Why don't you use self-signed certificates? They are free are better than any roll-your-own encryption via PHP or JavaScript. –  Apr 20 '11 at 12:57
2

http://www.jcryption.org/ -- Is the combination you are looking for.

MartyIX
  • 27,828
  • 29
  • 136
  • 207
1

You don't need to encrypt the password. You need to hash the password. You really really don't want to have any access to the plaintext password yourself whatsoever, otherwise you lose non-repudiation, which has serious legal consequences. You need to investigate the meaning of this thoroughly before proceeeding.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • 2
    except he still needs to pass a salt or nonce to the client-side and store that on the server side so that he can compare the hashes. –  Apr 20 '11 at 13:01
0

This is the code to take the input and then encrypt the content by java script The entire code is also available in github.you guys can search for it encrypt_js_decrypt_php. The problem was running since long.I have come up with the solution.Just import it into localhost.

<html>

<input type="text" id="code" name="code"/>
<input type="submit" name="submit" value="submit" onclick="return encryptCode();"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script type="text/javascript">
function rc4(key, str)
{
    var s = [], j = 0, x, res = '';
    for (var i = 0; i < 256; i++) 
    {
        s[i] = i;
    }
    for (i = 0; i < 256; i++) 
    {
        j = (j + s[i] + key.charCodeAt(i % key.length)) % 256;
        x = s[i];
        s[i] = s[j];
        s[j] = x;
    }
    i = 0;
    j = 0;
    for (var y = 0; y < str.length; y++) 
    {
        i = (i + 1) % 256;
        j = (j + s[i]) % 256;
        x = s[i];
        s[i] = s[j];
        s[j] = x;
        res += String.fromCharCode(str.charCodeAt(y) ^ s[(s[i] + s[j]) % 256]);
    }
    return res;
}

function encryptCode()
{
  var value = document.getElementById("code").value;
  var key = "secretKeyToProvide";  /*--Provide Your secret key here--*/
  var codeValue = rc4(key, value);
  var arr = {code:codeValue, Age:25};
  $.ajax({
                url: "response.php",
                type: "POST",
                data: JSON.stringify(arr),
                dataType: 'json',
                async: false,
                contentType: 'application/json; charset=utf-8',
                success: function(data) 
                {
                    alert(data);
                }
            });   
}
</script>
</html>

Now,lets decrypt the code in php

<?php

function mb_chr($char) 
{
    return mb_convert_encoding('&#'.intval($char).';', 'UTF-8', 'HTML-ENTITIES');
}

function mb_ord($char)
{
    $result = unpack('N', mb_convert_encoding($char, 'UCS-4BE', 'UTF-8'));
    if (is_array($result) === true) 
    {
        return $result[1];
    }
        return ord($char);
}

function rc4($key, $str) 
{   
    if (extension_loaded('mbstring') === true) 
    {
        mb_language('Neutral');
        mb_internal_encoding('UTF-8');
        mb_detect_order(array('UTF-8', 'ISO-8859-15', 'ISO-8859-1', 'ASCII'));
    }
    $s = array();
    for ($i = 0; $i < 256; $i++)
    {
        $s[$i] = $i;
    }
    $j = 0;
    for ($i = 0; $i < 256; $i++)
    {
        $j = ($j + $s[$i] + mb_ord(mb_substr($key, $i % mb_strlen($key), 1))) % 256;
        $x = $s[$i];
        $s[$i] = $s[$j];
        $s[$j] = $x;
    }
    $i = 0;
    $j = 0;
    $res = '';
    for ($y = 0; $y < mb_strlen($str); $y++)
    {
        $i = ($i + 1) % 256;
        $j = ($j + $s[$i]) % 256;
        $x = $s[$i];
        $s[$i] = $s[$j];
        $s[$j] = $x;
        $res .= mb_chr(mb_ord(mb_substr($str, $y, 1)) ^ $s[($s[$i] + $s[$j]) % 256]);
    }
    return $res;
}

$request_body = file_get_contents('php://input');
$json = json_decode($request_body);
$secretCode =$json->code ;
$age =$json->Age  ;
$key = "secretKeyToProvide";  /*--Provide Your secret key here what you have given in javascript--*/
$decryptedSecretCode  = rc4($key, $secretCode) ;
echo $decryptedSecretCode;
exit;
?>
Dibyendu Konar
  • 175
  • 1
  • 1
  • 12