1
0
mirror of https://github.com/danog/fast-srp.git synced 2024-11-26 20:04:49 +01:00

Initial import

This commit is contained in:
Gerardo Pirla Diaz 2015-09-07 15:23:26 +02:00
parent b97a9dd7b4
commit f33302df91
9 changed files with 2811 additions and 1 deletions

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2015 Zarmack Tanen
Copyright (c) 2012-2013 Jed Parsons
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

3
index.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = require('./lib/srp');
module.exports.params = require('./lib/params');

1542
lib/jsbn.js Normal file

File diff suppressed because it is too large Load Diff

182
lib/params.js Normal file
View File

@ -0,0 +1,182 @@
/*
* SRP Group Parameters
* http://tools.ietf.org/html/rfc5054#appendix-A
*
* The 1024-, 1536-, and 2048-bit groups are taken from software
* developed by Tom Wu and Eugene Jhong for the Stanford SRP
* distribution, and subsequently proven to be prime. The larger primes
* are taken from [MODP], but generators have been calculated that are
* primitive roots of N, unlike the generators in [MODP].
*
* The 1024-bit and 1536-bit groups MUST be supported.
*/
// since these are meant to be used internally, all values are numbers. If
// you want to add parameter sets, you'll need to convert them to bignums.
"use strict";
var BigInteger = require('./jsbn');
function hex(s) {
return new BigInteger(s.split(/\s/).join(''), 16);
}
module.exports = {
1024: {
N_length_bits: 1024,
N: hex(' EEAF0AB9 ADB38DD6 9C33F80A FA8FC5E8 60726187 75FF3C0B 9EA2314C'
+'9C256576 D674DF74 96EA81D3 383B4813 D692C6E0 E0D5D8E2 50B98BE4'
+'8E495C1D 6089DAD1 5DC7D7B4 6154D6B6 CE8EF4AD 69B15D49 82559B29'
+'7BCF1885 C529F566 660E57EC 68EDBC3C 05726CC0 2FD4CBF4 976EAA9A'
+'FD5138FE 8376435B 9FC61D2F C0EB06E3'),
g: hex('02'),
hash: 'sha1'},
1536: {
N_length_bits: 1536,
N: hex(' 9DEF3CAF B939277A B1F12A86 17A47BBB DBA51DF4 99AC4C80 BEEEA961'
+'4B19CC4D 5F4F5F55 6E27CBDE 51C6A94B E4607A29 1558903B A0D0F843'
+'80B655BB 9A22E8DC DF028A7C EC67F0D0 8134B1C8 B9798914 9B609E0B'
+'E3BAB63D 47548381 DBC5B1FC 764E3F4B 53DD9DA1 158BFD3E 2B9C8CF5'
+'6EDF0195 39349627 DB2FD53D 24B7C486 65772E43 7D6C7F8C E442734A'
+'F7CCB7AE 837C264A E3A9BEB8 7F8A2FE9 B8B5292E 5A021FFF 5E91479E'
+'8CE7A28C 2442C6F3 15180F93 499A234D CF76E3FE D135F9BB'),
g: hex('02'),
hash: 'sha1'},
2048: {
N_length_bits: 2048,
N: hex(' AC6BDB41 324A9A9B F166DE5E 1389582F AF72B665 1987EE07 FC319294'
+'3DB56050 A37329CB B4A099ED 8193E075 7767A13D D52312AB 4B03310D'
+'CD7F48A9 DA04FD50 E8083969 EDB767B0 CF609517 9A163AB3 661A05FB'
+'D5FAAAE8 2918A996 2F0B93B8 55F97993 EC975EEA A80D740A DBF4FF74'
+'7359D041 D5C33EA7 1D281E44 6B14773B CA97B43A 23FB8016 76BD207A'
+'436C6481 F1D2B907 8717461A 5B9D32E6 88F87748 544523B5 24B0D57D'
+'5EA77A27 75D2ECFA 032CFBDB F52FB378 61602790 04E57AE6 AF874E73'
+'03CE5329 9CCC041C 7BC308D8 2A5698F3 A8D0C382 71AE35F8 E9DBFBB6'
+'94B5C803 D89F7AE4 35DE236D 525F5475 9B65E372 FCD68EF2 0FA7111F'
+'9E4AFF73'),
g: hex('02'),
hash: 'sha256'},
3072: {
N_length_bits: 3072,
N: hex(' FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08'
+'8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B'
+'302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9'
+'A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6'
+'49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8'
+'FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D'
+'670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C'
+'180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718'
+'3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D'
+'04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D'
+'B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226'
+'1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C'
+'BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC'
+'E0FD108E 4B82D120 A93AD2CA FFFFFFFF FFFFFFFF'),
g: hex('05'),
hash: 'sha256'},
4096: {
N_length_bits: 4096,
N: hex(' FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08'
+'8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B'
+'302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9'
+'A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6'
+'49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8'
+'FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D'
+'670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C'
+'180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718'
+'3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D'
+'04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D'
+'B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226'
+'1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C'
+'BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC'
+'E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26'
+'99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB'
+'04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2'
+'233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127'
+'D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199'
+'FFFFFFFF FFFFFFFF'),
g: hex('05'),
hash: 'sha256'},
6244: {
N_length_bits: 6244,
N: hex(' FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08'
+'8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B'
+'302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9'
+'A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6'
+'49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8'
+'FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D'
+'670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C'
+'180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718'
+'3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D'
+'04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D'
+'B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226'
+'1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C'
+'BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC'
+'E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26'
+'99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB'
+'04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2'
+'233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127'
+'D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492'
+'36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD F8FF9406'
+'AD9E530E E5DB382F 413001AE B06A53ED 9027D831 179727B0 865A8918'
+'DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B DB7F1447 E6CC254B 33205151'
+'2BD7AF42 6FB8F401 378CD2BF 5983CA01 C64B92EC F032EA15 D1721D03'
+'F482D7CE 6E74FEF6 D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F'
+'BEC7E8F3 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA'
+'CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328 06A1D58B'
+'B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C DA56C9EC 2EF29632'
+'387FE8D7 6E3C0468 043E8F66 3F4860EE 12BF2D5B 0B7474D6 E694F91E'
+'6DCC4024 FFFFFFFF FFFFFFFF'),
g: hex('05'),
hash: 'sha256'},
8192: {
N_length_bits: 8192,
N: hex(' FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1 29024E08'
+'8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD EF9519B3 CD3A431B'
+'302B0A6D F25F1437 4FE1356D 6D51C245 E485B576 625E7EC6 F44C42E9'
+'A637ED6B 0BFF5CB6 F406B7ED EE386BFB 5A899FA5 AE9F2411 7C4B1FE6'
+'49286651 ECE45B3D C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8'
+'FD24CF5F 83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D'
+'670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B E39E772C'
+'180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9 DE2BCBF6 95581718'
+'3995497C EA956AE5 15D22618 98FA0510 15728E5A 8AAAC42D AD33170D'
+'04507A33 A85521AB DF1CBA64 ECFB8504 58DBEF0A 8AEA7157 5D060C7D'
+'B3970F85 A6E1E4C7 ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226'
+'1AD2EE6B F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C'
+'BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31 43DB5BFC'
+'E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7 88719A10 BDBA5B26'
+'99C32718 6AF4E23C 1A946834 B6150BDA 2583E9CA 2AD44CE8 DBBBC2DB'
+'04DE8EF9 2E8EFC14 1FBECAA6 287C5947 4E6BC05D 99B2964F A090C3A2'
+'233BA186 515BE7ED 1F612970 CEE2D7AF B81BDD76 2170481C D0069127'
+'D5B05AA9 93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34028492'
+'36C3FAB4 D27C7026 C1D4DCB2 602646DE C9751E76 3DBA37BD F8FF9406'
+'AD9E530E E5DB382F 413001AE B06A53ED 9027D831 179727B0 865A8918'
+'DA3EDBEB CF9B14ED 44CE6CBA CED4BB1B DB7F1447 E6CC254B 33205151'
+'2BD7AF42 6FB8F401 378CD2BF 5983CA01 C64B92EC F032EA15 D1721D03'
+'F482D7CE 6E74FEF6 D55E702F 46980C82 B5A84031 900B1C9E 59E7C97F'
+'BEC7E8F3 23A97A7E 36CC88BE 0F1D45B7 FF585AC5 4BD407B2 2B4154AA'
+'CC8F6D7E BF48E1D8 14CC5ED2 0F8037E0 A79715EE F29BE328 06A1D58B'
+'B7C5DA76 F550AA3D 8A1FBFF0 EB19CCB1 A313D55C DA56C9EC 2EF29632'
+'387FE8D7 6E3C0468 043E8F66 3F4860EE 12BF2D5B 0B7474D6 E694F91E'
+'6DBE1159 74A3926F 12FEE5E4 38777CB6 A932DF8C D8BEC4D0 73B931BA'
+'3BC832B6 8D9DD300 741FA7BF 8AFC47ED 2576F693 6BA42466 3AAB639C'
+'5AE4F568 3423B474 2BF1C978 238F16CB E39D652D E3FDB8BE FC848AD9'
+'22222E04 A4037C07 13EB57A8 1A23F0C7 3473FC64 6CEA306B 4BCBC886'
+'2F8385DD FA9D4B7F A2C087E8 79683303 ED5BDD3A 062B3CF5 B3A278A6'
+'6D2A13F8 3F44F82D DF310EE0 74AB6A36 4597E899 A0255DC1 64F31CC5'
+'0846851D F9AB4819 5DED7EA1 B1D510BD 7EE74D73 FAF36BC3 1ECFA268'
+'359046F4 EB879F92 4009438B 481C6CD7 889A002E D5EE382B C9190DA6'
+'FC026E47 9558E447 5677E9AA 9E3050E2 765694DF C81F56E8 80B96E71'
+'60C980DD 98EDD3DF FFFFFFFF FFFFFFFF'),
g: hex('13'),
hash: 'sha256'}
};

