On 12/24/11 13:01, Mark Rotteveel wrote:
> Is there any documentation available on SRP for driver developers. I 
> looked over the commit Alex did yesterday and although I think I get the 
> general gist of it, it would be helpful to have some additional 
> documentation.
>
> Mark

A lot of info is present on SRP. First of all - official RFC
http://www.ietf.org/rfc/rfc5054.txt

Next - a nice description of SRP by Jim Starkey in architect.

>   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...
>

I attach mentioned Java code here.

And some more comments.

To save round-trip from client to server PROTOCOL13 is using (in fact
very old, pre- fb1.0) ability to send authentication information in
p_cnct_user_id part of connect packet. User_id itself has clumplets
internal structure, and in P13 I've added new tags to it:
CNCT_specific_data      = 7;    // Some data, needed for user
verification on server
CNCT_plugin_name        = 8;    // Name of plugin, which generated that data
CNCT_login              = 9;    // Same data as isc_dpb_user_name
CNCT_plugin_list        = 10;   // List of plugins, available on client

If this packet comes to old server, this new tags are safely ignored.
But there is a small problem with CNCT_specific_data. Size of clumplet
is limited to 255 bytes, and we can't change it in order to keep
backward compatibility. But plugins may need to pass more specific data
than 255 bytes, therefore we may have >1 clumplet tagged
CNCT_specific_data. First data byte must contain number of this chain
(with length up to 254 bytes) in the resulting specific data. I.e. if we
have 2 clumplets:
CNCT_specific_data, \3, \1, D, E        // 3 - data length, 1 - chain number
and
CNCT_specific_data, \4, \0, A, B, C    // 4 - data length, 0 - chain number
they will be collected in specific data string ABCDE.
Server may completely ignore this data if ti does not work with plugin
CNCT_plugin_name.

Information from server is returned to client in accept packet. It
contains agreed plugin list (plugins known to both client and server),
name of current plugin to work with and may be specific data to pass to
that plugin.

Next step is performed at attach phase. All required data is passed in
DPB. Since firebird3 and protocol13 we may use version2 DPB, which may
have clumplets with size up to 4Gb-1 (format is same with one used
internally for monitoring tables). Firebird code automatically converts
DPB v1 to v2 when >255 bytes clumplet is added to it.

If 2 roundtrips is not enough to complete authentication, a special
operation op_cont_authentication is used to continue (sent from server
to client and from client to server) as long as process is not complete.
Nothing outstanding with format in data in it on my mind.

This all was about database attachment process. Service attach contains
same basic operations, but the take place in other points of
client-server conversation, which is related with multiple security
databases and multiple providers in fb3. If required I'm ready to
continue about services after additional requests.


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;
                        }
                }
        }
------------------------------------------------------------------------------
Write once. Port to many.
Get the SDK and tools to simplify cross-platform app development. Create 
new or port existing apps to sell to consumers worldwide. Explore the 
Intel AppUpSM program developer opportunity. appdeveloper.intel.com/join
http://p.sf.net/sfu/intel-appdev
Firebird-Devel mailing list, web interface at 
https://lists.sourceforge.net/lists/listinfo/firebird-devel

Reply via email to