Stanford JavaScript Crypto Library providing comprehensive cryptographic operations including AES encryption, hash functions, key derivation, and elliptic curve cryptography.
—
SJCL provides Secure Remote Password (SRP) protocol implementation for password-authenticated key agreement, allowing secure authentication and key derivation without transmitting passwords over the network.
The Secure Remote Password protocol allows two parties to authenticate each other and derive a shared secret using only a password, without ever transmitting the password itself.
/**
* Calculate SRP verifier for user registration
* @param {string} I - Username/identity
* @param {string} P - Password
* @param {BitArray} s - Salt (random value)
* @param {Object} [group] - SRP group parameters (default: SRP-6a with 1024-bit prime)
* @returns {BitArray} SRP verifier value
*/
sjcl.keyexchange.srp.makeVerifier(I, P, s, group);
/**
* Calculate SRP x value (private key derived from password)
* @param {string} I - Username/identity
* @param {string} P - Password
* @param {BitArray} s - Salt value
* @returns {BitArray} SRP x value (private key)
*/
sjcl.keyexchange.srp.makeX(I, P, s);Usage Examples:
const sjcl = require('sjcl');
// User registration phase
function registerUser(username, password) {
// Generate random salt
const salt = sjcl.random.randomWords(4); // 128-bit salt
// Calculate verifier for storage on server
const verifier = sjcl.keyexchange.srp.makeVerifier(username, password, salt);
// Store username, salt, and verifier on server
// NEVER store the actual password
return {
username: username,
salt: salt,
verifier: verifier
};
}
// Example registration
const userRecord = registerUser("alice@example.com", "strongPassword123");
console.log("User registered with salt:", sjcl.codec.hex.fromBits(userRecord.salt));
console.log("Verifier:", sjcl.codec.hex.fromBits(userRecord.verifier));
// Calculate x value (used internally during authentication)
const xValue = sjcl.keyexchange.srp.makeX("alice@example.com", "strongPassword123", userRecord.salt);
console.log("X value:", sjcl.codec.hex.fromBits(xValue));The SRP protocol consists of several phases:
During user registration, the client calculates a verifier that the server stores instead of the password.
const sjcl = require('sjcl');
function srpRegistration(username, password) {
// Client generates salt (or server can generate and send to client)
const salt = sjcl.random.randomWords(4);
// Client calculates verifier
const verifier = sjcl.keyexchange.srp.makeVerifier(username, password, salt);
// Send username, salt, and verifier to server for storage
return {
username: username,
salt: sjcl.codec.hex.fromBits(salt),
verifier: sjcl.codec.hex.fromBits(verifier)
};
}
// Usage
const registration = srpRegistration("user@domain.com", "mySecurePassword");
console.log("Registration data:", registration);During authentication, both client and server perform calculations to verify the password without transmitting it.
const sjcl = require('sjcl');
// Client-side authentication start
function clientAuthStart(username, password, saltFromServer) {
// Convert salt from hex if received from server
const salt = sjcl.codec.hex.toBits(saltFromServer);
// Calculate x (private key)
const x = sjcl.keyexchange.srp.makeX(username, password, salt);
// In full SRP implementation, client would also:
// 1. Generate random 'a' value
// 2. Calculate A = g^a mod N (public key)
// 3. Send A to server
return {
x: x,
// Additional SRP values would be calculated here
};
}
// Server-side verification preparation
function serverPrepareAuth(storedUserRecord) {
// Server has stored: username, salt, verifier
// In full SRP implementation, server would:
// 1. Generate random 'b' value
// 2. Calculate B = (kv + g^b) mod N
// 3. Send B and salt to client
return {
salt: storedUserRecord.salt,
// B value would be calculated and included
};
}const sjcl = require('sjcl');
// Best practices for SRP implementation
class SRPBestPractices {
static generateSecureSalt() {
// Use at least 128 bits of random salt
return sjcl.random.randomWords(4);
}
static validateUsername(username) {
// Normalize username to prevent attacks
return username.toLowerCase().trim();
}
static strengthenPassword(password) {
// Consider additional password strengthening
// This is a simplified example
if (password.length < 8) {
throw new Error("Password too short");
}
return password;
}
static secureRegistration(username, password) {
const normalizedUsername = SRPBestPractices.validateUsername(username);
const strengthenedPassword = SRPBestPractices.strengthenPassword(password);
const salt = SRPBestPractices.generateSecureSalt();
const verifier = sjcl.keyexchange.srp.makeVerifier(
normalizedUsername,
strengthenedPassword,
salt
);
return {
username: normalizedUsername,
salt: salt,
verifier: verifier,
timestamp: Date.now()
};
}
}
// Usage with best practices
try {
const secureReg = SRPBestPractices.secureRegistration("User@Example.com", "MyStrongPassword123!");
console.log("Secure registration successful");
} catch (error) {
console.error("Registration failed:", error.message);
}SRP can use different mathematical groups for different security levels:
const sjcl = require('sjcl');
// Example of using SRP with custom parameters
function srpWithCustomGroup(username, password, salt, customGroup) {
// Custom group would define different prime modulus and generator
// This is an advanced feature for specific security requirements
const verifier = sjcl.keyexchange.srp.makeVerifier(
username,
password,
salt,
customGroup
);
return verifier;
}
// Note: Custom groups require careful cryptographic analysis
// Default parameters are recommended for most applicationsCombine SRP with other key derivation methods:
const sjcl = require('sjcl');
function enhancedSRPRegistration(username, password, additionalEntropy) {
// Generate salt
const salt = sjcl.random.randomWords(4);
// Optional: Strengthen password with additional entropy
const strengthenedPassword = password + additionalEntropy;
// Calculate SRP verifier
const verifier = sjcl.keyexchange.srp.makeVerifier(username, strengthenedPassword, salt);
// Derive additional keys using HKDF
const masterKey = sjcl.misc.hkdf(
verifier,
256,
salt,
sjcl.codec.utf8String.toBits("SRP-master-key")
);
const authKey = sjcl.misc.hkdf(
masterKey,
256,
salt,
sjcl.codec.utf8String.toBits("authentication")
);
return {
username: username,
salt: salt,
verifier: verifier,
authKey: authKey
};
}
// Usage
const enhancedReg = enhancedSRPRegistration(
"user@example.com",
"password123",
"device-specific-entropy"
);Example of integrating SRP into a web application:
const sjcl = require('sjcl');
class WebSRPClient {
constructor(apiEndpoint) {
this.apiEndpoint = apiEndpoint;
}
async register(username, password) {
// Client-side registration
const salt = sjcl.random.randomWords(4);
const verifier = sjcl.keyexchange.srp.makeVerifier(username, password, salt);
// Send to server
const registrationData = {
username: username,
salt: sjcl.codec.hex.fromBits(salt),
verifier: sjcl.codec.hex.fromBits(verifier)
};
// In real implementation, this would be an HTTP request
console.log("Would send to server:", registrationData);
return true;
}
async authenticate(username, password) {
// Step 1: Get salt from server
// const { salt } = await fetch(`${this.apiEndpoint}/auth/salt/${username}`);
// Step 2: Calculate client credentials
const salt = sjcl.random.randomWords(4); // In real app, from server
const x = sjcl.keyexchange.srp.makeX(username, password, salt);
// Step 3: Continue with full SRP protocol
// (Additional steps would involve A/B value exchange)
console.log("Authentication x value calculated");
return { success: true, sessionKey: "derived-session-key" };
}
}
// Usage
const srpClient = new WebSRPClient("https://api.example.com");
// srpClient.register("user@example.com", "securePassword");Install with Tessl CLI
npx tessl i tessl/npm-sjcl