456
lib/srp.js Normal file
View File

@ -0,0 +1,456 @@
"use strict";
var crypto = require('crypto');
var assert = require('assert');
var util = require('util');
var BigInteger = require('./jsbn');
var zero = new BigInteger(0);
function assert_(val, msg) {
if (!val)
throw new Error(msg||"assertion");
}
/*
* If a conversion is explicitly specified with the operator PAD(),
* the integer will first be implicitly converted, then the resultant
* byte-string will be left-padded with zeros (if necessary) until its
* length equals the implicitly-converted length of N.
*
* params:
* n (buffer) Number to pad
* len (int) length of the resulting Buffer
*
* returns: buffer
*/
function padTo(n, len) {
assertIsBuffer(n, "n");
var padding = len - n.length;
/*
console.log("n = " + n.toString('hex'));
console.log("n.length = " + n.length);
console.log("len = " + len);
*/
assert_(padding > -1, "Negative padding. Very uncomfortable.");
var result = new Buffer(len);
result.fill(0, 0, padding);
n.copy(result, padding);
assert.equal(result.length, len);
return result;
};
function padToN(number, params) {
assertIsBigInteger(number);
//! return padTo(number.toBuffer(), params.N_length_bits/8);
var n = number.toString(16).length % 2 != 0 ? "0" + number.toString(16) : number.toString(16);
return padTo(new Buffer(n, 'hex'), params.N_length_bits / 8);
}
function padToH(number, params) {
assertIsBigInteger(number);
var hashlen_bits;
if (params.hash === "sha1")
hashlen_bits = 160;
else if (params.hash === "sha256")
hashlen_bits = 256;
else if (params.hash === "sha512")
hashlen_bits = 512;
else
throw Error("cannot determine length of hash '"+params.hash+"'");
//! return padTo(number.toBuffer(), hashlen_bits / 8);
return padTo(new Buffer(number.toString(16), 'hex'), hashlen_bits / 8);
}
function assertIsBuffer(arg, argname) {
argname = argname || "arg";
assert_(Buffer.isBuffer(arg), "Type error: "+argname+" must be a buffer");
}
function assertIsNBuffer(arg, params, argname) {
argname = argname || "arg";
assert_(Buffer.isBuffer(arg), "Type error: "+argname+" must be a buffer");
if (arg.length != params.N_length_bits/8)
assert_(false, argname+" was "+arg.length+", expected "+(params.N_length_bits/8));
}
function assertIsBigInteger(arg) {
assert_(arg.constructor.name === 'BigInteger', "Type error: " + arg.argname + " must be a BigInteger");
}
/*
* compute the intermediate value x as a hash of three buffers:
* salt, identity, and password. And a colon. FOUR buffers.
*
* x = H(s | H(I | ":" | P))
*
* params:
* salt (buffer) salt
* I (buffer) user identity
* P (buffer) user password
*
* returns: x (bignum) user secret
*/
function getx(params, salt, I, P) {
assertIsBuffer(salt, "salt (salt)");
assertIsBuffer(I, "identity (I)");
assertIsBuffer(P, "password (P)");
var hashIP = crypto.createHash(params.hash)
.update(Buffer.concat([I, new Buffer(':'), P]))
.digest();
var hashX = crypto.createHash(params.hash)
.update(salt)
.update(hashIP)
.digest();
//! return bignum.fromBuffer(hashX);
return(new BigInteger(hashX));
};
/*
* The verifier is calculated as described in Section 3 of [SRP-RFC].
* We give the algorithm here for convenience.
*
* The verifier (v) is computed based on the salt (s), user name (I),
* password (P), and group parameters (N, g).
*
* x = H(s | H(I | ":" | P))
* v = g^x % N
*
* params:
* params (obj) group parameters, with .N, .g, .hash
* salt (buffer) salt
* I (buffer) user identity
* P (buffer) user password
*
* returns: buffer
*/
function computeVerifier(params, salt, I, P) {
assertIsBuffer(salt, "salt (salt)");
assertIsBuffer(I, "identity (I)");
assertIsBuffer(P, "password (P)");
//* var v_num = params.g.powm(getx(params, salt, I, P), params.N);
var v_num = params.g.modPow(getx(params, salt, I, P), params.N);
return padToN(v_num, params);
};
/*
* calculate the SRP-6 multiplier
*
* params:
* params (obj) group parameters, with .N, .g, .hash
*
* returns: bignum
*/
function getk(params) {
var k_buf = crypto
.createHash(params.hash)
.update(padToN(params.N, params))
.update(padToN(params.g, params))
.digest();
return(new BigInteger(k_buf.toString('hex'), 16));
};
/*
* Generate a random key
*
* params:
* bytes (int) length of key (default=32)
* callback (func) function to call with err,key
*
* returns: nothing, but runs callback with a Buffer
*/
function genKey(bytes, callback) {
// bytes is optional
if (arguments.length < 2) {
callback = bytes;
bytes = 32;
}
if (typeof callback !== 'function') {
throw("Callback required");
}
crypto.randomBytes(bytes, function(err, buf) {
if (err) return callback (err);
return callback(null, buf);
});
};
/*
* The server key exchange message also contains the server's public
* value (B). The server calculates this value as B = k*v + g^b % N,
* where b is a random number that SHOULD be at least 256 bits in length
* and k = H(N | PAD(g)).
*
* Note: as the tests imply, the entire expression is mod N.
*
* params:
* params (obj) group parameters, with .N, .g, .hash
* v (bignum) verifier (stored)
* b (bignum) server secret exponent
*
* returns: B (buffer) the server public message
*/
function getB(params, k, v, b) {
assertIsBigInteger(v);
assertIsBigInteger(k);
assertIsBigInteger(b);
var N = params.N;
//* var r = k.mul(v).add(params.g.powm(b, N)).mod(N);
var r = k.multiply(v).add(params.g.modPow(b, N)).mod(N);
return padToN(r, params);
};
/*
* The client key exchange message carries the client's public value
* (A). The client calculates this value as A = g^a % N, where a is a
* random number that SHOULD be at least 256 bits in length.
*
* Note: for this implementation, we take that to mean 256/8 bytes.
*
* params:
* params (obj) group parameters, with .N, .g, .hash
* a (bignum) client secret exponent
*
* returns A (bignum) the client public message
*/
function getA(params, a_num) {
assertIsBigInteger(a_num);
//! if (Math.ceil(a_num.bitLength() / 8) < 256/8) {
if (Math.ceil(a_num.toString(16).length / 2) < 32) {
console.warn("getA: client key length", a_num.bitLength(), "is less than the recommended 256 bits");
}
//* return padToN(params.g.powm(a_num, params.N), params);
return padToN(params.g.modPow(a_num, params.N), params);
};
/*
* getu() hashes the two public messages together, to obtain a scrambling
* parameter "u" which cannot be predicted by either party ahead of time.
* This makes it safe to use the message ordering defined in the SRP-6a
* paper, in which the server reveals their "B" value before the client
* commits to their "A" value.
*
* params:
* params (obj) group parameters, with .N, .g, .hash
* A (Buffer) client ephemeral public key
* B (Buffer) server ephemeral public key
*
* returns: u (bignum) shared scrambling parameter
*/
function getu(params, A, B) {
assertIsNBuffer(A, params, "A");
assertIsNBuffer(B, params, "B");
var u_buf = crypto.createHash(params.hash)
.update(A).update(B)
.digest();
//! return bignum.fromBuffer(u_buf);
return(new BigInteger(u_buf.toString('hex'), 16));
};
/*
* The TLS premaster secret as calculated by the client
*
* params:
* params (obj) group parameters, with .N, .g, .hash
* salt (buffer) salt (read from server)
* I (buffer) user identity (read from user)
* P (buffer) user password (read from user)
* a (bignum) ephemeral private key (generated for session)
* B (bignum) server ephemeral public key (read from server)
*
* returns: buffer
*/
function client_getS(params, k_num, x_num, a_num, B_num, u_num) {
assertIsBigInteger(k_num);
assertIsBigInteger(x_num);
assertIsBigInteger(a_num);
assertIsBigInteger(B_num);
assertIsBigInteger(u_num);
var g = params.g;
var N = params.N;
// if (zero.greater(B_num) || N.lesser(B_num))
if((zero.compareTo(B_num) > 0) && (N.compareTo(B_num) < 0))
throw new Error("invalid server-supplied 'B', must be 1..N-1");
//* var S_num = B_num.sub(k_num.mul(g.powm(x_num, N))).powm(a_num.add(u_num.mul(x_num)), N).mod(N);
var S_num = B_num.subtract(k_num.multiply(g.modPow(x_num, N))).modPow(a_num.add(u_num.multiply(x_num)), N).mod(N);
return padToN(S_num, params);
};
/*
* The TLS premastersecret as calculated by the server
*
* params:
* params (obj) group parameters, with .N, .g, .hash
* v (bignum) verifier (stored on server)
* A (bignum) ephemeral client public key (read from client)
* b (bignum) server ephemeral private key (generated for session)
*
* returns: bignum
*/
function server_getS(params, v_num, A_num, b_num, u_num) {
assertIsBigInteger(v_num);
assertIsBigInteger(A_num);
assertIsBigInteger(b_num);
assertIsBigInteger(u_num);
var N = params.N;
//! if (zero.greater(A_num) || N.lesser(A_num))
if((zero.compareTo(A_num) > 0) && (N.compareTo(A_num) < 0))
throw new Error("invalid client-supplied 'A', must be 1..N-1");
//* var S_num = A_num.mul(v_num.powm(u_num, N)).powm(b_num, N).mod(N);
var S_num = A_num.multiply(v_num.modPow(u_num, N)).modPow(b_num, N).mod(N);
return padToN(S_num, params);
};
/*
* Compute the shared session key K from S
*
* params:
* params (obj) group parameters, with .N, .g, .hash
* S (buffer) Session key
*
* returns: buffer
*/
function getK(params, S_buf) {
assertIsNBuffer(S_buf, params, "S");
return crypto.createHash(params.hash)
.update(S_buf)
.digest();
};
function getM1(params, A_buf, B_buf, S_buf) {
assertIsNBuffer(A_buf, params, "A");
assertIsNBuffer(B_buf, params, "B");
assertIsNBuffer(S_buf, params, "S");
return crypto.createHash(params.hash)
.update(A_buf).update(B_buf).update(S_buf)
.digest();
}
function getM2(params, A_buf, M_buf, K_buf) {
assertIsNBuffer(A_buf, params, "A");
assertIsBuffer(M_buf, "M");
assertIsBuffer(K_buf, "K");
return crypto.createHash(params.hash)
.update(A_buf).update(M_buf).update(K_buf)
.digest();
}
function equal(buf1, buf2) {
// constant-time comparison. A drop in the ocean compared to our
// non-constant-time modexp operations, but still good practice.
var mismatch = buf1.length - buf2.length;
if (mismatch) {
return false;
}
for (var i = 0; i < buf1.length; i++) {
mismatch |= buf1[i] ^ buf2[i];
}
return mismatch === 0;
}
function Client(params, salt_buf, identity_buf, password_buf, secret1_buf) {
if (!(this instanceof Client)) {
return new Client(params, salt_buf, identity_buf, password_buf, secret1_buf);
}
assertIsBuffer(salt_buf, "salt (salt)");
assertIsBuffer(identity_buf, "identity (I)");
assertIsBuffer(password_buf, "password (P)");
assertIsBuffer(secret1_buf, "secret1");
this._private = { params: params,
k_num: getk(params),
x_num: getx(params, salt_buf, identity_buf, password_buf),
//! a_num: bignum.fromBuffer(secret1_buf) };
a_num: new BigInteger(secret1_buf.toString('hex'), 16) };
this._private.A_buf = getA(params, this._private.a_num);
}
Client.prototype = {
computeA: function computeA() {
return this._private.A_buf;
},
setB: function setB(B_buf) {
var p = this._private;
//! var B_num = bignum.fromBuffer(B_buf);
var B_num = new BigInteger(B_buf.toString('hex'), 16);
var u_num = getu(p.params, p.A_buf, B_buf);
// console.log(util.inspect(p));
var S_buf_x = client_getS(p.params, p.k_num, p.x_num, p.a_num, B_num, u_num);
p.K_buf = getK(p.params, S_buf_x);
p.M1_buf = getM1(p.params, p.A_buf, B_buf, S_buf_x);
p.M2_buf = getM2(p.params, p.A_buf, p.M1_buf, p.K_buf);
p.u_num = u_num; // only for tests
p.S_buf = S_buf_x; // only for tests
},
computeM1: function computeM1() {
if (this._private.M1_buf === undefined)
throw new Error("incomplete protocol");
return this._private.M1_buf;
},
checkM2: function checkM2(serverM2_buf) {
if (!equal(this._private.M2_buf, serverM2_buf))
throw new Error("server is not authentic");
},
computeK: function computeK() {
if (this._private.K_buf === undefined)
throw new Error("incomplete protocol");
return this._private.K_buf;
}
};
function Server(params, verifier_buf, secret2_buf) {
if (!(this instanceof Server)) {
return new Server(params, verifier_buf, secret2_buf);
}
assertIsBuffer(verifier_buf, "verifier");
assertIsBuffer(secret2_buf, "secret2");
this._private = { params: params,
k_num: getk(params),
//! b_num: bignum.fromBuffer(secret2_buf),
//! v_num: bignum.fromBuffer(verifier_buf) };
b_num: new BigInteger(secret2_buf.toString('hex'), 16),
v_num: new BigInteger(verifier_buf.toString('hex'), 16) };
this._private.B_buf = getB(params, this._private.k_num,
this._private.v_num, this._private.b_num);
}
Server.prototype = {
computeB: function computeB() {
return this._private.B_buf;
},
setA: function setA(A_buf) {
var p = this._private;
//! var A_num = bignum.fromBuffer(A_buf);
var A_num = new BigInteger(A_buf.toString('hex'), 16);
var u_num = getu(p.params, A_buf, p.B_buf);
var S_buf = server_getS(p.params, p.v_num, A_num, p.b_num, u_num);
p.K_buf = getK(p.params, S_buf);
p.M1_buf = getM1(p.params, A_buf, p.B_buf, S_buf);
p.M2_buf = getM2(p.params, A_buf, p.M1_buf, p.K_buf);
p.u_num = u_num; // only for tests
p.S_buf = S_buf; // only for tests
},
checkM1: function checkM1(clientM1_buf) {
if (this._private.M1_buf === undefined)
throw new Error("incomplete protocol");
if (!equal(this._private.M1_buf, clientM1_buf))
throw new Error("client did not use the same password");
return this._private.M2_buf;
},
computeK: function computeK() {
if (this._private.K_buf === undefined)
throw new Error("incomplete protocol");
return this._private.K_buf;
}
};
module.exports = {
params: require('./params'),
genKey: genKey,
computeVerifier: computeVerifier,
Client: Client,
Server: Server
};

