Skip to content

Commit

Permalink
Add entropy tests
Browse files Browse the repository at this point in the history
  • Loading branch information
thomaschampagne committed Jan 25, 2024
1 parent eb99be3 commit 0bb635b
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 36 deletions.
49 changes: 46 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,62 @@ import crypto from 'crypto';
* @description Password generator used as demo for the purpose of this starter library
*/
export class Sesame {
private static readonly AVAILABLE_CHARS =
public static readonly AVAILABLE_CHARS =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*{}(),.;:/<>?|_-+=';

/**
* Create a password according to the length parameter
* Generate a password according to the length parameter
*
* @param length the length of the password to be created.
* @returns password created
*/
public static create(length: number): string {
public static generate(length: number): string {
if (!Number.isInteger(length) || length <= 0) {
throw new Error('Length must be a positive integer');
}

const availableChars = Sesame.AVAILABLE_CHARS;
const cryptoArray = new Uint32Array(length);
crypto.getRandomValues(cryptoArray);
return Array.from(cryptoArray, num => availableChars.charAt(num % availableChars.length)).join('');
}

/**
* This function calculates the entropy of a given password.
* Entropy is a measure of the unpredictability or randomness of a set of data.
* In the context of passwords, a higher entropy means a more secure password.
*
* @param password the password to calculate the entropy for.
* @returns the calculated entropy of the password.
*/
public static entropy(password: string): number {
// Define an array of regular expressions and their corresponding entropy values
const classes: Array<[RegExp, number]> = [
[/^[0-9]+$/, 3.322], // numeric
[/^[0-9A-F]+$/, 4.0], // hexadecimal
[/^([A-Z]+|[a-z]+)$/, 4.7], // alphabetic
[/^([A-Z0-9]+|[a-z0-9]+)$/, 5.17], // alphanumeric
[/^[A-Za-z]+$/, 5.7], // case-insensitive alphabetic
[/^[A-Za-z0-9]+$/, 5.954], // case-insensitive alphanumeric
[/^[a-z0-9!"#$%&'()*+,.\\/:;<=>?@\\[\] ^_`{|}~-]*$/i, 6.555], // case-insensitive alphanumeric and special characters
[/^[\p{Cc}\p{Cn}\p{Cs}]+$/gu, 7.768], // unicode control characters
[/^.+$/, 8.0] // all characters
];

// Function to calculate bits per symbol based on password characters
const bitsPerSymbol = (passwd: string): number => {
// Iterate over the classes array
for (const [pattern, bits] of classes) {
// If the password matches a regular expression, return the associated entropy value
if (pattern.test(passwd)) {
return bits;
}
}
// If no match is found, return 0
return 0;
};

// Return the total entropy by multiplying the bits per symbol by the length of the password
return bitsPerSymbol(password) * password.length;
}
}
102 changes: 69 additions & 33 deletions test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,80 @@
import { describe, expect, it } from 'vitest';
import { Sesame } from '../src';

describe('Sesame', () => {
it('should create and assert password length', () => {
// Given
const expectedLength = 64;
describe('Sesame ', () => {
describe('Create password ', () => {
it('should create and assert password length', () => {
// Given
const expectedLength = 64;

// When
const password = Sesame.create(expectedLength);
// When
const password = Sesame.generate(expectedLength);

// Then
expect(password.length).toEqual(expectedLength);
});
// Then
expect(password.length).toEqual(expectedLength);
});

it('should return a string of the specified length', () => {
// Given a specified length
const length = 10;
// When the password is created
const password = Sesame.create(length);
// Then the password should have the specified length
expect(password).to.have.lengthOf(length);
});
it('should return a string of the specified length', () => {
// Given a specified length
const length = 10;
// When the password is created
const password = Sesame.generate(length);
// Then the password should have the specified length
expect(password).to.have.length(length);
});

it('should only contain characters from the available characters', () => {
// Given a password of length 100
const password = Sesame.generate(100);
const availableChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*{}(),.;:/<>?| _-+=';
// When we check each character in the password
for (const char of password) {
// Then each character should be in the list of available characters
expect(availableChars).to.include(char);
}
});

it('should only contain characters from the available characters', () => {
// Given a password of length 100
const password = Sesame.create(100);
const availableChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*{}(),.;:/<>?|_-+=';
// When we check each character in the password
for (const char of password) {
// Then each character should be in the list of available characters
expect(availableChars).to.include(char);
}
it('should generate different passwords each time', () => {
// Given two passwords of equal length
const password1 = Sesame.generate(10);
const password2 = Sesame.generate(10);
// When we compare the two passwords
// Then they should not be equal
expect(password1).to.not.equal(password2);
});

// Additional test for handling invalid inputs
it('should throw an error for invalid length', () => {
// Given an invalid length
const invalidLength = -1;
// When we try to create a password with the invalid length
// Then it should throw an error
expect(() => Sesame.generate(invalidLength)).to.throw('Length must be a positive integer');
});
});

it('should generate different passwords each time', () => {
// Given two passwords of equal length
const password1 = Sesame.create(10);
const password2 = Sesame.create(10);
// When we compare the two passwords
// Then they should not be equal
expect(password1).to.not.equal(password2);
describe('Test passwords', () => {
it('should calculate password entropy', () => {
// Given
const password = Sesame.generate(32);
const expectedEntropy = 180;

// When
const entropy = Sesame.entropy(password);

// Then
expect(entropy).to.be.at.least(expectedEntropy);
});

it('should return zero entropy for an empty password', () => {
// Given
const password = '';

// When
const entropy = Sesame.entropy(password);

// Then
expect(entropy).toEqual(0);
});
});
});

0 comments on commit 0bb635b

Please sign in to comment.