Skip to content

Commit 5c80e37

Browse files
Jan Kopcsekjkopcsek
authored andcommitted
fix pbkdf2_prf sha/sha1 handling
fix
1 parent e072bc7 commit 5c80e37

File tree

4 files changed

+109
-18
lines changed

4 files changed

+109
-18
lines changed

COUCH_MIGRATION.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# CouchDB Migration 3.3 -> 3.4+
2+
3+
## Upgraded couch-auth, configured on old hashing
4+
5+
### Preconditions
6+
7+
- CouchDB running at 3.3
8+
- `couch-auth` at 0.21.2
9+
10+
### Steps
11+
12+
- Configure couch-auth session hashing to CouchDB 3.3 settings:
13+
14+
```
15+
sessionHashing: {
16+
pbkdf2Prf: 'sha',
17+
iterations: 10,
18+
}
19+
```
20+
21+
This will use a 16 byte salt and 20 byte key. This does not influence hashing of the `sl-users` passwords.
22+
23+
- Upgrade couch-auth to 0.23.0
24+
- Deploy
25+
26+
### Expected result
27+
28+
- Everything works as before, only that `_users` documents get additional fields for the `prf` and `iterations` used. These values are used by `couch-auth` and CouchDB when checking against the hash, thus ensuring that both systems are able to work with the hash.
29+
30+
## Upgrade CouchDB
31+
32+
### Preconditions
33+
34+
- CouchDB running at 3.3
35+
- `couch-auth` at >0.23.0 configured on `sha` with 10 iterations
36+
37+
### Steps (with auto upgrade)
38+
39+
- Decide on how many iterations you want. CouchDB defaults to 600,000 but that takes some time and `couch-auth` doesn't have caching, but the passwords are synthetic and short-lived. Let's use 1000 in this example.
40+
- Configure CouchDB `chttpd_auth`:
41+
- Set `upgrade_hash_on_auth` to true
42+
- Set `iterations` to 1000
43+
44+
At this point, `couch-auth` will generate `_users` with sha/10 and couch db will upgrade them to sha256/1000. As CouchDB will update the `iterations` and `pbkdf2_prf` fields, `couch-auth` will be able to work with those hashes as well.
45+
46+
- Align configuration of `couch-auth`:
47+
- Set `pbkdf2Prf` to `sha256`
48+
- Set `iterations` to 1000
49+
50+
At this point, there is no need for auto upgrading anymore as the `_users` are already generated with the correct values.
51+
52+
### Steps (without auto upgrade)
53+
54+
- Decide on how many iterations you want. CouchDB defaults to 600,000 but that takes some time and `couch-auth` doesn't have caching, but the passwords are synthetic and short-lived. Let's use 1000 in this example.
55+
- Configure CouchDB `chttpd_auth`:
56+
- Set `upgrade_hash_on_auth` to false
57+
- Set `iterations` to 1000
58+
59+
At this point, `couch-auth` will generate `_users` with sha/10 and couch db will be able to work with them as well.
60+
61+
- Upgrade security in `couch-auth`:
62+
- Set `pbkdf2Prf` to `sha256`
63+
- Set `iterations` to 1000
64+
65+
At this point, any new `_users` are generated with the more secure hashes. Existing `_users` are still valid for both systems.
66+
67+
### Expected result
68+
69+
- As all `_users` are generated with `iterations` and `pbkdf2_prf` and both systems respect them when resolving the hash, no matter if you use auto upgrade or not and if both systems are configured to use the same iterations or not, the `_users` can be validated by both systems.
70+
71+
## Tests
72+
73+
| CouchDB | couch-auth | auto up | Works |
74+
| ----------- | ----------- | ------- | ----- |
75+
| CouchDB 3.3 | sha/10 | - | Yep |
76+
| CouchDB 3.5 | sha/10 | No | Yep |
77+
| CouchDB 3.5 | sha/10 | Yes | Yep |
78+
| CouchDB 3.5 | sha256/1000 | | Yep |

