{"id":15319123,"url":"https://github.com/zoontek/secure-remote-password","last_synced_at":"2025-10-09T05:30:36.991Z","repository":{"id":48703617,"uuid":"385608427","full_name":"zoontek/secure-remote-password","owner":"zoontek","description":"A modern SRP implementation for Node.js (v15+) and web browsers","archived":false,"fork":true,"pushed_at":"2021-10-04T14:17:46.000Z","size":233,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-10-02T09:03:46.305Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"LinusU/secure-remote-password","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zoontek.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-07-13T13:10:57.000Z","updated_at":"2022-05-24T23:51:11.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/zoontek/secure-remote-password","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zoontek%2Fsecure-remote-password","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zoontek%2Fsecure-remote-password/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zoontek%2Fsecure-remote-password/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zoontek%2Fsecure-remote-password/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zoontek","download_url":"https://codeload.github.com/zoontek/secure-remote-password/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235791916,"owners_count":19045577,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-01T09:03:49.735Z","updated_at":"2025-10-09T05:30:31.693Z","avatar_url":"https://github.com/zoontek.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Secure Remote Password for JavaScript\n\nA modern [SRP](http://srp.stanford.edu) implementation for modern Node.js (v15+) and web browsers.\n\n## Installation\n\n```sh\nyarn add @zoontek/secure-remote-password\n```\n\n## Usage\n\n### Signing up\n\nWhen 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:\n\n```ts\nimport * as client from \"@zoontek/secure-remote-password/client\";\n\n// These should come from the user signing up\nconst username = \"linus@folkdatorn.se\";\nconst password = \"$uper$ecure\";\n\nconst salt = client.generateSalt();\nconst privateKey = await client.derivePrivateKey(salt, username, password);\nconst verifier = client.deriveVerifier(privateKey);\n\nconsole.log(salt);\n//=\u003e FB95867E...\n\nconsole.log(verifier);\n//=\u003e 9392093F...\n\n// Send `username`, `salt` and `verifier` to the server\n```\n\n_note:_ `derivePrivateKey` is provided for completeness with the SRP 6a specification. It is however recommended to use some form of \"slow hashing\", like [PBKDF2](https://en.wikipedia.org/wiki/PBKDF2), to reduce the viability of a brute force attack against the verifier.\n\n_note:_ The use of a username as part of the verifier calculation means that if the user changes their username they must simultaneously provide an update salt and verifier to the server. If a user is able to login with multiple identifiers (e.g. username, phone number, or email address) you would need a separate verifier for each identifier. To avoid these issues you can leave the `username` blank for purposes of this algorithm. The downside of not using a username is that a server can do an attack to determine whether two users have the same password. For normal apps that trust the server but use SRP just to avoid transmitting plaintext passwords, this may be an acceptable trade-off.\n\n### Logging in\n\nAuthenticating with the server involves multiple steps.\n\n**1** - The client generates a secret/public ephemeral value pair.\n\n```ts\nimport * as client from \"@zoontek/secure-remote-password/client\";\n\n// This should come from the user logging in\nconst username = \"linus@folkdatorn.se\";\n\nconst clientEphemeral = client.generateEphemeral();\n\nconsole.log(clientEphemeral.public);\n//=\u003e DE63C51E...\n\n// Send `username` and `clientEphemeral.public` to the server\n```\n\n**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.\n\n_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\n\n```ts\nimport * as server from \"@zoontek/secure-remote-password/server\";\n\n// This should come from the user database\nconst salt = \"FB95867E...\";\nconst verifier = \"9392093F...\";\n\nconst serverEphemeral = await server.generateEphemeral(verifier);\n\nconsole.log(serverEphemeral.public);\n//=\u003e DA084F5C...\n\n// Store `serverEphemeral.secret` for later use\n// Send `salt` and `serverEphemeral.public` to the client\n```\n\n**3** - The client can now derive the shared strong session key, and a proof of it to provide to the server.\n\n```ts\nimport * as client from \"@zoontek/secure-remote-password/client\";\n\n// This should come from the user logging in\nconst password = \"$uper$ecret\";\nconst privateKey = await client.derivePrivateKey(salt, username, password);\n\nconst clientSession = await client.deriveSession(\n  clientEphemeral.secret,\n  serverPublicEphemeral,\n  salt,\n  username,\n  privateKey,\n);\n\nconsole.log(clientSession.key);\n//=\u003e 2A6FF04E...\n\nconsole.log(clientSession.proof);\n//=\u003e 6F8F4AC3\n\n// Send `clientSession.proof` to the server\n```\n\n**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.\n\n```ts\nimport * as server from \"@zoontek/secure-remote-password/server\";\n\n// Previously stored `serverEphemeral.secret`\nconst serverSecretEphemeral = \"784D6E83...\";\n\nconst serverSession = await server.deriveSession(\n  serverSecretEphemeral,\n  clientPublicEphemeral,\n  salt,\n  username,\n  verifier,\n  clientSessionProof,\n);\n\nconsole.log(serverSession.key);\n//=\u003e 2A6FF04E...\n\nconsole.log(serverSession.proof);\n//=\u003e 92561B95\n\n// Send `serverSession.proof` to the client\n```\n\n**5** - Finally, the client can verify that the server have derived the correct strong session key, using the proof that the server sent back.\n\n```ts\nimport client from \"@zoontek/secure-remote-password/client\";\n\nawait client.verifySession(\n  clientEphemeral.public,\n  clientSession,\n  serverSessionProof,\n);\n\n// All done!\n```\n\n## API\n\n```ts\nimport {\n  generateSalt,\n  derivePrivateKey,\n  deriveVerifier,\n  generateEphemeral,\n  deriveSession,\n  verifySession,\n} from \"@zoontek/secure-remote-password/client\";\n\ntype generateSalt = () =\u003e string;\n\n// Generate a salt suitable for computing the verifier with.\n\ntype derivePrivateKey = (\n  salt: string,\n  username: string,\n  password: string,\n) =\u003e Promise\u003cstring\u003e;\n\n// Derives a private key suitable for computing the verifier with.\n\ntype deriveVerifier = (privateKey: string) =\u003e string;\n\n// Derive a verifier to be stored for subsequent authentication attempts.\n\ntype generateEphemeral = () =\u003e Ephemeral;\n\n// Generate ephemeral values used to initiate an authentication session.\n\ntype deriveSession = (\n  clientSecretEphemeral: string,\n  serverPublicEphemeral: string,\n  salt: string,\n  username: string,\n  privateKey: string,\n) =\u003e Promise\u003cSession\u003e;\n\n// Compute a session key and proof. The proof is to be sent to the server for verification.\n\ntype verifySession = (\n  clientPublicEphemeral: string,\n  clientSession: Session,\n  serverSessionProof: string,\n) =\u003e Promise\u003cvoid\u003e;\n\n// Verifies the server provided session proof. Throws an error if the session proof is invalid.\n```\n\n```ts\nimport {\n  generateEphemeral,\n  deriveSession,\n} from \"@zoontek/secure-remote-password/server\";\n\ntype generateEphemeral = (verifier: string) =\u003e Promise\u003cEphemeral\u003e;\n\n// Generate ephemeral values used to continue an authentication session.\n\ntype deriveSession = (\n  serverSecretEphemeral: string,\n  clientPublicEphemeral: string,\n  salt: string,\n  username: string,\n  verifier: string,\n  clientSessionProof: string,\n) =\u003e Promise\u003cSession\u003e;\n\n// Compute a session key and proof. The proof is to be sent to the client for verification.\n// Throws an error if the session proof from the client is invalid.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzoontek%2Fsecure-remote-password","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzoontek%2Fsecure-remote-password","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzoontek%2Fsecure-remote-password/lists"}