Import pwd hashing and verification
ci/woodpecker/manual/test Pipeline was successful

This commit is contained in:
2024-10-15 21:48:56 +02:00
parent 17c0b1b414
commit dbba961dbf
3 changed files with 154 additions and 0 deletions
+2
View File
@@ -4,6 +4,7 @@ import PrivateBox from './src/private-box'
import PrivateWrap from './src/private-wrap'
import PwdBox from './src/pwd-box'
import PwdWrap from './src/pwd-wrap'
import {hash, verify} from './src/pwd'
export enum Strength {
weak,
@@ -22,5 +23,6 @@ export * as kdf from './src/kdf'
export * as misc from './src/misc'
export * as signature from './src/signature'
export * as JWT from './src/jwt'
export const pwd = {hash, verify}
export {SecretBox, SecretWrap, PrivateBox, PrivateWrap, PwdBox, PwdWrap}
+73
View File
@@ -0,0 +1,73 @@
import logger from 'log'
import * as misc from 'misc'
import {pbkdf} from './kdf'
const log = logger('libcrypto:pwd')
type Salt = Uint8Array
type Hash = Uint8Array
export async function hash(password: string) : Promise<string> {
log.debug('hash password')
const salt = crypto.getRandomValues(new Uint8Array(16))
const h = await pbkdf(salt, password)
return encode(salt, h!)
}
export async function verify(password: string, hash: string) : Promise<boolean> {
log.debug("verify password's hash")
const params = decode(hash)
if (params === null) return false
const h = await pbkdf(params[0], password)
return compare(h!, params[1])
}
export function encode(salt: Salt, hash: Hash) : string {
const s = salt_encode(salt)
const h = hash_encode(hash)
return `${s}.${h}`
}
export function decode(hash: string) : [Salt, Hash] | null {
const parts = hash.split('.')
if (parts.length !== 2) {
log.warn('Invalid part count for password hash :', parts.length)
return null
}
const salt = salt_decode(parts[0])
if (salt === null) return null
const h = hash_decode(parts[1])
if (h === null) return null
return [salt, h]
}
export function salt_encode(salt: Salt) : string {
return misc.a2b64(salt)
}
export function salt_decode(salt: string) : Salt | null {
return misc.b642a(salt)
}
export function hash_encode(hash: Hash) : string {
return misc.a2b64(new Uint8Array(hash))
}
export function hash_decode(hash: string) : Hash | null {
return misc.b642a(hash)
}
export function compare(a: Hash, b: Hash) : boolean {
const a1 = new Uint8Array(a)
const b1 = new Uint8Array(b)
let result = a1.length === b1.length
for (let i = 0;i < Math.max(a1.length, b1.length); ++i) {
const a2 = a1[i] ?? 'a'
const b2 = b1[i] ?? 'b'
const test = a2 === b2
result = result && test
}
return result
}
+79
View File
@@ -0,0 +1,79 @@
import {beforeAll, describe, test, expect} from 'bun:test'
import {Level, options, writers} from 'log'
import {Console} from 'logger-console'
import * as pwd from '../src/pwd'
const console = new Console({
minLevel: Level.DEBUG,
with_color: true,
})
beforeAll(() => {
options.verbose = false
writers.set('console', console)
})
test('base case', async () => {
const password = "AwesomePassword123!"
const hash = await pwd.hash(password)
const verification = await pwd.verify(password, hash)
expect(verification).toBeTrue()
})
test('wrong password', async () => {
const password1 = "AwesomePassword123!"
const password2 = "AwesomePassword321!"
expect(password1).not.toEqual(password2)
const hash = await pwd.hash(password1)
const verification = await pwd.verify(password2, hash)
expect(verification).toBeFalse()
})
test("Empty password isn't a trick", async () => {
const p1 = ""
const p2 = "abc"
const hash = await pwd.hash(p1)
const verification = await pwd.verify(p2, hash)
expect(verification).toBeFalse()
const h2 = await pwd.hash(p2)
const verification2 = await pwd.verify(p1, h2)
expect(verification2).toBeFalse()
})
test('salt changes', async () => {
const password = "AwesomePassword123!"
const hash1 = await pwd.hash(password)
const hash2 = await pwd.hash(password)
expect(hash1).not.toEqual(hash2)
})
test('tampered hash', async () => {
const password = "AwesomePassword123"
const hash = await pwd.hash(password)
const tamperedHash = hash
.replace('a', 'b')
.replace('c', 'd')
.replace('e', 'f')
expect(tamperedHash).not.toEqual(hash)
const verification = await pwd.verify(password, tamperedHash)
expect(verification).toBeFalse()
})
describe('Serialization', () => {
describe('Hash', () => {
test('base case', () => {
const h = new Uint8Array(16)
const ser = pwd.hash_encode(h)
const de = pwd.hash_decode(ser)
expect(de).toEqual(h)
})
})
describe('Salt', () => {
const salt = new Uint8Array(16)
const ser = pwd.salt_encode(salt)
const de = pwd.salt_decode(ser)
expect(de).toEqual(salt)
})
})