src/session-hashing.ts

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,22 @@ export class SessionHashing {
99

1010
// Hasher for hashing _users passwords
1111
private pwdCouch: pwdModule;
12+
private iterations: number;
13+
private pbkdf2Prf: string;
14+
private keyLength: number;
15+
private saltLength: number;
1216

1317
constructor(config: Partial<Config>) {
14-
const iterations = config.security?.sessionHashing?.iterations || 1000;
15-
const pbkdf2Prf = config.security?.sessionHashing?.pbkdf2Prf || 'sha256';
16-
const keyLength = config.security?.sessionHashing?.keyLength || 32;
17-
const saltLength = config.security?.sessionHashing?.saltLength || 16;
18+
this.iterations = config.security?.sessionHashing?.iterations || 1000;
19+
this.pbkdf2Prf = config.security?.sessionHashing?.pbkdf2Prf || 'sha256';
20+
this.keyLength = config.security?.sessionHashing?.keyLength || (this.pbkdf2Prf === 'sha' ? 20 : 32);
21+
this.saltLength = config.security?.sessionHashing?.saltLength || 16;
1822

19-
this.pwdCouch = new pwdModule(
20-
iterations,
21-
keyLength,
22-
saltLength,
23-
'hex',
24-
pbkdf2Prf
23+
this.pwdCouch = SessionHashing.createPwdModule(
24+
this.iterations,
25+
this.keyLength,
26+
this.saltLength,
27+
this.pbkdf2Prf
2528
);
2629
}
2730

@@ -36,8 +39,8 @@ export class SessionHashing {
3639
salt: salt,
3740
derived_key: hash,
3841
password_scheme: 'pbkdf2',
39-
pbkdf2_prf: this.pwdCouch.digest,
40-
iterations: this.pwdCouch.iterations
42+
pbkdf2_prf: this.pbkdf2Prf,
43+
iterations: this.iterations
4144
});
4245
});
4346
});
@@ -46,9 +49,9 @@ export class SessionHashing {
4649
public verifySessionPassword(hashObj: HashResult, pw: string): Promise<boolean> {
4750
return new Promise((resolve, reject) => {
4851
const iterations = hashObj.iterations || 10;
49-
const digest = hashObj.pbkdf2_prf || 'sha1';
50-
const length = digest === 'sha1' ? 20 : 32;
51-
const pwdCouch = new pwdModule(iterations, length, 16, 'hex', digest);
52+
const digest = hashObj.pbkdf2_prf || 'sha';
53+
const length = digest === 'sha' ? 20 : 32;
54+
const pwdCouch = SessionHashing.createPwdModule(iterations, length, 16, digest);
5255

5356
const salt = hashObj.salt;
5457
const derived_key = hashObj.derived_key;
@@ -63,4 +66,14 @@ export class SessionHashing {
6366
});
6467
});
6568
}
69+
70+
private static createPwdModule(iterations: number, keyLength: number, saltLength: number, digest: string): pwdModule {
71+
return new pwdModule(
72+
iterations,
73+
keyLength,
74+
saltLength,
75+
'hex',
76+
digest === 'sha' ? 'sha1' : 'sha256'
77+
);
78+
}
6679
}

src/types/config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ export interface SecurityConfig {
9494
*/
9595
iterations?: [number, number][];
9696
sessionHashing?: {
97-
/** Hashing algorithm for pbkdf2, either 'sha1' or 'sha256'. Default: 'sha256' */
98-
pbkdf2Prf?: 'sha1' | 'sha256';
97+
/** Hashing algorithm for pbkdf2, either 'sha' or 'sha256'. Default: 'sha256' */
98+
pbkdf2Prf?: 'sha' | 'sha256';
9999
/** Number of iterations for pbkdf2 password hashing. Default: 1000 */
100100
iterations?: number;
101101
/** Length of the derived key. Default: 32 */

src/types/typings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export interface HashResult {
6363
*/
6464
password_scheme?: string;
6565
/**
66-
* The pseudorandom function used for PBKDF2 hashing (e.g., 'sha1' or 'sha256')
66+
* The pseudorandom function used for PBKDF2 hashing (e.g., 'sha' or 'sha256')
6767
*/
6868
pbkdf2_prf?: string;
6969

0 commit comments

Comments
 (0)