Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ export declare function aesGcmSivNonce(): Array<number>

export declare function aesNonce(): Array<number>

export declare function argon2DeriveAes128Key(password: Array<number>): Array<number>

export declare function argon2DeriveAes256Key(password: Array<number>): Array<number>

export declare function argon2Hash(password: string): string

export declare function argon2HashParams(memoryCost: number, iterations: number, parallelism: number, password: string): string
Expand Down
106 changes: 54 additions & 52 deletions index.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cas-typescript-sdk",
"version": "1.1.000",
"version": "1.1.001",
"description": "",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
Expand Down
32 changes: 31 additions & 1 deletion src-ts/password-hashers/argon2-wrapper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

import { argon2Hash, argon2HashParams, argon2Verify} from "./../../index";
import { argon2DeriveAes128Key, argon2DeriveAes256Key, argon2Hash, argon2HashParams, argon2Verify} from "./../../index";
import { IPasswordHasherBase } from "./password-hasher-base";

export class Argon2Wrapper implements IPasswordHasherBase {
Expand Down Expand Up @@ -33,6 +33,36 @@ export class Argon2Wrapper implements IPasswordHasherBase {
return argon2HashParams(memoryCost, timeCost, parallelism, password);
}

/**
* Derives a 16 byte AES-128 key from a password with Argon2id.
* A random salt is generated internally and discarded, so the key cannot be
* re-derived from the password — each call produces a fresh one-time key.
* Use Pbkdf2Wrapper.deriveWithSalt if you need a re-derivable key.
* @param password
* @returns Array<number>
*/
public deriveAes128Key(password: Array<number>): Array<number> {
if (!password || password.length === 0) {
throw new Error("You must provide a password to derive an AES-128 key with Argon2");
}
return argon2DeriveAes128Key(password);
}

/**
* Derives a 32 byte AES-256 key from a password with Argon2id.
* A random salt is generated internally and discarded, so the key cannot be
* re-derived from the password — each call produces a fresh one-time key.
* Use Pbkdf2Wrapper.deriveWithSalt if you need a re-derivable key.
* @param password
* @returns Array<number>
*/
public deriveAes256Key(password: Array<number>): Array<number> {
if (!password || password.length === 0) {
throw new Error("You must provide a password to derive an AES-256 key with Argon2");
}
return argon2DeriveAes256Key(password);
}

/**
* Verifies that a password is the same as the hashed password with Argon2.
* @param hashedPassword
Expand Down
11 changes: 10 additions & 1 deletion src-ts/password-hashers/password-hasher-factory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Argon2Wrapper } from "./argon2-wrapper";
import { BCryptWrapper } from "./bcrypt-wrapper";
import { IPasswordHasherBase } from "./password-hasher-base";
import { PasswordHasherType } from "./password-hasher-type";
import { ScryptWrapper } from "./scrypt-wrapper";

Expand All @@ -11,15 +12,23 @@ export class PasswordHasherFactory {
*/
static getHasher(type: PasswordHasherType): any {
// Argon2 by default
let hasher = new Argon2Wrapper();
let hasher: IPasswordHasherBase | null = null;
switch (type) {
case PasswordHasherType.Bcrypt:
hasher = new BCryptWrapper();
break;
case PasswordHasherType.Scrypt:
hasher = new ScryptWrapper();
break;
case PasswordHasherType.Argon2:
hasher = new Argon2Wrapper();
break;
}

if (!hasher) {
throw new Error(`Password hasher type ${type} is not supported.`);
}

return hasher;
}
}
40 changes: 40 additions & 0 deletions src/password_hashers/argon2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ pub fn argon2_hash_params(memory_cost: u32, iterations: u32, parallelism: u32, p
crate::map_cas_err(CASArgon::hash_password_parameters(memory_cost, iterations, parallelism, password))
}

#[napi]
pub fn argon2_derive_aes_128_key(password: Vec<u8>) -> napi::Result<Vec<u8>> {
crate::map_cas_err(CASArgon::derive_aes_128_key(password))
}

