Skip to content

Commit

Permalink
Merge pull request #34 from cathrkri/feature/password-hashing
Browse files Browse the repository at this point in the history
Feature/password hashing
  • Loading branch information
robinsp authored Mar 3, 2026
2 parents 67aecfc + 1bb6914 commit db8bb74
Show file tree
Hide file tree
Showing 39 changed files with 524 additions and 97 deletions.
6 changes: 6 additions & 0 deletions helpmehelpapplication/helpmehelpapplication.iml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="CheckStyle-IDEA-Module" serialisationVersion="2">
<option name="activeLocationsIds" />
</component>
</module>
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package ntnu.sytemutvikling.team6.models;

import ntnu.sytemutvikling.team6.models.user.User;

import java.time.LocalDateTime;
import java.util.UUID;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package ntnu.sytemutvikling.team6.models;

import ntnu.sytemutvikling.team6.models.user.User;

import java.time.LocalDateTime;
import java.util.UUID;

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ntnu.sytemutvikling.team6.models;
package ntnu.sytemutvikling.team6.models.user;

import java.util.*;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ntnu.sytemutvikling.team6.models;
package ntnu.sytemutvikling.team6.models.user;

/**
* Supported application languages.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ntnu.sytemutvikling.team6.models;
package ntnu.sytemutvikling.team6.models.user;

import java.time.LocalDateTime;
import java.util.UUID;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ntnu.sytemutvikling.team6.models;
package ntnu.sytemutvikling.team6.models.user;

/**
* Available users
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package ntnu.sytemutvikling.team6.models;
package ntnu.sytemutvikling.team6.models.user;

// Mangler Enhetstesting

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package ntnu.sytemutvikling.team6.models.user;

import ntnu.sytemutvikling.team6.security.PasswordHasher;

import java.util.UUID;

// TODO: Enhetstesting mangler

/**
* Represents a user.
*
* @author Robin Strand Prestmo
*/
public class User {
private static final PasswordHasher passwordHasher = new PasswordHasher();

private final UUID id;
private String name;
private String email;
private String passwordHash;
private final Role role;
private final Settings settings;
private final Inbox inbox;

/**
* Creates a new user
*
* @param id gives the user a unique identifier with UUID
* @param name the name of the user
* @param email the email of the user
* @param password the password for the user
* @param role users role
* @param settings the user´s settings
* @param inbox the user´s inbox
*
*/
public User(UUID id,
String name,
String email,
String password,
Role role,
Settings settings,
Inbox inbox) {
if (id == null) {
throw new IllegalArgumentException("ID cannot be null.");
}

if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name cannot be null or blank.");
}

if (email == null || email.isBlank() || !email.contains("@") || !email.contains(".")) {
throw new IllegalArgumentException("Email cannot be null or blank, and must contain '@' and '.'");
}

if (role == null) {
throw new IllegalArgumentException("Role cannot be null");
}

if (settings == null) {
throw new IllegalArgumentException("Settings cannot be null");
}

if (inbox == null) {
throw new IllegalArgumentException("Inbox cannot be null");
}

this.id = id;
this.name = name;
this.email = email;
this.passwordHash = passwordHasher.getHashPassword(password);
this.role = role;
this.settings = settings;
this.inbox = inbox;
}

// Add Getters

public UUID getId() {
return id;
}

public String getName() {
return name;
}

public String getEmail() {
return email;
}

public Role getRole() {
return role;
}

public Settings getSettings() {
return settings;
}

public Inbox getInbox() {
return inbox;
}

// Add Setters

public void setName(String name) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("Name cannot be null or blank.");
}
this.name = name;
}

public void setPassword(String password) {
this.passwordHash = passwordHasher.getHashPassword(password);
}

public void setEmail(String email) {
if (email == null || email.isBlank() || !email.contains("@") || !email.contains(".")) {
throw new IllegalArgumentException("Email cannot be null or blank, and must contains '@' and '.'");
}
this.email = email;
}

// Other methods

public boolean checkPassword(String password) {
return passwordHasher.isValidPassword(password, passwordHash);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package ntnu.sytemutvikling.team6.security;

import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Base64;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;

/**
* A utility for hashing and verifying passwords using PBKDF2.
*
* <p>The generated hash contains both a random salt and the hashed password,
* encoded as Base64 string.
* </p>
*
* @author Robin Strand Prestmo
*/
public final class PasswordHasher {
private static final SecureRandom RNG = new SecureRandom();

/**
* Hashes a password using PBKDF2 and a random salt.
*
* @param password the password to hash.
* @return a Base64 string containing the salt and the hashed password.
* @throws IllegalArgumentException if the password is null or blank.
*/
public String getHashPassword(String password) {
if (password == null || password.isBlank()) {
throw new IllegalArgumentException("Password cannot be null or blank.");
}

String hashPass = "";

try {
// 1. Create salt
byte[] salt = new byte[16];
RNG.nextBytes(salt);

// 2. Create PBKDF2 Hash value
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 100000, 32 * 8);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] hash = factory.generateSecret(spec).getEncoded();

// 3. Combine salt and password bytes
byte[] hashBytes = new byte[48];
System.arraycopy(salt, 0, hashBytes, 0, 16);
System.arraycopy(hash, 0, hashBytes, 16, 32);

// 4. Turn the combined salt+hash into a string.
hashPass = Base64.getEncoder().encodeToString(hashBytes);
} catch (Exception e) {
throw new RuntimeException("Error while hashing password.", e);
}
return hashPass;
}

/**
* Checks if the password matches a perviously stored hash.
*
* @param password The password the user types.
* @param hashPass Is the stored hashed password
* @return True if password is valid, otherwise false.
*/
public boolean isValidPassword(String password, String hashPass) {
if (password == null || password.isBlank()) {
return false;
}

try {
// Extract the bytes
byte[] hashBytes = Base64.getDecoder().decode(hashPass);

// Get salt
byte[] salt = new byte[16];
System.arraycopy(hashBytes, 0, salt, 0, 16);

// Compute the hash on the password the user entered
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 100000, 32 * 8);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] hash = factory.generateSecret(spec).getEncoded();

// Compare results
byte[] storedHash = new byte[32];
System.arraycopy(hashBytes, 16, storedHash, 0, 32);

return MessageDigest.isEqual(storedHash, hash);

} catch (Exception e) {
throw new RuntimeException("Error while validating password.", e);
}
}
}
Loading

0 comments on commit db8bb74

Please sign in to comment.