22
package.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "fast-srp",
"description": "Secure Remote Password (SRP)",
"version": "0.9.0",
"main": "index.js",
"scripts": {
"test": "vows test/test_*.js --spec"
},
"repository": {
"type": "git",
"url": "https://github.com/zarmack/fast-srp"
},
"author": "Zarmack Tanen",
"licence": "MIT",
"readmeFilename": "README.md",
"dependencies": {
"lodash": "^3.10.1"
},
"devDependencies": {
"vows": "0.7.0"
}
}

313
test/test_picl_vectors.js Normal file
View File

@ -0,0 +1,313 @@
"use strict";
var vows = require('vows');
var assert = require('assert');
var BigInteger = require('../lib/jsbn');
var srp = require('../lib/srp');
var util = require('util');
/*
* Vectors from https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol
*
* Verify that we are inter-compatible with the SRP implementation used by
* Mozilla's Identity-Attached Services, aka PiCl (Profile in the Cloud).
*
* Note that P is the HKDF-stretched key, computed elsewhere.
*/
function join(s) {
return s.split(/\s/).join('');
}
function h(s) {
return new Buffer(join(s), 'hex');
}
var params = srp.params['2048'];
/* inputs_1/expected_1 are the main PiCl test vectors. They were mechanically
* generated to force certain derived values (stretched-password "P", v, A,
* B, and S) to begin with a 0x00 byte (to exercise padding bugs).
*/
var inputs_1 = {
I: new Buffer('andré@example.org', 'utf8'),
P: h('00f9b71800ab5337 d51177d8fbc682a3 653fa6dae5b87628 eeec43a18af59a9d'),
salt: h('00f1000000000000000000000000000000000000000000000000000000000179'),
// a and b are usually random. For testing, we force them to specific values.
a: h(' 00f2000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 000000000000d3d7'
),
b: h(' 00f3000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 000000000000000f'
)
};
var expected_1 = {
// 'k' encodes the group (N and g), used in SRP-6a
k: h('05b9e8ef059c6b32 ea59fc1d322d37f0 4aa30bae5aa9003b 8321e21ddb04e300'),
// 'x' is derived from the salt and password
// 'v' is the SRP verifier
x: h('b5200337cc3f3f92 6cdddae0b2d31029 c069936a844aff58 779a545be89d0abe'),
v: h(' 00173ffa0263e63c cfd6791b8ee2a40f 048ec94cd95aa8a3 125726f9805e0c82'
+'83c658dc0b607fbb 25db68e68e93f265 8483049c68af7e82 14c49fde2712a775'
+'b63e545160d64b00 189a86708c69657d a7a1678eda0cd79f 86b8560ebdb1ffc2'
+'21db360eab901d64 3a75bf1205070a57 91230ae56466b8c3 c1eb656e19b794f1'
+'ea0d2a077b3a7553 50208ea0118fec8c 4b2ec344a05c66ae 1449b32609ca7189'
+'451c259d65bd15b3 4d8729afdb5faff8 af1f3437bbdc0c3d 0b069a8ab2a959c9'
+'0c5a43d42082c774 90f3afcc10ef5648 625c0605cdaace6c 6fdc9e9a7e6635d6'
+'19f50af773452247 0502cab26a52a198 f5b00a2798589165 07b0b4e9ef9524d6'),
// 'B' is the server's public message
B: h(' 0022ce5a7b9d8127 7172caa20b0f1efb 4643b3becc535664 73959b07b790d3c3'
+'f08650d5531c19ad 30ebb67bdb481d1d 9cf61bf272f84398 48fdda58a4e6abc5'
+'abb2ac496da5098d 5cbf90e29b4b110e 4e2c033c70af7392 5fa37457ee13ea3e'
+'8fde4ab516dff1c2 ae8e57a6b264fb9d b637eeeae9b5e43d faba9b329d3b8770'
+'ce89888709e02627 0e474eef822436e6 397562f284778673 a1a7bc12b6883d1c'
+'21fbc27ffb3dbeb8 5efda279a69a1941 4969113f10451603 065f0a0126666456'
+'51dde44a52f4d8de 113e2131321df1bf 4369d2585364f9e5 36c39a4dce33221b'
+'e57d50ddccb4384e 3612bbfd03a268a3 6e4f7e01de651401 e108cc247db50392'),
// 'A' is the client's public message
A: h(' 007da76cb7e77af5 ab61f334dbd5a958 513afcdf0f47ab99 271fc5f7860fe213'
+'2e5802ca79d2e5c0 64bb80a38ee08771 c98a937696698d87 8d78571568c98a1c'
+'40cc6e7cb101988a 2f9ba3d65679027d 4d9068cb8aad6ebf f0101bab6d52b5fd'
+'fa81d2ed48bba119 d4ecdb7f3f478bd2 36d5749f2275e948 4f2d0a9259d05e49'
+'d78a23dd26c60bfb a04fd346e5146469 a8c3f010a627be81 c58ded1caaef2363'
+'635a45f97ca0d895 cc92ace1d09a99d6 beb6b0dc0829535c 857a419e834db128'
+'64cd6ee8a843563b 0240520ff0195735 cd9d316842d5d3f8 ef7209a0bb4b54ad'
+'7374d73e79be2c39 75632de562c59647 0bb27bad79c3e2fc ddf194e1666cb9fc'),
// 'u' combines the two public messages
u: h('b284aa1064e87751 50da6b5e2147b47c a7df505bed94a6f4 bb2ad873332ad732'),
// 'S' is the shared secret
S: h(' 0092aaf0f527906a a5e8601f5d707907 a03137e1b601e04b 5a1deb02a981f4be'
+'037b39829a27dba5 0f1b27545ff2e287 29c2b79dcbdd32c9 d6b20d340affab91'
+'a626a8075806c26f e39df91d0ad979f9 b2ee8aad1bc783e7 097407b63bfe58d9'
+'118b9b0b2a7c5c4c debaf8e9a460f4bf 6247b0da34b760a5 9fac891757ddedca'
+'f08eed823b090586 c63009b2d740cc9f 5397be89a2c32cdc fe6d6251ce11e44e'
+'6ecbdd9b6d93f30e 90896d2527564c7e b9ff70aa91acc0ba c1740a11cd184ffb'
+'989554ab58117c21 96b353d70c356160 100ef5f4c28d19f6 e59ea2508e8e8aac'
+'6001497c27f362ed bafb25e0f045bfdf 9fb02db9c908f103 40a639fe84c31b27'),
// 'K' is the shared derived key
K: h('e68fd0112bfa31dc ffc8e9c96a1cbadb 4c3145978ff35c73 e5bf8d30bbc7499a'),
// 'M1' is the client's proof that it knows the shared key
M1: h('27949ec1e0f16256 33436865edb037e2 3eb6bf5cb91873f2 a2729373c2039008')
};
/* inputs_2/expected_2 have leading 0x00 bytes in 'x' and 'u' */
var inputs_2 = {
I: inputs_1.I, P: inputs_1.P,
salt: h('00f1000000000000000000000000000000000000000000000000000000000021'),
a: h(' 00f2000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 000000000000000d'
),
b: h(' 00f3000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000001'
)
};
var expected_2 = {
k: expected_1.k,
x: h('009b2740fb49284d 69cab7c916d449ee d7dcabf41332b8b8 d6928f529bd1a94e'),
v: h(' 1cd8b856685672ee 7a5895d897121234 6c17c3472f2696e4 8cdeec5533c06693'
+'179bc24802b762bc c1e1f8fc8abe607a f2f44aac9172e7dd 0c0110e45cf3b700'
+'f8db153b67fb0e76 3c6710b8c1c26baf c3b67a50652ee0d7 c6045a5c4b51ff33'
+'d0135065dca5d6bb 7e150e07414bd572 a954471059c1b466 d0530b0a80bd2d0c'
+'f1bedf5abfc05c3c f2736ac40b083dcf 62271e834042ecb0 d4882ddd35403c1e'
+'d24bc4ffe274c5f6 be50ec9b85aa0cfa 26d97e086ec45e06 3c29174d3dbe5490'
+'1d2a557b7eb46b18 9e17cc721fc098a0 baee2f364a2b409d 49d9372a9625db11'
+'acfd74ba7f41285f 9c1916d3caaf5238 852694bbde2a13f7 8fcc92d16658dd04'),
// 'B' is the server's public message
B: h(' 485d56912c60d9c1 7af15494d4d50006 45eefa2d41f6bcb5 785e08efad0833a1'
+'3cb43ee3869e78d4 c2006f42b9741782 a85c90a110cc9a74 4fc2a361d5535966'
+'2dc5fa4a8d0c7c0e 63e0cf32a28af655 863dd5d66f550557 eacd3e3e64d90f9f'
+'0d757403c9bbfb08 fcc9a35e1cb421d7 3bb93fa72d5b54ed bfa219d3867255ba'
+'f96223eef038f085 722b2d14457a5a13 1857a56e66d3011b b5aa7504c4b9a346'
+'8d0ebdd817d20105 be06ba261ea16740 723faa097f27ddc2 efe34cf8fe59451a'
+'5bb3987d7161085f b8fc28d5cc28c466 6a3ca486ad0ca83d 1984248ac838574e'
+'348fb9745ffd1163 f53b5566768a8971 237065d8f6e786be e15107125fb10df1'),
// 'A' is the client's public message
A: h(' a4b17836b1e7d6f1 5b9901f644bcdf5e 119e7a861c6ee88d 006d8420a5066f22'
+'d9bf5ccf3d380437 0d29d778ec40afcf c88de7bf22ec03fc 6ab12e0dd95d15e3'
+'a6249c94393435b0 0d23b1b0439dabed cce1726b2b3cdea2 647c8790d604d87d'
+'2ac890cfceec0dbe 434f09a9bc11d984 a1e1990f69956ae0 db6068992ad1715f'
+'b4381516da83637a 73de4211908c8f2f f8b3a09e8535acf3 c2b8de4e9a632f89'
+'9bfa08cee543b4ea 50d0aca0b3e4fbfa e49ffa2a1ab89a42 8bea928868828501'
+'2e8af13fcdd444ad da9ad1d0ab4c2069 91919e5391bd2b1a ab8c2d006baceaf8'
+'cdcb555a6b16e844 5b03e09776eba841 7576dac458afbbd5 2902dfb0282bed79'),
// 'u' combines the two public messages
u: h('000e4039be3989ad 088dc17d8ade899a 6409e7e57b3e8518 cee1cbc77e1de243'),
// 'S' is the shared secret
S: h(' 5c7f591d134d19f9 fcedc2b4e3eecd3d 5deadfe7dd42bd59 b1c960516c65ab61'
+'d007f8134e0a7ca3 0dd409128ef2c780 6784afd95985c8f3 c2d42cd73d26d315'
+'541645d28aefabc9 980c9a6e5714b178 aa69e5321828ca00 f3d10d742776cfe4'
+'4b7f5f5c0247addc 0ab0640b49b540ff 9bccea8702e1f996 49448680c00fb484'
+'51919224d44236ba 1b1e5cf62a5946bd 637f189ff7b8eba9 7b719f18ad9251f0'
+'a81c157604065388 d7bf4abbf774bfb2 d7b95ed8359b0d70 6ff5df0223992c81'
+'4aac506e1bace002 d134ed5e41d74f93 a8f410dfe7dc5954 f70b6bafcd0ddfde'
+'e75f0058f718ec14 f9bbeb29ff966e00 ddfdd2d38a1c7a68 ac455a57b972d528'),
// 'K' is the shared derived key
K: h('b637ede0b7a31c46 b2567e855eb8a7f7 a994937deee76479 62afbe35d6929709'),
// 'M1' is the client's proof that it knows the shared key
M1: h('67c83797eb1a3987 e2d48d287e3bd772 d25db2b3cd86ea22 c8cf3ae932a1e45b')
};
/* inputs_3/expected_3 have leading 0x00 bytes in 'x' and 'K' */
var inputs_3 = {
I: inputs_2.I, P: inputs_2.P,
salt: h('00f1000000000000000000000000000000000000000000000000000000000021'),
a: h(' 00f2000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 00000000000001a0'
),
b: inputs_2.b
};
var expected_3 = {
k: expected_2.k,
x: expected_2.x,
v: expected_2.v,
B: expected_2.B,
A: h(' 87b6da9e4162843b 4d5ee60c403ae3e1 e9fdab64883f13ab 4a44b0718a9ea1b6'
+'1ad17c675e0f0395 b37d58a046a2d5ab 1fb665a9777abe80 8077ccf6fd8ec583'
+'854eab98deb257d9 10e5bf5cafed4955 2a5cd9927c0979f7 5a21654644000173'
+'aef6f2244296439c 10b3c61a03e7146e f6c9c9564b1d2bf5 1ece84d115965f9c'
+'c82006bdb7a124da 3304bcc24c8f3724 522b748fb19a0cb6 b60e355acbf649b5'
+'40b4972e24077c29 32004a3ad9e59464 2e90a3bfc8de7085 f4a4efc195bd06c9'
+'6c7011f3c979eaab 469f06465a5b7239 afaee535aedc5bd2 1a220546e0e6b70b'
+'5b6f54db3fea46d5 7ebc7fe46156d793 c59e6290d3cf9bc2 4316528da34f4640'),
u: h('865d0efca6cf17d6 f489e129231f1a48 b20c83ec6581d11f 3a2fa48ea93cd305'),
S: h(' 0ae26456e1a0dec1 ce162fb2e5bc7300 3c285e17c0b44f03 7ebbc57f8020ceae'
+'5d10a9e6e44eab2a 6915b582ab5f6e7d 16002ce05e524015 e9bc7c56d5131da4'
+'d4c4d7c3debaffcd b60e58468bd2c0da 5de95855480190a3 5258c79032001882'
+'3d836ca91848c5b6 3ca4265c3329eb44 161af9ce64cf4468 ef0eb88a788a0d07'
+'52a69821278c94ae 7193161b5c638b55 bf732e2a5996ccc5 16335f9f3d00dfa9'
+'8ac1b1e4971c5417 d34eba1e2a90ed60 a07d1d8be5b9d773 d8f2cb03bfb75994'
+'249f7734081aa42d 58dd54f8f725b245 175cf7d102e1086c eba4cfe7e49a2d27'
+'ffd6aef7549d402f bfcea78b4f3398ac 9ab1ee199f70acb6 4d2a17e159ff500d'),
K: h('00217598a4008956 4b17196bd43422d6 03a0a88a545b61b3 98c42c9cbcc1d1b3'),
M1: h('96d815ecece1dff4 254cd77517b37b97 65e741c1a57169ab af538e867444ec7f')
};
/* inputs_4/expected_4 have leading 0x00 bytes in 'x' and 'M1' */
var inputs_4 = {
I: inputs_2.I, P: inputs_2.P,
salt: h('00f1000000000000000000000000000000000000000000000000000000000021'),
a: h(' 00f2000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000000'
+'0000000000000000 0000000000000000 0000000000000000 0000000000000190'
),
b: inputs_2.b
};
var expected_4 = {
k: expected_2.k,
x: expected_2.x,
v: expected_2.v,
B: expected_2.B,
A: h(' 4aee66beefb92d12 c8e341814809afcd 9ce083c11abcda70 0c03d5379c429cb9'
+'acbde6bb42a628f3 7a2536c864c40f74 f48a9d9356029a8b fe0e10cb9cf5a8a4'
+'2e591841f426d281 edf7c9b04112d8ef bf73f9768a4faace ddd351d3e9380bf1'
+'dcd0590c7ab50a95 bd23e9617e303bea 6f8fbe8a657b6417 4b60cdf5c059ba67'
+'1b6735324ae0c30a e7f3e361de8f273c af7b2513fa048ed1 0106c66ce460c5cc'
+'78544c790f5ffcce 378b79d5f02ec361 3a457b03fa0cc39c 80d6fdd645e24f65'
+'c690f9478d5b331d c00eef68670edbf3 629fd1a6c85267d2 cbb90f1670e7ba09'
+'cf2b5a9b00be8e11 f33e47a1c1f04eca f35bccb61af1116e 4d0f9d475017bad2'),
u: h('d0913eb75b61e15a 87756ffa04d4f967 e492bd0b330a2b11 fe8976aada2bb1ee'),
S: h(' 7ba3ce4a3d236b95 3c2d0fee42195c85 081664a44f55b82d a3abf66ac68bdbd7'
+'ad82d5ad95090782 5241fb706de8fc58 0a29e4579fbbedf3 0bec0138b3f76e06'
+'f9c86b16ad673890 3003ce8c86cb14ea 552db904a20970a9 7d9258a768087d30'
+'47a6e77520d32968 de3f64e94cd8c463 92c13e194194745c 8e53a9bb15a79473'
+'2a645068970fcdd9 a7c98b4aec19773a 5196802c2e932e71 d3a4a340e6f4fe16'
+'9e7ccc687f7246fe 20edeaf88d1125da c812751317f7213c d84f9efe2313d701'
+'d4a9bf0242bfe703 26fc19b68c90e83b 59b5cc21886ab602 f8bfa16fb50c3147'
+'9aad5e31698abf67 863b7ca6b6ac25a7 09a24d8f94c80bbf 691e38c81beb3c72'),
K: h('bd2a167a93b8496e 68c7e24b37956924 672eb8249d25c281 13984912d5cf27a6'),
M1: h('00cef66a047d506c bf941c236218e583 5343534ae08cf0cd 0fb7980bed242e05')
};
function hexequal(a, b, msg) {
assert.equal(a.length, b.length, msg);
assert.equal(a.toString('hex'), b.toString('hex'), msg);
}
function numequal(a, b, msg) {
assert(a.compareTo(b) == 0, msg);
}
function checkVectors(params, inputs, expected) {
hexequal(inputs.I, new Buffer('616e6472c3a9406578616d706c652e6f7267', "hex"), "I");
hexequal(srp.computeVerifier(params, inputs.salt, inputs.I, inputs.P), expected.v, "v");
var client = new srp.Client(params, inputs.salt, inputs.I, inputs.P, inputs.a);
var server = new srp.Server(params, expected.v, inputs.b);
//! numequal(client._private.k_num, bignum.fromBuffer(expected.k), "k");
//! numequal(client._private.x_num, bignum.fromBuffer(expected.x), "x");
// console.log("k_num = " + client._private.k_num.toString(16) + ", expected k = " + bigInt(expected.k.toString('hex'), 16).toString(16));
numequal(client._private.k_num, new BigInteger(expected.k.toString('hex'), 16), "k");
numequal(client._private.x_num, new BigInteger(expected.x.toString('hex'), 16), "x");
hexequal(client.computeA(), expected.A);
hexequal(server.computeB(), expected.B);
assert.throws(function() {client.computeM1();}, /incomplete protocol/);
assert.throws(function() {client.computeK();}, /incomplete protocol/);
assert.throws(function() {server.checkM1(expected.M1);}, /incomplete protocol/);
assert.throws(function() {server.computeK();}, /incomplete protocol/);
client.setB(expected.B);
numequal(client._private.u_num, new BigInteger(expected.u.toString('hex'), 16));
hexequal(client._private.S_buf, expected.S);
hexequal(client.computeM1(), expected.M1);
hexequal(client.computeK(), expected.K);
server.setA(expected.A);
numequal(server._private.u_num, new BigInteger(expected.u.toString('hex'), 16));
hexequal(server._private.S_buf, expected.S);
assert.throws(function() {server.checkM1(Buffer("notM1"));},
/client did not use the same password/);
server.checkM1(expected.M1); // happy, not throwy
hexequal(server.computeK(), expected.K);
}
vows.describe('picl vectors')
.addBatch({
'vectors 1': function() { checkVectors(params, inputs_1, expected_1); },
'vectors 2': function() { checkVectors(params, inputs_2, expected_2); },
'vectors 3': function() { checkVectors(params, inputs_3, expected_3); },
'vectors 4': function() { checkVectors(params, inputs_4, expected_4); }
})
.export(module);

