@swan-io/srp
TypeScript icon, indicating that this package has built-in type declarations

0.2.6 • Public • Published

@swan-io/srp

A modern SRP implementation for Node.js (v15+) and web browsers. Living fork of secure-remote-password.

Installation

yarn add @swan-io/srp

Usage

Signing up

When creating an account with the server, the client will provide a salt and a verifier for the server to store. They are calculated by the client as follows:

import { createSRPClient } from "@swan-io/srp";
const client = createSRPClient("SHA-256", 2048);

// These should come from the user signing up
const username = "linus@folkdatorn.se";
const password = "$uper$ecure";

const salt = client.generateSalt();
const privateKey = await client.deriveSafePrivateKey(salt, password);
const verifier = client.deriveVerifier(privateKey);

// Send `username`, `salt` and `verifier` to the server

⚠️  Note that derivePrivateKey is also provided for completeness with the SRP-6a specification. It is, however, recommended to avoid using it as it's highly exposed to brute force attack against the verifier. Also, the use of a username as part of the verifier calculation means that if it changes, the salt and verifier needs to be updated to.

To avoid these issues, we provide a deriveSafePrivateKey function that uses PBKDF2 for "slow hashing".
When using it instead of derivePrivateKey, as the private key is not generated using an username, you will have to pass an empty string to the deriveSession function instead (""). The downside of this method is that a server can do an attack to determine whether two users have the same password. This is an acceptable trade-off.

Logging in

Authenticating with the server involves multiple steps.

1 - The client generates a secret/public ephemeral value pair.

import { createSRPClient } from "@swan-io/srp";
const client = createSRPClient("SHA-256", 2048);

// This should come from the user logging in
const username = "linus@folkdatorn.se";
const clientEphemeral = client.generateEphemeral();

// Send `username` and `clientEphemeral.public` to the server

2 - The server receives the client's public ephemeral value and username. Using the username we retrieve the salt and verifier from our user database. We then generate our own ephemeral value pair.

note: if no user cannot be found in the database, a bogus salt and ephemeral value should be returned, to avoid leaking which users have signed up.

import { createSRPServer } from "@swan-io/srp";
const server = createSRPServer("SHA-256", 2048);

// This should come from the user database
const salt = "fb95867e…";
const verifier = "9392093f…";

const serverEphemeral = await server.generateEphemeral(verifier);

// Store `serverEphemeral.secret` for later use
// Send `salt` and `serverEphemeral.public` to the client

3 - The client can now derive the shared strong session key and a proof of it to provide to the server.

import { createSRPClient } from "@swan-io/srp";
const client = createSRPClient("SHA-256", 2048);

// This should come from the user logging in
const password = "$uper$ecret";
const privateKey = await client.deriveSafePrivateKey(salt, password);

const clientSession = await client.deriveSession(
  clientEphemeral.secret,
  serverPublicEphemeral,
  salt,
  "", // or `username` if you used `derivePrivateKey`
  privateKey,
);

// Send `clientSession.proof` to the server

4 - The server is also ready to derive the shared strong session key and can verify that the client has the same key using the provided proof.

import { createSRPServer } from "@swan-io/srp";
const server = createSRPServer("SHA-256", 2048);

// Previously stored `serverEphemeral.secret`
const serverSecretEphemeral = "784d6e83…";

const serverSession = await server.deriveSession(
  serverSecretEphemeral,
  clientPublicEphemeral,
  salt,
  "", // or `username` if you used `derivePrivateKey`
  verifier,
  clientSessionProof,
);

// Send `serverSession.proof` to the client

5 - Finally, the client can verify that the server has derived the correct strong session key, using the proof that the server sent back.

import { createSRPClient } from "@swan-io/srp";
const client = createSRPClient("SHA-256", 2048);

await client.verifySession(
  clientEphemeral.public,
  clientSession,
  serverSessionProof,
);

API

Client

import { createSRPClient } from "@swan-io/srp";

type HashAlgorithm = "SHA-1" | "SHA-256" | "SHA-384" | "SHA-512";
type PrimeGroup = 1024 | 1536 | 2048 | 3072 | 4096 | 6144 | 8192;

const hashAlgorithm: HashAlgorithm = "SHA-256";
const primeGroup: PrimeGroup = 2048;

const client = createSRPClient(hashAlgorithm, primeGroup);

client.generateSalt

Generate a salt suitable for computing the verifier with.

type generateSalt() => string;

client.derivePrivateKey

Derives a private key suitable for computing the verifier with.

type derivePrivateKey = (
  salt: string,
  username: string,
  password: string,
) => Promise<string>;

client.deriveSafePrivateKey

Derives a private key suitable for computing the verifier with using PBKDF2. By default, it will use the iterations count recommended by OWASP.

type deriveSafePrivateKey = (
  salt: string,
  password: string,
  iterations?: number,
) => Promise<string>;

client.deriveVerifier

Derive a verifier to be stored for subsequent authentication attempts.

type deriveVerifier = (privateKey: string) => string;

client.generateEphemeral

Generate ephemeral values used to initiate an authentication session.

type generateEphemeral = () => {
  secret: string;
  public: string;
};

client.deriveSession

Compute a session key and proof. The proof is to be sent to the server for verification.

type deriveSession = (
  clientSecretEphemeral: string,
  serverPublicEphemeral: string,
  salt: string,
  username: string,
  privateKey: string,
) => Promise<{
  key: string;
  proof: string;
}>;

client.verifySession

Verifies the server provided session proof.
⚠️ Rejects a SRPError if the session proof is invalid.

type verifySession = (
  clientPublicEphemeral: string,
  clientSession: Session,
  serverSessionProof: string,
) => Promise<void>;

Server

import { createSRPServer } from "@swan-io/srp";

type HashAlgorithm = "SHA-1" | "SHA-256" | "SHA-384" | "SHA-512";
type PrimeGroup = 1024 | 1536 | 2048 | 3072 | 4096 | 6144 | 8192;

const hashAlgorithm: HashAlgorithm = "SHA-256";
const primeGroup: PrimeGroup = 2048;

const server = createSRPServer(hashAlgorithm, primeGroup);

server.generateEphemeral

Generate ephemeral values used to continue an authentication session.

type generateEphemeral = (verifier: string) => Promise<{
  public: string;
  secret: string;
}>;

server.deriveSession

Compute a session key and proof. The proof is to be sent to the client for verification.
⚠️ Rejects a SRPError if the session proof from the client is invalid.

type deriveSession = (
  serverSecretEphemeral: string,
  clientPublicEphemeral: string,
  salt: string,
  username: string,
  verifier: string,
  clientSessionProof: string,
) => Promise<{
  key: string;
  proof: string;
}>;

Package Sidebar

Install

npm i @swan-io/srp

Weekly Downloads

335

Version

0.2.6

License

MIT

Unpacked Size

107 kB

Total Files

19

Last publish

Collaborators

  • swan-io