first commit
This commit is contained in:
64
docs/security-stack/security/srp/src/math/proof.md
Normal file
64
docs/security-stack/security/srp/src/math/proof.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# `proof`
|
||||
|
||||
## Purpose
|
||||
|
||||
Internal helpers for computing the SRP-6a / RFC 2945 §3 mutual-authentication proofs `M1` and `M2`. Both client and server compute these values: the client emits `M1 = H(H(N) XOR H(g) | H(I) | salt | A | B | K)` to prove it knows the password, and the server emits `M2 = H(A | M1 | K)` to prove it knows the verifier. Co-locating the math in one module keeps the formula in exactly one place so any future correction (e.g. a hash-binding change) lands consistently across both roles.
|
||||
|
||||
## Features
|
||||
|
||||
- `computeM1(I, salt, A, B, K, params)` — RFC 2945 §3 client proof.
|
||||
- `computeM2(A, M1, K, params)` — RFC 2945 §3 server proof.
|
||||
- Equal-length XOR helper for the `H(N) XOR H(g)` term — bound to the chosen `params.hash` digest size.
|
||||
- UTF-8 encode `I` once per call.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { computeM1, computeM2 } from "@suaveplan/srp";
|
||||
import { params2048Sha256 } from "@suaveplan/srp/params";
|
||||
import { nobleHashBridge } from "@suaveplan/srp/hash";
|
||||
|
||||
const params = params2048Sha256(nobleHashBridge("SHA256"));
|
||||
const salt = new Uint8Array(16);
|
||||
const A = new Uint8Array(256);
|
||||
const B = new Uint8Array(256);
|
||||
const K = new Uint8Array(32);
|
||||
const M1 = computeM1("alice", salt, A, B, K, params);
|
||||
const M2 = computeM2(A, M1, K, params);
|
||||
console.log(M1.byteLength, M2.byteLength); // 32 32
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { computeM1, computeM2 } from "@suaveplan/srp";
|
||||
import { params2048Sha256 } from "@suaveplan/srp/params";
|
||||
import { nobleHashBridge } from "@suaveplan/srp/hash";
|
||||
import { constantTimeEqual } from "@suaveplan/crypto-utils";
|
||||
|
||||
const params = params2048Sha256(nobleHashBridge("SHA256"));
|
||||
const salt = new Uint8Array(16);
|
||||
const A = new Uint8Array(256);
|
||||
const B = new Uint8Array(256);
|
||||
const K = new Uint8Array(32);
|
||||
|
||||
const clientM1 = computeM1("alice", salt, A, B, K, params);
|
||||
const serverExpectedM1 = computeM1("alice", salt, A, B, K, params);
|
||||
console.log(constantTimeEqual(clientM1, serverExpectedM1));
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `computeM1(I, salt, A, B, K, params): Uint8Array`
|
||||
|
||||
Returns the client proof `H(H(N) XOR H(g) | H(I) | salt | A | B | K)`. Result length equals `params.hash.digestSize`.
|
||||
|
||||
### `computeM2(A, M1, K, params): Uint8Array`
|
||||
|
||||
Returns the server proof `H(A | M1 | K)`. Result length equals `params.hash.digestSize`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `H(N) XOR H(g)` term is a deliberate design choice in RFC 2945 — it commits both ends to the chosen group AND ties the proof to the hash function. An attacker who substituted a weakened group during transport would produce a different `H(N) XOR H(g)` value than the legitimate counterpart computed, causing the proof to fail.
|
||||
|
||||
`computeM1` deliberately accepts `A` and `B` as-is rather than re-padding them. RFC 2945's formula is over the un-padded public values, AND the values supplied to `computeM1` from the handshake functions are already produced by `bigIntToBytes(_, params.N.byteLength)` and so are already zero-padded to the modulus width. Double-padding would produce off-by-N-byte digests.
|
||||
Reference in New Issue
Block a user