Skip to content

Commit

Permalink
Remove legacy password hashing
Browse files Browse the repository at this point in the history
  • Loading branch information
LoicPoullain committed Aug 27, 2020
1 parent 7b58026 commit 2f11786
Show file tree
Hide file tree
Showing 5 changed files with 7 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ The `hashPassword` utility uses the [PBKDF2](https://en.wikipedia.org/wiki/PBKDF
| Name | Type | Default value |
| --- | --- | --- |
| plainTextPassword | string | |
| options | `{ legacy?: boolean }` | `{}` |

**Return type**

Expand All @@ -25,21 +24,17 @@ Promise<string>

The `verifyPassword` takes three arguments:
- the password to check in plain text,
- the password hash (usually fetched from the database),
- and an optional `options` object (see below).
- and the password hash (usually fetched from the database).

**Parameters**

| Name | Type | Default value |
| --- | --- | --- |
| plainTextPassword | string | |
| passwordHash | string | |
| options | `{ legacy?: boolean }` | `{}` |

**Return type**

```typescript
Promise<boolean>
```

> If you used the `parsePassword` function in previous versions of Foal (<0.7.0), you must pass the `legacy: true` option to `verifyPassword` and `hashPassword`.
18 changes: 0 additions & 18 deletions packages/core/src/common/utils/hash-password.util.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,4 @@ describe('hashPassword', () => {
strictEqual(derivedKey, expectedBuffer.toString('base64'));
});

it('should be able to salt and hash the plain password using the legacy way (old parsePassword util).', async () => {
const plainPassword = 'hello world';
const actual = await hashPassword(plainPassword, { legacy: true });

strictEqual(typeof actual, 'string');
const [ algorithm, iterations, salt, derivedKey] = actual.split('$');

strictEqual(algorithm, 'pbkdf2_sha256');
strictEqual(parseInt(iterations, 10), 100000);
strictEqual(salt.length, 32); // 16 * 2
strictEqual(derivedKey.length, 128); // 64 * 2

const expectedBuffer = await promisify(pbkdf2)(
plainPassword, salt, parseInt(iterations, 10), 64, 'sha256'
);
strictEqual(expectedBuffer.toString('hex'), derivedKey);
});

});
22 changes: 2 additions & 20 deletions packages/core/src/common/utils/hash-password.util.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,21 @@
import { pbkdf2, randomBytes } from 'crypto';
import { promisify } from 'util';

/**
* Legacy function to hash passwords. Only kept for backward compatibility.
* @param password
*/
async function parsePassword(password: string): Promise<string> {
const salt = (await promisify(randomBytes)(16)).toString('hex');
const iterations = 100000;
const keylen = 64;
const digest = 'sha256';
const derivedKey = await promisify(pbkdf2)(password, salt, iterations, keylen, digest);
return `pbkdf2_${digest}$${iterations}$${salt}$${derivedKey.toString('hex')}`;
}

/**
* Hash a password using the PBKDF2 algorithm.
*
* Configured to use PBKDF2 + HMAC + SHA256.
* The result is a 64 byte binary string (or hex if the legacy option is true).
* The result is a 64 byte binary string.
*
* The random salt is 16 bytes long.
* The number of iterations is 150000.
* The length key is 32 bytes long.
*
* @export
* @param {string} plainTextPassword - The password to hash.
* @param {{ legacy?: boolean }} [options={}]
* @returns {Promise<string>} The derived key with the algorithm name, the number of iterations and the salt.
*/
export async function hashPassword(plainTextPassword: string,
options: { legacy?: boolean } = {}): Promise<string> {
if (options.legacy) {
return parsePassword(plainTextPassword);
}
export async function hashPassword(plainTextPassword: string): Promise<string> {
const saltBuffer = await promisify(randomBytes)(16);
const iterations = 150000;
const keylen = 32;
Expand Down
9 changes: 0 additions & 9 deletions packages/core/src/common/utils/verify-password.util.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,4 @@ describe('verifyPassword', () => {
strictEqual(await verifyPassword('wrong password', passwordHash), false);
});

it('should verify password hashes created from hashPassword with the legacy'
+ ' option (old parsePassword).', async () => {
const plainPassword = 'hello world';
const passwordHash = await hashPassword(plainPassword, { legacy: true });

ok(await verifyPassword(plainPassword, passwordHash, { legacy: true }));
strictEqual(await verifyPassword('wrong password', passwordHash, { legacy: true }), false);
});

});
11 changes: 4 additions & 7 deletions packages/core/src/common/utils/verify-password.util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,9 @@ import { promisify } from 'util';
* @export
* @param {string} plainTextPassword - The password in clear text.
* @param {string} passwordHash - The password hash generated by the `hashPassword` function.
* @param {{ legacy?: boolean }} [options={}]
* @returns {Promise<boolean>} True if the hash and the password match. False otherwise.
*/
export async function verifyPassword(plainTextPassword: string, passwordHash: string,
options: { legacy?: boolean } = {}): Promise<boolean> {
const legacy = options.legacy || false;
export async function verifyPassword(plainTextPassword: string, passwordHash: string): Promise<boolean> {
const [ algorithm, iterations, salt, derivedKey ] = passwordHash.split('$');

strictEqual(algorithm, 'pbkdf2_sha256', 'Invalid algorithm.');
Expand All @@ -23,12 +20,12 @@ export async function verifyPassword(plainTextPassword: string, passwordHash: st
strictEqual(typeof derivedKey, 'string', 'Invalid password format.');
strictEqual(isNaN(parseInt(iterations, 10)), false, 'Invalid password format.');

const saltBuffer = Buffer.from(salt, legacy ? 'hex' : 'base64');
const derivedKeyBuffer = Buffer.from(derivedKey, legacy ? 'hex' : 'base64');
const saltBuffer = Buffer.from(salt, 'base64');
const derivedKeyBuffer = Buffer.from(derivedKey, 'base64');
const digest = 'sha256'; // TODO: depends on the algorthim var
const password = await promisify(pbkdf2)(
plainTextPassword,
legacy ? saltBuffer.toString('hex') : saltBuffer,
saltBuffer,
parseInt(iterations, 10),
derivedKeyBuffer.length,
digest
Expand Down

0 comments on commit 2f11786

Please sign in to comment.