Skip to content

Commit

Permalink
fix (hash): use phc-pbkdf2 instead of custom algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
maxgalbu committed Nov 15, 2021
1 parent c43bd07 commit 85ea224
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 73 deletions.
4 changes: 2 additions & 2 deletions adonis-typings/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ declare module '@ioc:Adonis/Core/Hash' {
export type Pbkdf2Config = {
driver: 'pbkdf2'
iterations: number
saltLength: number
keyLength: number
saltSize: number
digest: "sha1" | "sha256" | "sha512",
}

/**
Expand Down
89 changes: 46 additions & 43 deletions instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import { parse as parseEditorConfig } from 'editorconfig'

type InstructionsState = {
iterations: number
saltLength: number
keyLength: number
saltSize: number
digest: 'sha1' | 'sha256' | 'sha512'
}

/**
Expand Down Expand Up @@ -72,21 +72,21 @@ async function editContract(
const hashContractFile = project.getSourceFileOrThrow(contractPath)

//Doesn't work without single quotes wrapping the module name
const hashModule = hashContractFile?.getModuleOrThrow("'@ioc:Adonis/Core/Hash'");
const hashModule = hashContractFile?.getModuleOrThrow("'@ioc:Adonis/Core/Hash'")

const hashersInterface = hashModule.getInterfaceOrThrow('HashersList');
const hashersInterface = hashModule.getInterfaceOrThrow('HashersList')

//Remove pbkdf2 hasher, if already present
hashersInterface.getProperty("pbkdf2")?.remove();
hashersInterface.getProperty('pbkdf2')?.remove()

//Insert pbkdf2 hasher in last position
hashersInterface.addProperty({
name: "pbkdf2",
name: 'pbkdf2',
type: `{
implementation: Pbkdf2Contract,
config: Pbkdf2Config,
}`,
});
})

hashContractFile.formatText()
await hashContractFile?.save()
Expand Down Expand Up @@ -127,8 +127,8 @@ async function editConfig(
initializer: Writers.object({
driver: '"pbkdf2"',
iterations: `${state.iterations}`,
saltLength: `${state.saltLength}`,
keyLength: `${state.keyLength}`,
saltSize: `${state.saltSize}`,
digest: `"${state.digest}"`,
}),
})

Expand All @@ -145,45 +145,48 @@ async function getIterations(sink: typeof sinkStatic, state: InstructionsState):
return sink
.getPrompt()
.ask(
'Enter the number of iterations you want to do (equal or more than 10)',
`Enter the number of iterations you want to do (should be equal or more than ${state.iterations})`,
{
default: state.iterations.toString(),
validate(value) {
const num = parseInt(value);
return !isNaN(num) && num >= 10;
const num = parseInt(value)
return !isNaN(num);
},
}
)
}

async function getSaltLength(sink: typeof sinkStatic, state: InstructionsState): Promise<number> {
return sink
.getPrompt()
.ask(
'Enter the length of the generated salt (more than 16 bytes)',
{
default: state.saltLength.toString(),
validate(value) {
const num = parseInt(value);
return !isNaN(num) && num >= 16;
},
}
)
async function getSaltSize(sink: typeof sinkStatic, state: InstructionsState): Promise<number> {
return sink.getPrompt().ask('Enter the size of the generated salt (more than 16 bytes)', {
default: state.saltSize.toString(),
validate(value) {
const num = parseInt(value)
return !isNaN(num) && num >= state.saltSize
},
})
}

async function getKeyLength(sink: typeof sinkStatic, state: InstructionsState): Promise<number> {
return sink
.getPrompt()
.ask(
'Enter length of the key (more than 256 bytes)',
async function getDigest(
sink: typeof sinkStatic,
state: InstructionsState
): Promise<'sha1' | 'sha256' | 'sha512'> {
return sink.getPrompt().choice(
'Choose the digest algorithm to be used',
[
{
default: state.keyLength.toString(),
validate(value) {
const num = parseInt(value);
return !isNaN(num) && num >= 256;
},
}
)
name: 'sha1',
},
{
name: 'sha256',
},
{
name: 'sha512',
},
],
{
default: state.digest,
}
)
}

/**
Expand All @@ -195,14 +198,14 @@ export default async function instructions(
sink: typeof sinkStatic
) {
const state: InstructionsState = {
iterations: 10,
saltLength: 32,
keyLength: 256,
iterations: 25000,
saltSize: 32,
digest: 'sha256',
}

state.iterations = await getIterations(sink, state);
state.saltLength = await getSaltLength(sink, state);
state.keyLength = await getKeyLength(sink, state);
state.iterations = await getIterations(sink, state)
state.saltSize = await getSaltSize(sink, state)
state.digest = await getDigest(sink, state)

/**
* Make contract file
Expand Down
35 changes: 10 additions & 25 deletions lib/Hashers/Pbkdf2Hasher.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Pbkdf2Config, Pbkdf2Contract } from '@ioc:Adonis/Core/Hash'
import crypto from 'crypto'
import pbkdf2 from '@phc/pbkdf2'

export class Pbkdf2Hasher implements Pbkdf2Contract {
public ids: Pbkdf2Contract['ids'] = ['pbkdf2']
Expand All @@ -11,38 +11,23 @@ export class Pbkdf2Hasher implements Pbkdf2Contract {
*/
public make(value: string): Promise<string> {
return new Promise((resolve, _reject) => {
const salt = crypto.randomBytes(this.config.saltLength).toString('base64')
const hashedSalt = crypto
.pbkdf2Sync(value, salt, this.config.iterations, this.config.keyLength, 'sha512')
.toString('base64')

resolve(`${this.config.iterations}:${salt}:${hashedSalt}`)
resolve(
pbkdf2.hash(value, {
iterations: this.config.iterations,
saltSize: this.config.saltSize,
digest: this.config.digest,
})
)
})
}

/**
* Verify an existing hash with the plain value.
*/
public verify(value: string, hash: string): Promise<boolean> {
return new Promise((resolve, reject) => {
const [iterations, salt, hashedPassword] = hash.split(':')

if (!hashedPassword) {
return reject(new Error('Invalid hashed password found'))
}
if (!salt) {
return reject(new Error('Invalid salt found'))
}
if (!iterations) {
return reject(new Error('Invalid iterations found'))
}

const iterationsNum = parseInt(iterations)
return new Promise((resolve, _reject) => {
resolve(
hashedPassword ===
crypto
.pbkdf2Sync(value, salt, iterationsNum, this.config.keyLength, 'sha512')
.toString('base64')
pbkdf2.verify(hash, value)
)
})
}
Expand Down
83 changes: 80 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,5 +65,8 @@
"providers": [
"adonis5-pbkdf2"
]
},
"dependencies": {
"@phc/pbkdf2": "^1.1.14"
}
}

0 comments on commit 85ea224

Please sign in to comment.