98
test/test_rfc_5054.js Normal file
View File

@ -0,0 +1,98 @@
const vows = require('vows'),
assert = require('assert'),
srp = require('../lib/srp'),
params = srp.params['1024'];
/*
* http://tools.ietf.org/html/rfc5054#appendix-B
*/
const I = new Buffer("alice"),
P = new Buffer("password123"),
s = Buffer('beb25379d1a8581eb5a727673a2441ee', 'hex'),
k_expected = '7556aa045aef2cdd07abaf0f665c3e818913186f',
x_expected = '94b7555aabe9127cc58ccf4993db6cf84d16c124',
v_expected = ('7e273de8 696ffc4f 4e337d05 b4b375be b0dde156 9e8fa00a 9886d812'
+'9bada1f1 822223ca 1a605b53 0e379ba4 729fdc59 f105b478 7e5186f5'
+'c671085a 1447b52a 48cf1970 b4fb6f84 00bbf4ce bfbb1681 52e08ab5'
+'ea53d15c 1aff87b2 b9da6e04 e058ad51 cc72bfc9 033b564e 26480d78'
+'e955a5e2 9e7ab245 db2be315 e2099afb').split(/\s/).join(''),
a = Buffer('60975527035cf2ad1989806f0407210bc81edc04e2762a56afd529ddda2d4393', 'hex'),
b = Buffer('e487cb59d31ac550471e81f00f6928e01dda08e974a004f49e61f5d105284d20', 'hex'),
A_expected = ('61d5e490 f6f1b795 47b0704c 436f523d d0e560f0 c64115bb 72557ec4'
+'4352e890 3211c046 92272d8b 2d1a5358 a2cf1b6e 0bfcf99f 921530ec'
+'8e393561 79eae45e 42ba92ae aced8251 71e1e8b9 af6d9c03 e1327f44'
+'be087ef0 6530e69f 66615261 eef54073 ca11cf58 58f0edfd fe15efea'
+'b349ef5d 76988a36 72fac47b 0769447b').split(/\s/).join(''),
B_expected = ('bd0c6151 2c692c0c b6d041fa 01bb152d 4916a1e7 7af46ae1 05393011'
+'baf38964 dc46a067 0dd125b9 5a981652 236f99d9 b681cbf8 7837ec99'
+'6c6da044 53728610 d0c6ddb5 8b318885 d7d82c7f 8deb75ce 7bd4fbaa'
+'37089e6f 9c6059f3 88838e7a 00030b33 1eb76840 910440b1 b27aaeae'
+'eb4012b7 d7665238 a8e3fb00 4b117b58').split(/\s/).join(''),
u_expected = 'ce38b9593487da98554ed47d70a7ae5f462ef019',
S_expected = ('b0dc82ba bcf30674 ae450c02 87745e79 90a3381f 63b387aa f271a10d'
+'233861e3 59b48220 f7c4693c 9ae12b0a 6f67809f 0876e2d0 13800d6c'
+'41bb59b6 d5979b5c 00a172b4 a2a5903a 0bdcaf8a 709585eb 2afafa8f'
+'3499b200 210dcc1f 10eb3394 3cd67fc8 8a2f39a4 be5bec4e c0a3212d'
+'c346d7e4 74b29ede 8a469ffe ca686e5a').split(/\s/).join('');
function asHex(num) {
//! return num.toBuffer().toString('hex');
return num.toString(16);
}
vows.describe('RFC 5054')
.addBatch({
"Test vectors": {
topic: function() {
return srp.computeVerifier(params, s, I, P);
},
"x": function() {
var client = new srp.Client(params, s, I, P, a);
assert.equal(asHex(client._private.x_num), x_expected);
},
"V": function(v) {
assert.equal(v.toString('hex'), v_expected);
},
"k": function() {
var client = new srp.Client(params, s, I, P, a);
assert.equal(asHex(client._private.k_num), k_expected);
},
"A": function() {
var client = new srp.Client(params, s, I, P, a);
assert.equal(client.computeA().toString('hex'), A_expected);
},
"B": function(v) {
var server = new srp.Server(params, v, b);
assert.equal(server.computeB().toString('hex'), B_expected);
},
"u": function() {
var client = new srp.Client(params, s, I, P, a);
client.setB(Buffer(B_expected, 'hex'));
assert.equal(asHex(client._private.u_num), u_expected);
},
"S client": function() {
var client = new srp.Client(params, s, I, P, a);
client.setB(Buffer(B_expected, 'hex'));
assert.equal(client._private.S_buf.toString('hex'), S_expected);
},
"S server": function(v) {
var server = new srp.Server(params, v, b);
server.setA(Buffer(A_expected, 'hex'));
assert.equal(server._private.S_buf.toString('hex'), S_expected);
}
}
})
.export(module);