#[napi]
pub fn argon2_derive_aes_256_key(password: Vec<u8>) -> napi::Result<Vec<u8>> {
crate::map_cas_err(CASArgon::derive_aes_256_key(password))
}

#[test]
pub fn argon2_hash_params_test() {
let password = "ThisIsNotMyPasswolrd".to_string();
Expand All @@ -39,6 +49,36 @@ pub fn argon2_verify_test() {
assert_eq!(true, verified);
}

#[test]
pub fn argon2_derive_aes_128_key_test() {
let password = b"ThisIsNotMyPasswolrd".to_vec();
let key = argon2_derive_aes_128_key(password.clone()).unwrap();
assert_eq!(key.len(), 16);
// A random salt is generated per call, so the same password must not repeat a key.
let second_key = argon2_derive_aes_128_key(password).unwrap();
assert_ne!(key, second_key);
}

#[test]
pub fn argon2_derive_aes_256_key_test() {
let password = b"ThisIsNotMyPasswolrd".to_vec();
let key = argon2_derive_aes_256_key(password.clone()).unwrap();
assert_eq!(key.len(), 32);
let second_key = argon2_derive_aes_256_key(password).unwrap();
assert_ne!(key, second_key);
}

#[test]
pub fn argon2_derive_aes_256_key_encrypt_decrypt_test() {
use crate::symmetric::aes::{aes256_decrypt, aes256_encrypt, aes_nonce};
let key = argon2_derive_aes_256_key(b"ThisIsNotMyPasswolrd".to_vec()).unwrap();
let nonce = aes_nonce();
let plaintext = b"WelcomeHome".to_vec();
let ciphertext = aes256_encrypt(key.clone(), nonce.clone(), plaintext.clone()).unwrap();
let decrypted_plaintext = aes256_decrypt(key, nonce, ciphertext).unwrap();
assert_eq!(decrypted_plaintext, plaintext)
}

#[test]
pub fn argon2_verify_fail_test() {
let password = "ThisIsNotMyPasswolrd".to_string();
Expand Down
32 changes: 32 additions & 0 deletions tests/password-hasher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
PasswordHasherFactory,
PasswordHasherType,
} from "../src-ts/password-hashers";
import { AESWrapper } from "../src-ts/symmetric";

test.describe("Bcrypt Tests", () => {

Expand Down Expand Up @@ -122,4 +123,35 @@ test.describe("Argon2 Tests", () => {
const hashed: string = hasher.hashPasswordWithParameters(password, 1024, 3, 1);
expect(hashed).not.toBe(password);
});

test("derive aes 128 key", () => {
const hasher: Argon2Wrapper = new Argon2Wrapper();
const password: number[] = Array.from(new TextEncoder().encode("Argon2Rocks"));
const key: number[] = hasher.deriveAes128Key(password);
expect(key.length).toBe(16);
// A random salt is generated per call, so the same password must not repeat a key.
const secondKey: number[] = hasher.deriveAes128Key(password);
expect(secondKey).not.toEqual(key);
});

test("derive aes 256 key", () => {
const hasher: Argon2Wrapper = new Argon2Wrapper();
const password: number[] = Array.from(new TextEncoder().encode("Argon2Rocks"));
const key: number[] = hasher.deriveAes256Key(password);
expect(key.length).toBe(32);
const secondKey: number[] = hasher.deriveAes256Key(password);
expect(secondKey).not.toEqual(key);
});

test("derive aes 256 key encrypt and decrypt round trip", () => {
const hasher: Argon2Wrapper = new Argon2Wrapper();
const aes: AESWrapper = new AESWrapper();
const password: number[] = Array.from(new TextEncoder().encode("Argon2Rocks"));
const key: number[] = hasher.deriveAes256Key(password);
const nonce: number[] = aes.generateAESNonce();
const plaintext: number[] = Array.from(new TextEncoder().encode("WelcomeHome"));
const ciphertext: number[] = aes.aes256Encrypt(key, nonce, plaintext);
const decrypted: number[] = aes.aes256Decrypt(key, nonce, ciphertext);
expect(decrypted).toEqual(plaintext);
});
});
Loading