Instructions
Requirements and Specifications
Source Code
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.io.Console;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.*;
import java.util.function.Predicate;
public class LoginSystem {
private static final int DEFAULT_LOGIN_ATTEMPTS = 3;
private static final String LOGIN_DATA_FILENAME = "data.txt";
private final Map data;
private final Collection rules;
private int loginAttemptsLeft;
private String salt;
public LoginSystem(Collection rules, int loginAttempts) {
this.rules = rules;
loginAttemptsLeft = loginAttempts;
data = new HashMap<>();
try (Scanner scanner = new Scanner(new File(LOGIN_DATA_FILENAME))) {
salt = scanner.nextLine().trim();
while (scanner.hasNextLine()) {
String[] parts = scanner.nextLine().trim().split("\\s+");
if (parts.length != 2) {
continue;
}
data.put(parts[0], parts[1]);
}
} catch (IOException e) {
System.out.println("Can not open data file.");
salt = EncryptionUtils.generateSalt();
}
}
public LoginSystem(Collection rules) {
this(rules, DEFAULT_LOGIN_ATTEMPTS);
}
public LoginSystem(int loginAttempts) {
this(Collections.emptyList(), loginAttempts);
}
public LoginSystem() {
this(Collections.emptyList(), DEFAULT_LOGIN_ATTEMPTS);
}
public boolean isBlocked() {
return loginAttemptsLeft == 0;
}
public boolean tryLogin(String login, String password) {
if (isBlocked()) {
return false;
}
boolean isOK = true;
String loginEnc = EncryptionUtils.getHashed(login, salt);
if (!data.containsKey(loginEnc)) {
isOK = false;
} else {
String passwordEnc = EncryptionUtils.getHashed(password, salt);
isOK = passwordEnc.equals(data.get(loginEnc));
}
if (!isOK) {
loginAttemptsLeft--;
}
return isOK;
}
public boolean signUp(String login, String password, String repeatPassword) {
if (!checkPassword(password)) {
System.out.println("Password does not satisfy security requiements");
return false;
}
if (!password.equals(repeatPassword)) {
System.out.println("Passwords do not match");
return false;
}
String loginEnc = EncryptionUtils.getHashed(login, salt);
if (data.containsKey(loginEnc)) {
System.out.println("User with such login already exists");
return false;
}
String passwordEnc = EncryptionUtils.getHashed(password, salt);
data.put(loginEnc, passwordEnc);
save();
return true;
}
private void save() {
try (PrintWriter printWriter = new PrintWriter(LOGIN_DATA_FILENAME)) {
printWriter.println(salt);
for (Map.Entry entry : data.entrySet()) {
printWriter.println(entry.getKey() + " " + entry.getValue());
}
} catch (IOException e) {
System.out.println("Can not open data file for writing");
}
}
public boolean checkPassword(String password) {
return rules.stream().allMatch(r -> r.check(password));
}
private static class EncryptionUtils {
private static final int SALT_LENGTH = 32;
private static final int KEY_LENGTH = 256;
private static final String SALT_SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static String generateSalt() {
StringBuilder builder = new StringBuilder();
Random random = new Random();
for (int i = 0; i < SALT_LENGTH; i++) {
builder.append(SALT_SYMBOLS.charAt(random.nextInt(SALT_SYMBOLS.length())));
}
return builder.toString();
}
public static String getHashed(String s, String salt) {
char[] cArray = s.toCharArray();
PBEKeySpec spec = new PBEKeySpec(cArray, salt.getBytes(), 1000, KEY_LENGTH);
Arrays.fill(cArray, Character.MIN_VALUE);
try {
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
return Base64.getEncoder().encodeToString(skf.generateSecret(spec).getEncoded());
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new IllegalArgumentException("Error while hashing a string: " + e.getMessage(), e);
} finally {
spec.clearPassword();
}
}
}
private interface PasswordRule {
boolean check(String password);
}
private static class LengthRule implements PasswordRule {
private final int minLength;
public LengthRule(int minLength) {
this.minLength = minLength;
}
@Override
public boolean check(String password) {
return password.length() >= minLength;
}
}
private static class CharactersNeededRule implements PasswordRule {
private final Predicate test;
public CharactersNeededRule(Predicate test) {
this.test = test;
}
public CharactersNeededRule(Collection chars) {
this(chars::contains);
}
@Override
public boolean check(String password) {
return password.chars().mapToObj(c -> (char) c).anyMatch(test);
}
}
public static class UpperCaseNeededRule extends CharactersNeededRule {
public UpperCaseNeededRule() {
super(c -> Character.isAlphabetic(c) && Character.isUpperCase(c));
}
}
public static class LowerCaseNeededRule extends CharactersNeededRule {
public LowerCaseNeededRule() {
super(c -> Character.isAlphabetic(c) && Character.isLowerCase(c));
}
}
public static class SpecialCharactersNeededRule extends CharactersNeededRule {
public SpecialCharactersNeededRule() {
super(Arrays.asList('!', '@', '#', '?'));
}
}
public static void main(String[] args) {
List rules = new ArrayList<>();
rules.add(new LengthRule(8));
rules.add(new UpperCaseNeededRule());
rules.add(new LowerCaseNeededRule());
rules.add(new SpecialCharactersNeededRule());
LoginSystem loginSystem = new LoginSystem(rules, 5);
Console scanner = System.console();
System.out.print("Do you want to sign up new user? (y/n): ");
boolean signUpNeeded = scanner.readLine().trim().equalsIgnoreCase("y");
while (signUpNeeded) {
System.out.print("Enter login: ");
String login = scanner.readLine();
System.out.print("Enter password: ");
String password = new String(scanner.readPassword());
System.out.print("Enter repeat password: ");
String repeatPassword = new String(scanner.readPassword());
if (loginSystem.signUp(login, password, repeatPassword)) {
System.out.println("User created successfully");
}
System.out.print("Do you want to sign up new user? (y/n): ");
signUpNeeded = scanner.readLine().trim().equalsIgnoreCase("y");
}
System.out.println("Trying to log in:");
boolean success = false;
while (!loginSystem.isBlocked() && !success) {
System.out.print("Login: ");
String login = scanner.readLine().trim();
System.out.print("Password: ");
String password = new String(scanner.readPassword());
if (loginSystem.tryLogin(login, password)) {
success = true;
} else {
System.out.println("Wrong login/password");
}
System.out.println();
}
if (success) {
System.out.println("You were successfully logged in");
} else {
System.out.println("You have no more attempts to log in. FAIL");
}
}
}