Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

💥 Pad values following RFC5054 #13

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions browser/sha256.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
55 changes: 28 additions & 27 deletions client.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
22 changes: 16 additions & 6 deletions lib/params.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
LinusU marked this conversation as resolved.
Show resolved Hide resolved
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
3 changes: 3 additions & 0 deletions lib/sha256.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down
8 changes: 8 additions & 0 deletions lib/srp-integer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
37 changes: 19 additions & 18 deletions server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down