
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
@bittery/srp6a
Advanced tools
A modern SRP implementation for all JavaScript runtimes (Node.js, Bun, Deno, Browser, React Native).
Modern implementation of SRP-6a for all JavaScript runtimes: Node.js, Bun, Deno, browsers, and React Native.
Fork of srp.
npm install @bittery/srp6a
yarn add @bittery/srp6a
pnpm add @bittery/srp6a
bun add @bittery/srp6a
For React Native, you must install and import react-native-get-random-values before importing this library:
npm install react-native-get-random-values
Then at the top of your entry file (e.g., index.js or App.js):
import 'react-native-get-random-values';
import { createSRPClient } from '@bittery/srp6a';
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 '@bittery/srp6a';
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:
derivePrivateKeyis also provided for completeness with the SRP-6a specification. However, it is recommended to usederiveSafePrivateKeyinstead, asderivePrivateKeyis highly exposed to brute force attacks against the verifier. Additionally, using ausernameas part of the verifier calculation means that if it changes, the salt and verifier need to be updated too.
deriveSafePrivateKey uses PBKDF2 for "slow hashing". When using it instead of derivePrivateKey, pass an empty string to the deriveSession function instead of the username (""). The downside is that a server can perform an attack to determine whether two users have the same password. This is an acceptable trade-off.
Authenticating with the server involves multiple steps.
1 - The client generates a secret/public ephemeral value pair.
import { createSRPClient } from '@bittery/srp6a';
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 can 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 '@bittery/srp6a';
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 '@bittery/srp6a';
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 '@bittery/srp6a';
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 '@bittery/srp6a';
const client = createSRPClient('SHA-256', 2048);
await client.verifySession(
clientEphemeral.public,
clientSession,
serverSessionProof,
);
import { createSRPClient } from '@bittery/srp6a';
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);
Generate a salt suitable for computing the verifier with.
type generateSalt = () => string;
Derives a private key suitable for computing the verifier with.
type derivePrivateKey = (
salt: string,
username: string,
password: string,
) => Promise<string>;
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>;
Derive a verifier to be stored for subsequent authentication attempts.
type deriveVerifier = (privateKey: string) => string;
Generate ephemeral values used to initiate an authentication session.
type generateEphemeral = () => {
secret: string;
public: string;
};
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;
}>;
Verifies the server provided session proof.
Warning: Throws
SRPErrorif the session proof is invalid.
type verifySession = (
clientPublicEphemeral: string,
clientSession: Session,
serverSessionProof: string,
) => Promise<void>;
import { createSRPServer } from '@bittery/srp6a';
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);
Generate ephemeral values used to continue an authentication session.
type generateEphemeral = (verifier: string) => Promise<{
public: string;
secret: string;
}>;
Compute a session key and proof. The proof is to be sent to the client for verification.
Warning: Throws
SRPErrorif 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;
}>;
For advanced use cases, you can provide a custom crypto implementation:
import { setCryptoProvider, type CryptoProvider } from '@bittery/srp6a';
const customProvider: CryptoProvider = {
getRandomValues: (array: Uint8Array) => {
// Your implementation
},
digest: (hashAlgorithm, data) => {
// Your implementation - return Promise<ArrayBuffer>
},
deriveKeyWithPBKDF2: (hashAlgorithm, salt, password, iterations) => {
// Your implementation - return Promise<ArrayBuffer>
},
};
setCryptoProvider(customProvider);
// Reset to auto-detection
setCryptoProvider(null);
| Platform | Status |
|---|---|
| Node.js 15+ | Native WebCrypto |
| Bun | Native WebCrypto |
| Deno | Native WebCrypto |
| Modern browsers | Native WebCrypto |
| React Native | Pure JS fallback (requires react-native-get-random-values) |
MIT
FAQs
A modern SRP implementation for all JavaScript runtimes (Node.js, Bun, Deno, Browser, React Native).
We found that @bittery/srp6a demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.