diff --git a/browser/sha256.js b/browser/sha256.js index 34cb349..edd7932 100644 --- a/browser/sha256.js +++ b/browser/sha256.js @@ -19,6 +19,9 @@ function concat (buffers) { return combined.buffer } +/** + * @param {(string | SRPInteger)[]} args + */ module.exports = function sha256 (...args) { const buffer = concat(args.map((arg) => { if (arg instanceof SRPInteger) { diff --git a/client.js b/client.js index 80d8013..f3ba81a 100644 --- a/client.js +++ b/client.js @@ -4,19 +4,19 @@ const params = require('./lib/params') const SRPInteger = require('./lib/srp-integer') exports.generateSalt = function () { - // s User's salt + // s User's salt const s = SRPInteger.randomInteger(params.hashOutputBytes) return s.toHex() } exports.derivePrivateKey = function (salt, username, password) { - // H() One-way hash function + // H() One-way hash function const { H } = params - // s User's salt - // I Username - // p Cleartext Password + // s User's salt + // I Username + // p Cleartext Password const s = SRPInteger.fromHex(salt) const I = String(username) const p = String(password) @@ -28,11 +28,11 @@ exports.derivePrivateKey = function (salt, username, password) { } exports.deriveVerifier = function (privateKey) { - // N A large safe prime (N = 2q+1, where q is prime) - // g A generator modulo N + // N A large safe prime (N = 2q+1, where q is prime) + // g A generator modulo N const { N, g } = params - // x Private key (derived from p and s) + // x Private key (derived from p and s) const x = SRPInteger.fromHex(privateKey) // v = g^x (computes password verifier) @@ -42,8 +42,8 @@ exports.deriveVerifier = function (privateKey) { } exports.generateEphemeral = function () { - // N A large safe prime (N = 2q+1, where q is prime) - // g A generator modulo N + // N A large safe prime (N = 2q+1, where q is prime) + // g A generator modulo N const { N, g } = params // A = g^a (a = random number) @@ -57,17 +57,18 @@ exports.generateEphemeral = function () { } exports.deriveSession = function (clientSecretEphemeral, serverPublicEphemeral, salt, username, privateKey) { - // N A large safe prime (N = 2q+1, where q is prime) - // g A generator modulo N - // k Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6) - // H() One-way hash function - const { N, g, k, H } = params - - // a Secret ephemeral values - // B Public ephemeral values - // s User's salt - // I Username - // x Private key (derived from p and s) + // N A large safe prime (N = 2q+1, where q is prime) + // g A generator modulo N + // k Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6) + // H() One-way hash function + // PAD() Pad the number to have the same number of bytes as N + const { N, g, k, H, PAD } = params + + // a Secret ephemeral values + // B Public ephemeral values + // s User's salt + // I Username + // x Private key (derived from p and s) const a = SRPInteger.fromHex(clientSecretEphemeral) const B = SRPInteger.fromHex(serverPublicEphemeral) const s = SRPInteger.fromHex(salt) @@ -83,8 +84,8 @@ exports.deriveSession = function (clientSecretEphemeral, serverPublicEphemeral, throw new Error('The server sent an invalid public ephemeral') } - // u = H(A, B) - const u = H(A, B) + // u = H(PAD(A), PAD(B)) + const u = H(PAD(A), PAD(B)) // S = (B - kg^x) ^ (a + ux) const S = B.subtract(k.multiply(g.modPow(x, N))).modPow(a.add(u.multiply(x)), N) @@ -102,12 +103,12 @@ exports.deriveSession = function (clientSecretEphemeral, serverPublicEphemeral, } exports.verifySession = function (clientPublicEphemeral, clientSession, serverSessionProof) { - // H() One-way hash function + // H() One-way hash function const { H } = params - // A Public ephemeral values - // M Proof of K - // K Shared, strong session key + // A Public ephemeral values + // M Proof of K + // K Shared, strong session key const A = SRPInteger.fromHex(clientPublicEphemeral) const M = SRPInteger.fromHex(clientSession.proof) const K = SRPInteger.fromHex(clientSession.key) diff --git a/lib/params.js b/lib/params.js index f21b262..0b76633 100644 --- a/lib/params.js +++ b/lib/params.js @@ -18,16 +18,26 @@ const input = { `, generatorModulo: '02', hashFunction: 'sha256', - hashOutputBytes: (256 / 8) + hashOutputBytes: (256 / 8), + paddedLength: 512 } -// N A large safe prime (N = 2q+1, where q is prime) -// g A generator modulo N -// k Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6) -// H() One-way hash function +/** + * @param {SRPInteger} integer + */ +function pad (integer) { + return integer.pad(input.paddedLength) +} + +// N A large safe prime (N = 2q+1, where q is prime) +// g A generator modulo N +// k Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6) +// H() One-way hash function +// PAD() Pad the number to have the same number of bytes as N exports.N = SRPInteger.fromHex(input.largeSafePrime.replace(/\s+/g, '')) exports.g = SRPInteger.fromHex(input.generatorModulo.replace(/\s+/g, '')) -exports.k = sha256(exports.N, exports.g) +exports.k = sha256(exports.N, pad(exports.g)) exports.H = sha256 +exports.PAD = pad exports.hashOutputBytes = input.hashOutputBytes diff --git a/lib/sha256.js b/lib/sha256.js index 229e884..12ac521 100644 --- a/lib/sha256.js +++ b/lib/sha256.js @@ -4,6 +4,9 @@ const crypto = require('crypto') const SRPInteger = require('./srp-integer') +/** + * @param {(string | SRPInteger)[]} args + */ module.exports = function sha256 (...args) { const h = crypto.createHash('sha256') diff --git a/lib/srp-integer.js b/lib/srp-integer.js index 9cc67c9..93cc18c 100644 --- a/lib/srp-integer.js +++ b/lib/srp-integer.js @@ -41,6 +41,14 @@ class SRPInteger { return new SRPInteger(this[kBigInteger].xor(val[kBigInteger]), this[kHexLength]) } + pad (hexLength) { + if (hexLength < this[kHexLength]) { + throw new Error('Cannot pad to a shorter length') + } + + return new SRPInteger(this[kBigInteger], hexLength) + } + inspect () { const hex = this[kBigInteger].toString(16) diff --git a/server.js b/server.js index 9674064..c8b0ab6 100644 --- a/server.js +++ b/server.js @@ -4,12 +4,12 @@ const params = require('./lib/params') const SRPInteger = require('./lib/srp-integer') exports.generateEphemeral = function (verifier) { - // N A large safe prime (N = 2q+1, where q is prime) - // g A generator modulo N - // k Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6) + // N A large safe prime (N = 2q+1, where q is prime) + // g A generator modulo N + // k Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6) const { N, g, k } = params - // v Password verifier + // v Password verifier const v = SRPInteger.fromHex(verifier) // B = kv + g^b (b = random number) @@ -23,18 +23,19 @@ exports.generateEphemeral = function (verifier) { } exports.deriveSession = function (serverSecretEphemeral, clientPublicEphemeral, salt, username, verifier, clientSessionProof) { - // N A large safe prime (N = 2q+1, where q is prime) - // g A generator modulo N - // k Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6) - // H() One-way hash function - const { N, g, k, H } = params - - // b Secret ephemeral values - // A Public ephemeral values - // s User's salt - // p Cleartext Password - // I Username - // v Password verifier + // N A large safe prime (N = 2q+1, where q is prime) + // g A generator modulo N + // k Multiplier parameter (k = H(N, g) in SRP-6a, k = 3 for legacy SRP-6) + // H() One-way hash function + // PAD() Pad the number to have the same number of bytes as N + const { N, g, k, H, PAD } = params + + // b Secret ephemeral values + // A Public ephemeral values + // s User's salt + // p Cleartext Password + // I Username + // v Password verifier const b = SRPInteger.fromHex(serverSecretEphemeral) const A = SRPInteger.fromHex(clientPublicEphemeral) const s = SRPInteger.fromHex(salt) @@ -50,8 +51,8 @@ exports.deriveSession = function (serverSecretEphemeral, clientPublicEphemeral, throw new Error('The client sent an invalid public ephemeral') } - // u = H(A, B) - const u = H(A, B) + // u = H(PAD(A), PAD(B)) + const u = H(PAD(A), PAD(B)) // S = (Av^u) ^ b (computes session key) const S = A.multiply(v.modPow(u, N)).modPow(b, N)