Subject | Secure Remote Password Protocol |
---|---|
Author | Jim Starkey |
Post date | 2010-12-15T19:50:30Z |
Attached is a self contained Java implementation of the Stanford
Secure Remote Password Protocol along with the 1024 bit group used as
test vector in RFC 5054. I've also attached a Java RC4 implementation.
Please feel free to use these in Firebird either directly or as a
reference implementation. Stanford also has unencumbered reference
implementations as well.
The standard describes an additional step when each side proves to the
other that it has computed the same session key without exposing the
session key or leaking any other information. An equally secure
alternative is simple to start encryption with the session key and see
if the next message makes sense. This can be done straightforwardly
without incurring another round trip.
The beauty of SRP is that it does mutual authentication in a single
message exchange, establishes a secure key for subsequent communication,
and does this all without the password present on the server.
SRP + RC4 is probably about a tenth of a percent of a full SSL/TLS
implementation. It's probably even smaller than the code to interface
with a full SSL/TLS implementation. And it gets you to exactly the same
place...
--
Jim Starkey
Founder, NimbusDB, Inc.
978 526-1376
----------
package nimbusdb.util;
import java.math.BigInteger;
import java.security.*;
import java.util.*;
public class RemoteGroup
{
BigInteger prime;
BigInteger generator;
BigInteger k;
static RemoteGroup group;
static Random random;
static
{
random = new Random(System.currentTimeMillis());
}
public RemoteGroup(String primeString, String generatorString)
{
MessageDigest sha1 = null;
try
{
sha1 = MessageDigest.getInstance("SHA1");
}
catch (NoSuchAlgorithmException exception)
{
}
generator = new BigInteger("2");
byte[] primeBytes = RemotePassword.getBytes(primeString);
prime = new BigInteger(1, primeBytes);
byte[] generatorBytes = generator.toByteArray();
sha1.update(primeBytes);
int pad = primeBytes.length - generatorBytes.length;
if (pad > 0)
sha1.update(new byte[pad]);
sha1.update(generatorBytes);
byte[] kBytes = sha1.digest();
k = new BigInteger(kBytes);
}
public static RemoteGroup getGroup (int groupSize)
{
if (group == null)
{
String prime = "EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C"+
"9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE4"+
"8E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B29"+
"7BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9A"+
"FD5138FE8376435B9FC61D2FC0EB06E3";
group = new RemoteGroup(prime, "02");
}
return group;
}
}
----------
package nimbusdb.util;
import java.math.*;
import java.security.*;
import java.util.*;
/*
* Order of battle for SRP handshake:
*
* 0. At account creation, the server generates
* a random salt and computes a password
* verifier from the account name, password,
* and salt.
*
* 1. Client generates random number
* as private key, computes public
* key.
*
* 2. Client sends server the account
* name and its public key.
* 3. Server receives account name, looks up
* salt and password verifier. Server
* generates random number as private key.
* Server computes public key from private
* key, account name, verifier, and salt.
*
* 4. Server sends client public key and salt
*
* 3. Client receives server public
* key and computes session key
* from server key, salt, account
* name, and password.
* 5. Server computes session key from client
* public key, client name, and verifier
*
* For full details, see http://www.ietf.org/rfc/rfc5054.txt
*
*/
public class RemotePassword
{
static byte[] hexDigits;
MessageDigest sha1;
BigInteger prime;
BigInteger generator;
BigInteger k;
BigInteger clientPrivateKey;
BigInteger clientPublicKey;
BigInteger serverPrivateKey;
BigInteger serverPublicKey;
BigInteger scramble;
Random random;
static
{
hexDigits = new byte[256];
for (int n = 0; n < 10; ++n)
hexDigits['0' + n] = (byte) n;
for (byte n = 0; n < 6; ++n)
{
hexDigits['a' + n] = (byte) (10 + n);
hexDigits['A' + n] = (byte) (10 + n);
}
}
public RemotePassword ()
{
RemoteGroup group = RemoteGroup.getGroup(1024);
prime = group.prime;
generator = group.generator;
k = group.k;
random = group.random;
try
{
sha1 = MessageDigest.getInstance("SHA1");
}
catch (NoSuchAlgorithmException exception)
{
}
}
/**
* @param args
*/
public static void main(String[] args)
{
String saltString = "BEB25379D1A8581EB5A727673A2441EE";
String a = "60975527035CF2AD1989806F0407210BC81EDC04E2762A56AFD529DDDA2D4393";
String b = "E487CB59D31AC550471E81F00F6928E01DDA08E974A004F49E61F5D105284D20";
RemotePassword server = new RemotePassword();
RemotePassword client = new RemotePassword();
String verifier = server.computeVerifier("alice", "password123", saltString);
String clientKey = client.setClientPrivateKey(a);
String serverKey = server.setServerPrivateKey(b, verifier);
byte key1[] = client.computeSessionKey("alice", "password123", saltString, serverKey);
byte key2[] = server.computeSessionKey(clientKey, verifier);
System.out.println();
}
public static BigInteger getBigInteger (String hex)
{
return new BigInteger(1, getBytes(hex));
}
public static byte[] getBytes (String hex)
{
int length = hex.length() / 2;
byte [] bytes = new byte[length];
for (int n = 0, c = 0; n < length; ++n, c += 2)
bytes[n] = (byte) ((hexDigits[hex.charAt(c)] << 4) | hexDigits[hex.charAt(c + 1)]);
return bytes;
}
public static String getHex (BigInteger number)
{
return getHex(number.toByteArray());
}
public static String getHex (byte [] rep)
{
int n = 0;
int length = rep.length;
if (rep[0] == 0)
{
++n;
--length;
}
byte [] hex = new byte[length * 2];
for (int c = 0; n < rep.length; ++n)
{
int b = rep[n] & 0xff;
int high = b >> 4;
int low = b & 0xf;
hex[c++] = (byte) ((high < 10) ? '0' + high : 'A' + high - 10);
hex[c++] = (byte) ((low < 10) ? '0' + low : 'A' + low - 10);
}
return new String(hex);
}
public BigInteger getUserHash (String account, String password, String salt)
{
sha1.reset();
sha1.update(account.getBytes());
sha1.update(":".getBytes());
sha1.update(password.getBytes());
byte[] hash1 = sha1.digest();
byte[] saltBytes = getBytes(salt);
sha1.reset();
sha1.update(saltBytes);
return new BigInteger(1, sha1.digest(hash1));
}
public String computeVerifier (String account, String password, String salt)
{
BigInteger x = getUserHash(account, password, salt);
BigInteger verifier = generator.modPow(x, prime);
byte [] result = verifier.toByteArray();
return getHex(result);
}
public String setClientPrivateKey(String key)
{
clientPrivateKey = new BigInteger(1, getBytes(key));
clientPublicKey = generator.modPow(clientPrivateKey, prime);
return getHex(clientPublicKey);
}
public String genClientKey()
{
clientPrivateKey = new BigInteger(256, random);
clientPublicKey = generator.modPow(clientPrivateKey, prime);
return getHex(clientPublicKey);
}
public String setServerPrivateKey(String key, String verifier)
{
return genServerKey(new BigInteger(1, getBytes(key)), verifier);
}
public String genServerKey(BigInteger privateKey, String verifier)
{
serverPrivateKey = privateKey; // b
BigInteger gb = generator.modPow(serverPrivateKey, prime); // g^b
BigInteger v = new BigInteger(1, getBytes(verifier)); // v
BigInteger kv = k.multiply(v);
kv = kv.mod(prime);
serverPublicKey = kv.add(gb);
serverPublicKey = serverPublicKey.mod(prime);
return getHex(serverPublicKey);
}
public String genServerKey(String verifier)
{
return genServerKey(new BigInteger(256, random), verifier);
}
public void computeScramble()
{
byte [] client = clientPublicKey.toByteArray();
byte [] server = serverPublicKey.toByteArray();
sha1.reset();
int n = (client[0] == 0) ? 1 : 0;
sha1.update(client, n, client.length - n);
n = (server[0] == 0) ? 1 : 0;
sha1.update(server, n, server.length - n);
scramble = new BigInteger(1, sha1.digest());
}
public byte[] computeSessionKey(String account, String password, String salt, String serverPubKey)
{
serverPublicKey = getBigInteger(serverPubKey);
computeScramble();
BigInteger x = getUserHash(account, password, salt); // x
BigInteger gx = generator.modPow(x, prime); // g^x
BigInteger kgx = k.multiply(gx).mod(prime); // kg^x
BigInteger diff = serverPublicKey.subtract(kgx).mod(prime); // B - kg^x
BigInteger ux = scramble.multiply(x).mod(prime); // ux
BigInteger aux = clientPrivateKey.add(ux).mod(prime); // A + ux
BigInteger sessionSecret = diff.modPow(aux, prime); // (B - kg^x) ^ (a + ux)
byte[] secret = sessionSecret.toByteArray();
int n = (secret[0] == 0) ? 1 : 0;
sha1.reset();
sha1.update(secret, n, secret.length - n);
return sha1.digest();
}
// Server session key
public byte[] computeSessionKey(String clientPubKey, String verifier)
{
clientPublicKey = getBigInteger(clientPubKey);
computeScramble();
BigInteger v = getBigInteger(verifier);
BigInteger vu = v.modPow(scramble, prime); // v^u
BigInteger Avu = clientPublicKey.multiply(vu).mod(prime); // Av^u
BigInteger sessionSecret = Avu.modPow(serverPrivateKey, prime); // (Av^u) ^ b
byte[] secret = sessionSecret.toByteArray();
int n = (secret[0] == 0) ? 1 : 0;
sha1.reset();
sha1.update(secret, n, secret.length - n);
return sha1.digest();
}
public String genSalt()
{
BigInteger n = new BigInteger(256, random);
return getHex(n);
}
}
----------
package nimbusdb.util;
public class RC4
{
byte[] state;
int s1;
int s2;
public RC4()
{
state = new byte[256];
}
/**
* @param args
*/
public static void main(String[] args)
{
byte[] key = "Key".getBytes();
RC4 cipher = new RC4();
cipher.setKey(key);
byte[] plaintext = "Plaintext".getBytes();
cipher.transform(plaintext, 0, plaintext.length);
}
public void setKey(byte[] key)
{
for (int n = 0; n < state.length; ++n)
state[n] = (byte) n;
for (int k1 = 0, k2 = 0; k1 < 256; ++k1)
{
k2 = (k2 + key[k1 % key.length] + state[k1]) & 0xff;
byte temp = state[k1];
state[k1] = state[k2];
state[k2] = temp;
}
s1 = s2 = 0;
}
public void transform (byte[] data, int offset, int length)
{
for (int n = offset, end = offset + length; n < end; ++n)
{
s1 = (s1 + 1) & 0xff;
s2 = (s2 + state[s1]) & 0xff;
byte temp = state[s1];
state[s1] = state[s2];
state[s2] = temp;
byte b = state[(state[s1] + state[s2]) & 0xff];
data[n] ^= b;
}
}
}
[Non-text portions of this message have been removed]
Secure Remote Password Protocol along with the 1024 bit group used as
test vector in RFC 5054. I've also attached a Java RC4 implementation.
Please feel free to use these in Firebird either directly or as a
reference implementation. Stanford also has unencumbered reference
implementations as well.
The standard describes an additional step when each side proves to the
other that it has computed the same session key without exposing the
session key or leaking any other information. An equally secure
alternative is simple to start encryption with the session key and see
if the next message makes sense. This can be done straightforwardly
without incurring another round trip.
The beauty of SRP is that it does mutual authentication in a single
message exchange, establishes a secure key for subsequent communication,
and does this all without the password present on the server.
SRP + RC4 is probably about a tenth of a percent of a full SSL/TLS
implementation. It's probably even smaller than the code to interface
with a full SSL/TLS implementation. And it gets you to exactly the same
place...
--
Jim Starkey
Founder, NimbusDB, Inc.
978 526-1376
----------
package nimbusdb.util;
import java.math.BigInteger;
import java.security.*;
import java.util.*;
public class RemoteGroup
{
BigInteger prime;
BigInteger generator;
BigInteger k;
static RemoteGroup group;
static Random random;
static
{
random = new Random(System.currentTimeMillis());
}
public RemoteGroup(String primeString, String generatorString)
{
MessageDigest sha1 = null;
try
{
sha1 = MessageDigest.getInstance("SHA1");
}
catch (NoSuchAlgorithmException exception)
{
}
generator = new BigInteger("2");
byte[] primeBytes = RemotePassword.getBytes(primeString);
prime = new BigInteger(1, primeBytes);
byte[] generatorBytes = generator.toByteArray();
sha1.update(primeBytes);
int pad = primeBytes.length - generatorBytes.length;
if (pad > 0)
sha1.update(new byte[pad]);
sha1.update(generatorBytes);
byte[] kBytes = sha1.digest();
k = new BigInteger(kBytes);
}
public static RemoteGroup getGroup (int groupSize)
{
if (group == null)
{
String prime = "EEAF0AB9ADB38DD69C33F80AFA8FC5E86072618775FF3C0B9EA2314C"+
"9C256576D674DF7496EA81D3383B4813D692C6E0E0D5D8E250B98BE4"+
"8E495C1D6089DAD15DC7D7B46154D6B6CE8EF4AD69B15D4982559B29"+
"7BCF1885C529F566660E57EC68EDBC3C05726CC02FD4CBF4976EAA9A"+
"FD5138FE8376435B9FC61D2FC0EB06E3";
group = new RemoteGroup(prime, "02");
}
return group;
}
}
----------
package nimbusdb.util;
import java.math.*;
import java.security.*;
import java.util.*;
/*
* Order of battle for SRP handshake:
*
* 0. At account creation, the server generates
* a random salt and computes a password
* verifier from the account name, password,
* and salt.
*
* 1. Client generates random number
* as private key, computes public
* key.
*
* 2. Client sends server the account
* name and its public key.
* 3. Server receives account name, looks up
* salt and password verifier. Server
* generates random number as private key.
* Server computes public key from private
* key, account name, verifier, and salt.
*
* 4. Server sends client public key and salt
*
* 3. Client receives server public
* key and computes session key
* from server key, salt, account
* name, and password.
* 5. Server computes session key from client
* public key, client name, and verifier
*
* For full details, see http://www.ietf.org/rfc/rfc5054.txt
*
*/
public class RemotePassword
{
static byte[] hexDigits;
MessageDigest sha1;
BigInteger prime;
BigInteger generator;
BigInteger k;
BigInteger clientPrivateKey;
BigInteger clientPublicKey;
BigInteger serverPrivateKey;
BigInteger serverPublicKey;
BigInteger scramble;
Random random;
static
{
hexDigits = new byte[256];
for (int n = 0; n < 10; ++n)
hexDigits['0' + n] = (byte) n;
for (byte n = 0; n < 6; ++n)
{
hexDigits['a' + n] = (byte) (10 + n);
hexDigits['A' + n] = (byte) (10 + n);
}
}
public RemotePassword ()
{
RemoteGroup group = RemoteGroup.getGroup(1024);
prime = group.prime;
generator = group.generator;
k = group.k;
random = group.random;
try
{
sha1 = MessageDigest.getInstance("SHA1");
}
catch (NoSuchAlgorithmException exception)
{
}
}
/**
* @param args
*/
public static void main(String[] args)
{
String saltString = "BEB25379D1A8581EB5A727673A2441EE";
String a = "60975527035CF2AD1989806F0407210BC81EDC04E2762A56AFD529DDDA2D4393";
String b = "E487CB59D31AC550471E81F00F6928E01DDA08E974A004F49E61F5D105284D20";
RemotePassword server = new RemotePassword();
RemotePassword client = new RemotePassword();
String verifier = server.computeVerifier("alice", "password123", saltString);
String clientKey = client.setClientPrivateKey(a);
String serverKey = server.setServerPrivateKey(b, verifier);
byte key1[] = client.computeSessionKey("alice", "password123", saltString, serverKey);
byte key2[] = server.computeSessionKey(clientKey, verifier);
System.out.println();
}
public static BigInteger getBigInteger (String hex)
{
return new BigInteger(1, getBytes(hex));
}
public static byte[] getBytes (String hex)
{
int length = hex.length() / 2;
byte [] bytes = new byte[length];
for (int n = 0, c = 0; n < length; ++n, c += 2)
bytes[n] = (byte) ((hexDigits[hex.charAt(c)] << 4) | hexDigits[hex.charAt(c + 1)]);
return bytes;
}
public static String getHex (BigInteger number)
{
return getHex(number.toByteArray());
}
public static String getHex (byte [] rep)
{
int n = 0;
int length = rep.length;
if (rep[0] == 0)
{
++n;
--length;
}
byte [] hex = new byte[length * 2];
for (int c = 0; n < rep.length; ++n)
{
int b = rep[n] & 0xff;
int high = b >> 4;
int low = b & 0xf;
hex[c++] = (byte) ((high < 10) ? '0' + high : 'A' + high - 10);
hex[c++] = (byte) ((low < 10) ? '0' + low : 'A' + low - 10);
}
return new String(hex);
}
public BigInteger getUserHash (String account, String password, String salt)
{
sha1.reset();
sha1.update(account.getBytes());
sha1.update(":".getBytes());
sha1.update(password.getBytes());
byte[] hash1 = sha1.digest();
byte[] saltBytes = getBytes(salt);
sha1.reset();
sha1.update(saltBytes);
return new BigInteger(1, sha1.digest(hash1));
}
public String computeVerifier (String account, String password, String salt)
{
BigInteger x = getUserHash(account, password, salt);
BigInteger verifier = generator.modPow(x, prime);
byte [] result = verifier.toByteArray();
return getHex(result);
}
public String setClientPrivateKey(String key)
{
clientPrivateKey = new BigInteger(1, getBytes(key));
clientPublicKey = generator.modPow(clientPrivateKey, prime);
return getHex(clientPublicKey);
}
public String genClientKey()
{
clientPrivateKey = new BigInteger(256, random);
clientPublicKey = generator.modPow(clientPrivateKey, prime);
return getHex(clientPublicKey);
}
public String setServerPrivateKey(String key, String verifier)
{
return genServerKey(new BigInteger(1, getBytes(key)), verifier);
}
public String genServerKey(BigInteger privateKey, String verifier)
{
serverPrivateKey = privateKey; // b
BigInteger gb = generator.modPow(serverPrivateKey, prime); // g^b
BigInteger v = new BigInteger(1, getBytes(verifier)); // v
BigInteger kv = k.multiply(v);
kv = kv.mod(prime);
serverPublicKey = kv.add(gb);
serverPublicKey = serverPublicKey.mod(prime);
return getHex(serverPublicKey);
}
public String genServerKey(String verifier)
{
return genServerKey(new BigInteger(256, random), verifier);
}
public void computeScramble()
{
byte [] client = clientPublicKey.toByteArray();
byte [] server = serverPublicKey.toByteArray();
sha1.reset();
int n = (client[0] == 0) ? 1 : 0;
sha1.update(client, n, client.length - n);
n = (server[0] == 0) ? 1 : 0;
sha1.update(server, n, server.length - n);
scramble = new BigInteger(1, sha1.digest());
}
public byte[] computeSessionKey(String account, String password, String salt, String serverPubKey)
{
serverPublicKey = getBigInteger(serverPubKey);
computeScramble();
BigInteger x = getUserHash(account, password, salt); // x
BigInteger gx = generator.modPow(x, prime); // g^x
BigInteger kgx = k.multiply(gx).mod(prime); // kg^x
BigInteger diff = serverPublicKey.subtract(kgx).mod(prime); // B - kg^x
BigInteger ux = scramble.multiply(x).mod(prime); // ux
BigInteger aux = clientPrivateKey.add(ux).mod(prime); // A + ux
BigInteger sessionSecret = diff.modPow(aux, prime); // (B - kg^x) ^ (a + ux)
byte[] secret = sessionSecret.toByteArray();
int n = (secret[0] == 0) ? 1 : 0;
sha1.reset();
sha1.update(secret, n, secret.length - n);
return sha1.digest();
}
// Server session key
public byte[] computeSessionKey(String clientPubKey, String verifier)
{
clientPublicKey = getBigInteger(clientPubKey);
computeScramble();
BigInteger v = getBigInteger(verifier);
BigInteger vu = v.modPow(scramble, prime); // v^u
BigInteger Avu = clientPublicKey.multiply(vu).mod(prime); // Av^u
BigInteger sessionSecret = Avu.modPow(serverPrivateKey, prime); // (Av^u) ^ b
byte[] secret = sessionSecret.toByteArray();
int n = (secret[0] == 0) ? 1 : 0;
sha1.reset();
sha1.update(secret, n, secret.length - n);
return sha1.digest();
}
public String genSalt()
{
BigInteger n = new BigInteger(256, random);
return getHex(n);
}
}
----------
package nimbusdb.util;
public class RC4
{
byte[] state;
int s1;
int s2;
public RC4()
{
state = new byte[256];
}
/**
* @param args
*/
public static void main(String[] args)
{
byte[] key = "Key".getBytes();
RC4 cipher = new RC4();
cipher.setKey(key);
byte[] plaintext = "Plaintext".getBytes();
cipher.transform(plaintext, 0, plaintext.length);
}
public void setKey(byte[] key)
{
for (int n = 0; n < state.length; ++n)
state[n] = (byte) n;
for (int k1 = 0, k2 = 0; k1 < 256; ++k1)
{
k2 = (k2 + key[k1 % key.length] + state[k1]) & 0xff;
byte temp = state[k1];
state[k1] = state[k2];
state[k2] = temp;
}
s1 = s2 = 0;
}
public void transform (byte[] data, int offset, int length)
{
for (int n = offset, end = offset + length; n < end; ++n)
{
s1 = (s1 + 1) & 0xff;
s2 = (s2 + state[s1]) & 0xff;
byte temp = state[s1];
state[s1] = state[s2];
state[s2] = temp;
byte b = state[(state[s1] + state[s2]) & 0xff];
data[n] ^= b;
}
}
}
[Non-text portions of this message have been removed]