{"_id":"@bittery/srp6a","_rev":"2-88ecda844d72d5744ec471067218dce9","name":"@bittery/srp6a","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@bittery/srp6a","version":"1.0.0","keywords":["srp","srp6a","authentication","remote","password","security","bun"],"license":"MIT","_id":"@bittery/srp6a@1.0.0","maintainers":[{"name":"pixelmund","email":"pixelmund@gmail.com"}],"homepage":"https://github.com/bittery-org/js-srp6a#readme","bugs":{"url":"https://github.com/bittery-org/js-srp6a/issues"},"dist":{"shasum":"4b90021af413862578f57a69eef2f882b9fb1a80","tarball":"https://registry.npmjs.org/@bittery/srp6a/-/srp6a-1.0.0.tgz","fileCount":17,"integrity":"sha512-qj/64FBBTJ7yJ+wSg8uXR9yRHh14cIHrhwkRZYWIck2TsoUKdBduWRSCi//DFF47X3UzSjggfDfSe3DepuUshw==","signatures":[{"sig":"MEQCICBafwiIEL2kim7r2Wg7YErEye9goWpzFVUjkcmjqyj9AiBQ14aju+3Q/gScmJ/CvA+ExmzpXE7D3J+MqPe5ZJMzoQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":98407},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"}},"gitHead":"6fbc56a096aa11d0ee06d864d7dfadedf849f91b","scripts":{"ci":"bun check && bun lint . && bun test tests","lint":"biome lint . --no-errors-on-unmatched","build":"bun build --outdir dist ./src/index.ts && bun generate:types","check":"tsc --project tsconfig.json","format":"biome format --write .","generate:types":"tsc --project tsconfig.build.json"},"_npmUser":{"name":"pixelmund","email":"pixelmund@gmail.com"},"repository":{"url":"git+https://github.com/bittery-org/js-srp6a.git","type":"git"},"_npmVersion":"11.6.1","description":"A modern SRP implementation for all JavaScript runtimes (Node.js, Bun, Deno, Browser, React Native).","directories":{},"_nodeVersion":"24.10.0","browserslist":[">0.2%","not op_mini all","not dead"],"dependencies":{"jsbn":"1.1.0","@noble/hashes":"^1.7.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"@types/bun":"latest","typescript":"5.8.2","@types/jsbn":"1.2.33","@biomejs/biome":"1.9.4"},"peerDependencies":{"react-native-get-random-values":">=1.0.0"},"peerDependenciesMeta":{"react-native-get-random-values":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/srp6a_1.0.0_1769076156222_0.9847821551537004","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@bittery/srp6a","version":"1.0.1","type":"module","license":"MIT","description":"A modern SRP implementation for all JavaScript runtimes (Node.js, Bun, Deno, Browser, React Native).","homepage":"https://github.com/bittery-org/js-srp6a#readme","repository":{"type":"git","url":"git+https://github.com/bittery-org/js-srp6a.git"},"main":"dist/index.js","module":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"}},"keywords":["srp","srp6a","authentication","remote","password","security","bun"],"scripts":{"build":"bun build --outdir dist ./src/index.ts && bun generate:types","prepack":"bun build --outdir dist ./src/index.ts && bun generate:types","generate:types":"tsc --project tsconfig.build.json","check":"tsc --project tsconfig.json","lint":"biome lint . --no-errors-on-unmatched","ci":"bun check && bun lint . && bun test tests","format":"biome format --write ."},"browserslist":[">0.2%","not op_mini all","not dead"],"dependencies":{"@noble/hashes":"^1.7.0","jsbn":"1.1.0"},"peerDependencies":{"react-native-get-random-values":">=1.0.0"},"peerDependenciesMeta":{"react-native-get-random-values":{"optional":true}},"devDependencies":{"@biomejs/biome":"1.9.4","@types/bun":"latest","@types/jsbn":"1.2.33","typescript":"5.8.2"},"publishConfig":{"access":"public"},"gitHead":"6fbc56a096aa11d0ee06d864d7dfadedf849f91b","_id":"@bittery/srp6a@1.0.1","bugs":{"url":"https://github.com/bittery-org/js-srp6a/issues"},"_nodeVersion":"24.10.0","_npmVersion":"11.6.1","dist":{"integrity":"sha512-T1RhjA//7v4ZfkJKHbxXgc0jBgmSYO8Uq6PXN/U0ROE8Wu8ONpUsIL3+ifrangKagLkPNeyNgLRQITqy7ygxjQ==","shasum":"a0c7e539ecfb9a692d37cf721ac44ba3fee8b394","tarball":"https://registry.npmjs.org/@bittery/srp6a/-/srp6a-1.0.1.tgz","fileCount":17,"unpackedSize":99835,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIG6aLBJbai5u7WmojKAGdkBHtF0gLIgj9356FS2QiVQ7AiBGWp996moX/f6yQwlxEyosLARoinV22a5KauwgG2Xq7w=="}]},"_npmUser":{"name":"pixelmund","email":"pixelmund@gmail.com"},"directories":{},"maintainers":[{"name":"pixelmund","email":"pixelmund@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/srp6a_1.0.1_1769076411371_0.06646559411716191"},"_hasShrinkwrap":false}},"time":{"created":"2026-01-22T10:02:36.110Z","modified":"2026-01-22T10:06:51.632Z","1.0.0":"2026-01-22T10:02:36.366Z","1.0.1":"2026-01-22T10:06:51.512Z"},"bugs":{"url":"https://github.com/bittery-org/js-srp6a/issues"},"license":"MIT","homepage":"https://github.com/bittery-org/js-srp6a#readme","keywords":["srp","srp6a","authentication","remote","password","security","bun"],"repository":{"type":"git","url":"git+https://github.com/bittery-org/js-srp6a.git"},"description":"A modern SRP implementation for all JavaScript runtimes (Node.js, Bun, Deno, Browser, React Native).","maintainers":[{"name":"pixelmund","email":"pixelmund@gmail.com"}],"readme":"# @bittery/srp6a\n\nModern implementation of SRP-6a for all JavaScript runtimes: Node.js, Bun, Deno, browsers, and React Native.\n\n- [RFC 2945](https://datatracker.ietf.org/doc/html/rfc2945)\n- [RFC 5054](https://datatracker.ietf.org/doc/html/rfc5054)\n\nFork of [srp](https://github.com/swan-io/srp).\n\n## Installation\n\n```sh\nnpm install @bittery/srp6a\nyarn add @bittery/srp6a\npnpm add @bittery/srp6a\nbun add @bittery/srp6a\n```\n\n### React Native\n\nFor React Native, you must install and import `react-native-get-random-values` before importing this library:\n\n```sh\nnpm install react-native-get-random-values\n```\n\nThen at the top of your entry file (e.g., `index.js` or `App.js`):\n\n```ts\nimport 'react-native-get-random-values';\nimport { createSRPClient } from '@bittery/srp6a';\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 { createSRPClient } from '@bittery/srp6a';\nconst client = createSRPClient('SHA-256', 2048);\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.deriveSafePrivateKey(salt, password);\nconst verifier = client.deriveVerifier(privateKey);\n\n// Send `username`, `salt` and `verifier` to the server\n```\n\n> **Note:** `derivePrivateKey` is also provided for completeness with the SRP-6a specification. However, it is recommended to use `deriveSafePrivateKey` instead, as `derivePrivateKey` is highly exposed to brute force attacks against the verifier. Additionally, using a `username` as part of the verifier calculation means that if it changes, the salt and verifier need to be updated too.\n\n`deriveSafePrivateKey` uses [PBKDF2](https://en.wikipedia.org/wiki/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.\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 { createSRPClient } from '@bittery/srp6a';\nconst client = createSRPClient('SHA-256', 2048);\n\n// This should come from the user logging in\nconst username = 'linus@folkdatorn.se';\nconst clientEphemeral = client.generateEphemeral();\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 can 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 { createSRPServer } from '@bittery/srp6a';\nconst server = createSRPServer('SHA-256', 2048);\n\n// This should come from the user database\nconst salt = 'fb95867e…';\nconst verifier = '9392093f…';\n\nconst serverEphemeral = await server.generateEphemeral(verifier);\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 { createSRPClient } from '@bittery/srp6a';\nconst client = createSRPClient('SHA-256', 2048);\n\n// This should come from the user logging in\nconst password = '$uper$ecret';\nconst privateKey = await client.deriveSafePrivateKey(salt, password);\n\nconst clientSession = await client.deriveSession(\n  clientEphemeral.secret,\n  serverPublicEphemeral,\n  salt,\n  '', // or `username` if you used `derivePrivateKey`\n  privateKey,\n);\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 { createSRPServer } from '@bittery/srp6a';\nconst server = createSRPServer('SHA-256', 2048);\n\n// Previously stored `serverEphemeral.secret`\nconst serverSecretEphemeral = '784d6e83…';\n\nconst serverSession = await server.deriveSession(\n  serverSecretEphemeral,\n  clientPublicEphemeral,\n  salt,\n  '', // or `username` if you used `derivePrivateKey`\n  verifier,\n  clientSessionProof,\n);\n\n// Send `serverSession.proof` to the client\n```\n\n**5** - Finally, the client can verify that the server has derived the correct strong session key, using the proof that the server sent back.\n\n```ts\nimport { createSRPClient } from '@bittery/srp6a';\nconst client = createSRPClient('SHA-256', 2048);\n\nawait client.verifySession(\n  clientEphemeral.public,\n  clientSession,\n  serverSessionProof,\n);\n```\n\n## API\n\n### Client\n\n```ts\nimport { createSRPClient } from '@bittery/srp6a';\n\ntype HashAlgorithm = 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512';\ntype PrimeGroup = 1024 | 1536 | 2048 | 3072 | 4096 | 6144 | 8192;\n\nconst hashAlgorithm: HashAlgorithm = 'SHA-256';\nconst primeGroup: PrimeGroup = 2048;\n\nconst client = createSRPClient(hashAlgorithm, primeGroup);\n```\n\n#### client.generateSalt\n\nGenerate a salt suitable for computing the verifier with.\n\n```ts\ntype generateSalt = () => string;\n```\n\n#### client.derivePrivateKey\n\nDerives a private key suitable for computing the verifier with.\n\n```ts\ntype derivePrivateKey = (\n  salt: string,\n  username: string,\n  password: string,\n) => Promise<string>;\n```\n\n#### client.deriveSafePrivateKey\n\nDerives a private key suitable for computing the verifier with using [PBKDF2](https://en.wikipedia.org/wiki/PBKDF2). By default, it will use the iterations count [recommended by OWASP](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2).\n\n```ts\ntype deriveSafePrivateKey = (\n  salt: string,\n  password: string,\n  iterations?: number,\n) => Promise<string>;\n```\n\n#### client.deriveVerifier\n\nDerive a verifier to be stored for subsequent authentication attempts.\n\n```ts\ntype deriveVerifier = (privateKey: string) => string;\n```\n\n#### client.generateEphemeral\n\nGenerate ephemeral values used to initiate an authentication session.\n\n```ts\ntype generateEphemeral = () => {\n  secret: string;\n  public: string;\n};\n```\n\n#### client.deriveSession\n\nCompute a session key and proof. The proof is to be sent to the server for verification.\n\n```ts\ntype deriveSession = (\n  clientSecretEphemeral: string,\n  serverPublicEphemeral: string,\n  salt: string,\n  username: string,\n  privateKey: string,\n) => Promise<{\n  key: string;\n  proof: string;\n}>;\n```\n\n#### client.verifySession\n\nVerifies the server provided session proof.\n\n> **Warning:** Throws `SRPError` if the session proof is invalid.\n\n```ts\ntype verifySession = (\n  clientPublicEphemeral: string,\n  clientSession: Session,\n  serverSessionProof: string,\n) => Promise<void>;\n```\n\n### Server\n\n```ts\nimport { createSRPServer } from '@bittery/srp6a';\n\ntype HashAlgorithm = 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512';\ntype PrimeGroup = 1024 | 1536 | 2048 | 3072 | 4096 | 6144 | 8192;\n\nconst hashAlgorithm: HashAlgorithm = 'SHA-256';\nconst primeGroup: PrimeGroup = 2048;\n\nconst server = createSRPServer(hashAlgorithm, primeGroup);\n```\n\n#### server.generateEphemeral\n\nGenerate ephemeral values used to continue an authentication session.\n\n```ts\ntype generateEphemeral = (verifier: string) => Promise<{\n  public: string;\n  secret: string;\n}>;\n```\n\n#### server.deriveSession\n\nCompute a session key and proof. The proof is to be sent to the client for verification.\n\n> **Warning:** Throws `SRPError` if the session proof from the client is invalid.\n\n```ts\ntype deriveSession = (\n  serverSecretEphemeral: string,\n  clientPublicEphemeral: string,\n  salt: string,\n  username: string,\n  verifier: string,\n  clientSessionProof: string,\n) => Promise<{\n  key: string;\n  proof: string;\n}>;\n```\n\n## Advanced: Custom Crypto Provider\n\nFor advanced use cases, you can provide a custom crypto implementation:\n\n```ts\nimport { setCryptoProvider, type CryptoProvider } from '@bittery/srp6a';\n\nconst customProvider: CryptoProvider = {\n  getRandomValues: (array: Uint8Array) => {\n    // Your implementation\n  },\n  digest: (hashAlgorithm, data) => {\n    // Your implementation - return Promise<ArrayBuffer>\n  },\n  deriveKeyWithPBKDF2: (hashAlgorithm, salt, password, iterations) => {\n    // Your implementation - return Promise<ArrayBuffer>\n  },\n};\n\nsetCryptoProvider(customProvider);\n\n// Reset to auto-detection\nsetCryptoProvider(null);\n```\n\n## Platform Support\n\n| Platform | Status |\n|----------|--------|\n| Node.js 15+ | Native WebCrypto |\n| Bun | Native WebCrypto |\n| Deno | Native WebCrypto |\n| Modern browsers | Native WebCrypto |\n| React Native | Pure JS fallback (requires `react-native-get-random-values`) |\n\n## License\n\nMIT\n","readmeFilename":"README.md"}