194
test/test_srp.js Normal file
View File

@ -0,0 +1,194 @@
const vows = require('vows'),
assert = require('assert'),
srp = require('../lib/srp'),
params = srp.params[4096],
salt = new Buffer("salty"),
identity = new Buffer("alice"),
password = new Buffer("password123");
assert(params, "missing parameters");
var client, server;
var a, A;
var b, B;
var verifier;
var S_client, S_server;
vows.describe("srp.js")
.addBatch({
"create Verifier": function() {
verifier = srp.computeVerifier(params, salt, identity, password);
assert.equal(verifier.toString("hex"), "f0e47f50f5dead8db8d93a279e3b62d6ff50854b31fbd3474a886bef916261717e84dd4fb8b4d27feaa5146db7b1cbbc274fdf96a132b5029c2cd72527427a9b9809d5a4d018252928b4fc343bc17ce63c1859d5806f5466014fc361002d8890aeb4d6316ff37331fc2761be0144c91cdd8e00ed0138c0ce51534d1b9a9ba629d7be34d2742dd4097daabc9ecb7aaad89e53c342b038f1d2adae1f2410b7884a3e9a124c357e421bccd4524467e1922660e0a4460c5f7c38c0877b65f6e32f28296282a93fc11bbabb7bb69bf1b3f9391991d8a86dd05e15000b7e38ba38a536bb0bf59c808ec25e791b8944719488b8087df8bfd7ff20822997a53f6c86f3d45d004476d6303301376bb25a9f94b552cce5ed40de5dd7da8027d754fa5f66738c7e3fc4ef3e20d625df62cbe6e7adfc21e47880d8a6ada37e60370fd4d8fc82672a90c29f2e72f35652649d68348de6f36d0e435c8bd42dd00155d35d501becc0661b43e04cdb2da84ce92b8bf49935d73d75efcbd1176d7bbccc3cc4d4b5fefcc02d478614ee1681d2ff3c711a61a7686eb852ae06fb8227be21fb8802719b1271ba1c02b13bbf0a2c2e459d9bedcc8d1269f6a785cb4563aa791b38fb038269f63f58f47e9051499549789269cc7b8ec7026fc34ba73289c4af829d5a532e723967ce9b6c023ef0fd0cfe37f51f10f19463b6534159a09ddd2f51f3b30033");
},
"create a and b": {
topic: function() {
var cb = this.callback;
srp.genKey(64, function(err, key) {
assert(err === null);
a = key;
srp.genKey(32, function(err, key) {
assert(err === null);
b = key;
cb();
});
});
},
"use a and b": function() {
client = new srp.Client(params, salt, identity, password, a);
// client produces A
A = client.computeA();
// create server
server = new srp.Server(params, verifier, b);
// server produces B
B = server.computeB();
// server accepts A
server.setA(A);
// client doesn't produce M1 too early
assert.throws(function(){client.computeM1();}, /incomplete protocol/);
// client accepts B
client.setB(B);
// client produces M1 now
client.computeM1();
// server likes client's M1
var serverM2 = server.checkM1(client.computeM1());
// client and server agree on K
var client_K = client.computeK();
var server_K = server.computeK();
assert.equal(client_K.toString("hex"), server_K.toString("hex"));
// server is authentic
assert.doesNotThrow(function(){client.checkM2(serverM2);}, "M2 didn't check");
},
"constructor doesn't require 'new'": function() {
client = srp.Client(params, salt, identity, password, a);
// client produces A
A = client.computeA();
// create server
server = srp.Server(params, verifier, b);
// server produces B
B = server.computeB();
// server accepts A
server.setA(A);
// client doesn't produce M1 too early
assert.throws(function(){client.computeM1();}, /incomplete protocol/);
// client accepts B
client.setB(B);
// client produces M1 now
client.computeM1();
// server likes client's M1
serverM2 = server.checkM1(client.computeM1());
// client and server agree on K
var client_K = client.computeK();
var server_K = server.computeK();
assert.equal(client_K.toString("hex"), server_K.toString("hex"));
// server is authentic
assert.doesNotThrow(function(){client.checkM2(serverM2);}, "M2 didn't check");
},
"server rejects wrong M1": function() {
var bad_client = new srp.Client(params, salt, identity, Buffer("bad"), a);
var server2 = new srp.Server(params, verifier, b);
bad_client.setB(server2.computeB());
assert.throws(function(){server.checkM1(bad_client.computeM1());},
/client did not use the same password/);
},
"server rejects bad A": function() {
// client's "A" must be 1..N-1 . Reject 0 and N and N+1. We should
// reject 2*N too, but our Buffer-length checks reject it before the
// number itself is examined.
var server2 = new srp.Server(params, verifier, b);
var Azero = new Buffer(params.N_length_bits/8);
Azero.fill(0);
//! var AN = params.N.toBuffer();
//! var AN1 = params.N.add(1).toBuffer();
var AN = new Buffer(params.N.toString(16), 16);
var AN1 = new Buffer(params.N.add(1).toString(16), 16);
assert.throws(function() {server2.setA(Azero);},
/invalid client-supplied 'A'/);
assert.throws(function() {server2.setA(AN);},
/invalid client-supplied 'A'/);
assert.throws(function() {server2.setA(AN1);},
/invalid client-supplied 'A'/);
},
"client rejects bad B": function() {
// server's "B" must be 1..N-1 . Reject 0 and N and N+1
var client2 = new srp.Client(params, salt, identity, password, a);
var Bzero = new Buffer(params.N_length_bits/8);
Bzero.fill(0, 0, params.N_length_bits/8);
//! var BN = params.N.toBuffer();
//! var BN1 = params.N.add(1).toBuffer();
var BN = new Buffer(params.N.toString(16), 16);
var BN1 = new Buffer(params.N.add(1).toString(16), 16);
assert.throws(function() {client2.setB(Bzero);},
/invalid server-supplied 'B'/);
assert.throws(function() {client2.setB(BN);},
/invalid server-supplied 'B'/);
assert.throws(function() {client2.setB(BN1);},
/invalid server-supplied 'B'/);
},
"client rejects bad M2": function() {
client = srp.Client(params, salt, identity, password, a);
// client produces A
A = client.computeA();
// create server
server = srp.Server(params, verifier, b);
// server produces B
B = server.computeB();
// server accepts A
server.setA(A);
// client accepts B
client.setB(B);
// client produces M1 now
client.computeM1();
// server likes client's M1
var serverM2 = server.checkM1(client.computeM1());
// we tamper with the server's M2
serverM2 = serverM2 + "a";
// client and server agree on K
var client_K = client.computeK();
var server_K = server.computeK();
assert.equal(client_K.toString("hex"), server_K.toString("hex"));
// server is NOT authentic
assert.throws(function(){client.checkM2(serverM2);}, "M2 didn't check");
},
}
})
.export(module);