UNPKG

@scirexs/srp6a

Version:

SRP-6a (Secure Remote Password) implementation in TypeScript for browser and server.

106 lines (105 loc) 4.97 kB
export { addRandomDelay, calculateVerifier, computeClientEvidence, computeClientKey, computeIdentity, computeMultiplier, computeScramblingParameter, computeSecret, computeServerEvidence, computeServerKey, generateKeyPair, generateRandomKey, generateSalt, generateServerKeyPair, isValidPublic, }; import { computeHash, CryptoNumber, generateSecureRandom } from "./crypto.js"; /** s = RAND() */ function generateSalt(config) { return generateSecureRandom(config.salt); } /** I = H(U | ":" | p) */ async function computeIdentity(username, password, config) { if (!isValidIdentitySource(username, password)) throw new Error("Username and password must have length."); return await computeHash(new TextEncoder().encode(`${username}:${password}`), config); } /** x = H(s | I) */ async function computeSecret(salt, identity, config) { return await computeHash(CryptoNumber.concat(salt, identity), config); } /** v = MP(g, x, N) */ function calculateVerifier(secret, config) { return CryptoNumber.modPow(config.generator, secret, config.prime); } /** k = H(N | PAD(g)) */ async function computeMultiplier(config) { const num = config.multiplier ? new CryptoNumber(config.multiplier) : CryptoNumber.concat(config.prime, config.generator.pad()); return await computeHash(num, config); } /** b, B = k * v + MP(g, b, N) */ function generateServerKeyPair(multiplier, verifier, config) { const pair = generateKeyPair(config); pair.public = new CryptoNumber((multiplier.int * verifier.int + pair.public.int) % config.prime.int); // to prevent exceed prime return pair; } /** a, A = MP(g, a, N) */ function generateKeyPair(config) { const pvt = generateRandomKey(config); return { private: pvt, public: CryptoNumber.modPow(config.generator, pvt, config.prime), }; } /** a = RAND(), b = RAND() */ function generateRandomKey(config) { let result; do { const array = generateSecureRandom(config.hashBytes); result = array.int % config.prime.int; } while (result === 0n); return new CryptoNumber(result); } /** u = H(PAD(A) | PAD(B)) */ async function computeScramblingParameter(client, server, config) { return await computeHash(CryptoNumber.concat(client.pad(), server.pad()), config); } /** Kc = H(Sc) */ async function computeClientKey(server, multiplier, secret, pvt, scrambling, config) { return await computeHash(calculateClientSession(server, multiplier, secret, pvt, scrambling, config), config); } /** Sc = MP(B - (k * MP(g, x, N)), a + (u * x), N) */ function calculateClientSession(server, multiplier, secret, pvt, scrambling, config) { const kgx = (multiplier.int * CryptoNumber.modPow(config.generator, secret, config.prime).int) % config.prime.int; const base = (server.int - kgx + config.prime.int) % config.prime.int; // to prevent negative, add prime const pow = (pvt.int + (scrambling.int * secret.int)) % config.prime.int; // to prevent exceed prime return CryptoNumber.modPow(base, pow, config.prime); } /** Mc = H(H(N) xor H(g), H(U), s, A, B, K) */ async function computeClientEvidence(username, salt, client, server, key, config) { const primeHash = await computeHash(config.prime, config); const generatorHash = await computeHash(config.generator, config); const xor = CryptoNumber.xor(primeHash, generatorHash); const usernameHash = await computeHash(new TextEncoder().encode(username), config); return await computeHash(CryptoNumber.concat(xor, usernameHash, salt, client, server, key), config); } /** Ks = H(Ss) */ async function computeServerKey(client, verifier, scrambling, pvt, config) { return await computeHash(calculateServerSession(client, verifier, scrambling, pvt, config), config); } /** Ss = MP(A * MP(v, u, N), b, N) */ function calculateServerSession(client, verifier, scrambling, pvt, config) { const base = (client.int * CryptoNumber.modPow(verifier, scrambling, config.prime).int) % config.prime.int; return CryptoNumber.modPow(base, pvt, config.prime); } /** Ms = H(A, Mc, K) */ async function computeServerEvidence(client, evidence, key, config) { return await computeHash(CryptoNumber.concat(client, evidence, key), config); } /** Confirm username and password is not empty. */ function isValidIdentitySource(username, password) { return Boolean(username) && Boolean(password); } /** Confirm public key is valid or not. */ function isValidPublic(pub, config) { return pub.int % config.prime.int !== 0n && 1n <= pub.int && pub.int < config.prime.int; } /** Add random delay to fail authentication. */ async function addRandomDelay(ms = 5) { ms = Math.ceil(Math.abs(ms)); ms = ms <= 1 ? 5 : ms; const delay = (Math.random() * (ms - 1)) + 1; await new Promise((resolve) => setTimeout(resolve, delay)); } // export const __internal = Deno?.args?.includes("test") // ? { // calculateClientSession, // calculateServerSession, // } // : {};