diff --git a/build.gradle b/build.gradle index acba92c..892b267 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ plugins { id 'java' + id 'com.gradleup.shadow' version '9.0.0-beta7' } group = 'me.zacharias' @@ -10,8 +11,16 @@ repositories { } dependencies { + implementation 'com.google.code.gson:gson:2.12.1' + implementation("org.jetbrains:annotations:23.1.0") } test { useJUnitPlatform() +} + +jar{ + manifest { + attributes 'Main-class': 'me.zacharias.bank.Main' + } } \ No newline at end of file diff --git a/src/main/java/me/zacharias/bank/Account.java b/src/main/java/me/zacharias/bank/Account.java index b73f6c8..89d0e17 100644 --- a/src/main/java/me/zacharias/bank/Account.java +++ b/src/main/java/me/zacharias/bank/Account.java @@ -2,21 +2,25 @@ package me.zacharias.bank; import me.zacharias.bank.transaction.Transaction; import me.zacharias.bank.transaction.TransactionType; +import org.intellij.lang.annotations.MagicConstant; import java.util.ArrayList; import java.util.UUID; -public abstract class Account { +public class Account { ArrayList transactions; String name; double balance; UUID id; + @MagicConstant(valuesFromClass = Interests.class) + double interestRate; - public Account(String name, int balance) { + public Account(String name, double balance, @MagicConstant(valuesFromClass = Interests.class) double interestRate) { this.name = name; this.balance = balance; transactions = new ArrayList<>(); id = UUID.randomUUID(); + this.interestRate = interestRate; } public void WithdrawTransaction(double amount, String description, UUID destination) { @@ -85,9 +89,13 @@ public abstract class Account { return transactions; } - abstract double getInterestRate(); - public double getInterest() { - return balance * (1+getInterestRate()); + return balance * (1+interestRate); + } + + public static class Interests + { + public static final double BANK_ACCOUNT_INTEREST_RATE = 0.001; + public static final double SAVINGS_ACCOUNT_INTEREST_RATE = 0.05; } } diff --git a/src/main/java/me/zacharias/bank/AccountType.java b/src/main/java/me/zacharias/bank/AccountType.java new file mode 100644 index 0000000..c8d12b3 --- /dev/null +++ b/src/main/java/me/zacharias/bank/AccountType.java @@ -0,0 +1,7 @@ +package me.zacharias.bank; + +public enum AccountType { + BANK_ACCOUNT, + SAVINGS_ACCOUNT, + UNKNOWN, +} diff --git a/src/main/java/me/zacharias/bank/BankAccount.java b/src/main/java/me/zacharias/bank/BankAccount.java index 3f4daef..8e63b24 100644 --- a/src/main/java/me/zacharias/bank/BankAccount.java +++ b/src/main/java/me/zacharias/bank/BankAccount.java @@ -1,12 +1,12 @@ package me.zacharias.bank; -public class BankAccount extends Account { - public BankAccount(String name, int balance) { - super(name, balance); - } +import static me.zacharias.bank.Account.Interests.BANK_ACCOUNT_INTEREST_RATE; - @Override - double getInterestRate() { - return 0.001; +/** + * This class only works as a way to have prefilled information, but can be replaced by just calling the {@link Account#Account(String, double, double)} instead + */ +public class BankAccount extends Account { + public BankAccount(String name, double balance) { + super(name, balance, BANK_ACCOUNT_INTEREST_RATE); } } diff --git a/src/main/java/me/zacharias/bank/Main.java b/src/main/java/me/zacharias/bank/Main.java index 05f025f..a7c3a15 100644 --- a/src/main/java/me/zacharias/bank/Main.java +++ b/src/main/java/me/zacharias/bank/Main.java @@ -1,5 +1,84 @@ package me.zacharias.bank; -public class Main { +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.io.Console; +import java.io.File; +import java.util.UUID; + +import static me.zacharias.bank.Utils.*; + +public class Main { + User user; + + static Gson gson; + + static { + GsonBuilder gsonBuilder = new GsonBuilder(); + //gsonBuilder.registerTypeAdapter(Account.class, new AccountAdapter()); + + gson = gsonBuilder.create(); + } + + public static void main(String[] args) { + String user = SHA256("zacharias"); + File userFile = new File("./users/" + user + ".json"); + + if(!userFile.exists()) { + + User u = new User("zacharias"); + u.createAccount("Konto", 0.0); + Account a = u.getAccount("Konto"); + a.DepositTransaction(104, "Spawnar pängar", UUID.randomUUID()); + + String json = gson.toJson(u); + try { + WriteFile(userFile, Encrypt(json, "pass")); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + new Main(); + } + + public Main() { + + Console console = System.console(); + + if(console == null) { + System.out.println("No console."); + //return; + } + + String name = "zacharias";//console.readLine("Enter your name: "); + + String userHash = SHA256(name); + + File userFile = new File("./users/" + userHash + ".json"); + + if(!userFile.exists()) { + System.out.println("User does not exist"); + return; + } + + String password = "pass";//new String(console.readPassword("Enter your password: ")); + + String userData; + + try { + userData = Decrypt(ReadFile(userFile), password); + } catch (Exception e) { + throw new RuntimeException(e); + } + + System.out.println(userData); + + user = gson.fromJson(userData, User.class); + + Account a = user.getAccount("Konto"); + System.out.println(a.getClass().getSimpleName()); + System.out.println(a.getBalance()); + } } diff --git a/src/main/java/me/zacharias/bank/SavingsAccount.java b/src/main/java/me/zacharias/bank/SavingsAccount.java index 6572a55..3f3b893 100644 --- a/src/main/java/me/zacharias/bank/SavingsAccount.java +++ b/src/main/java/me/zacharias/bank/SavingsAccount.java @@ -1,13 +1,13 @@ package me.zacharias.bank; +import static me.zacharias.bank.Account.Interests.SAVINGS_ACCOUNT_INTEREST_RATE; + +/** + * This class only works as a way to have prefilled information, but can be replaced by just calling the {@link Account#Account(String, double, double)} instead + */ public class SavingsAccount extends Account{ - public SavingsAccount(String name, int balance) { - super(name, balance); - } - - @Override - double getInterestRate() { - return 0.05; + public SavingsAccount(String name, double balance) { + super(name, balance, SAVINGS_ACCOUNT_INTEREST_RATE); } } diff --git a/src/main/java/me/zacharias/bank/User.java b/src/main/java/me/zacharias/bank/User.java index 945aeb2..935a538 100644 --- a/src/main/java/me/zacharias/bank/User.java +++ b/src/main/java/me/zacharias/bank/User.java @@ -6,25 +6,41 @@ import java.util.UUID; public class User { UUID id; String name; - String password; ArrayList accounts; - public User(String name, String password) { + public User(String name) { this.name = name; - this.password = password; id = UUID.randomUUID(); accounts = new ArrayList<>(); } - public void createAccount(String name, int balance) { + public void createAccount(String name, double balance) { Account account = new BankAccount(name, balance); accounts.add(account); } - public void createSavingsAccount(String name, int balance) { + public void createSavingsAccount(String name, double balance) { Account account = new SavingsAccount(name, balance); accounts.add(account); } + + public Account getAccount(String name) { + for (Account account : accounts) { + if (account.name.equals(name)) { + return account; + } + } + return null; + } + + public Account getAccount(UUID id) { + for (Account account : accounts) { + if (account.id.equals(id)) { + return account; + } + } + return null; + } public void deleteAccount(Account account) { Account base = null; diff --git a/src/main/java/me/zacharias/bank/Utils.java b/src/main/java/me/zacharias/bank/Utils.java new file mode 100644 index 0000000..1fd1a6e --- /dev/null +++ b/src/main/java/me/zacharias/bank/Utils.java @@ -0,0 +1,133 @@ +package me.zacharias.bank; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Base64; + +public class Utils { + public static String SHA256(String input) { + MessageDigest sha256 = null; + try { + sha256 = MessageDigest.getInstance("SHA-256"); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + byte[] hash = sha256.digest(input.getBytes(StandardCharsets.UTF_8)); + + StringBuilder hexString = new StringBuilder(); + for (byte b : hash) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) hexString.append('0'); + hexString.append(hex); + } + + return hexString.toString(); + } + + private static final int KEY_SIZE = 256; + private static final int ITERATIONS = 65536; + private static final int SALT_LENGTH = 16; + private static final int IV_LENGTH = 12; + + public static String Encrypt(String data, String username) throws Exception { + SecureRandom random = new SecureRandom(); + + byte[] salt = new byte[SALT_LENGTH]; + random.nextBytes(salt); + + byte[] iv = new byte[IV_LENGTH]; + random.nextBytes(iv); + + SecretKey key = deriveKey(username, salt); + + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv); + cipher.init(Cipher.ENCRYPT_MODE, key, gcmSpec); + byte[] encryptedData = cipher.doFinal(data.getBytes()); + + byte[] combined = new byte[salt.length + iv.length + encryptedData.length]; + System.arraycopy(salt, 0, combined, 0, salt.length); + System.arraycopy(iv, 0, combined, salt.length, iv.length); + System.arraycopy(encryptedData, 0, combined, salt.length + iv.length, encryptedData.length); + + return Base64.getEncoder().encodeToString(combined); + } + + public static String Decrypt(String data, String username) throws Exception { + byte[] combined = Base64.getDecoder().decode(data); + + // Extract salt, IV, and encrypted data + byte[] salt = new byte[SALT_LENGTH]; + byte[] iv = new byte[IV_LENGTH]; + byte[] cipherText = new byte[combined.length - SALT_LENGTH - IV_LENGTH]; + + System.arraycopy(combined, 0, salt, 0, salt.length); + System.arraycopy(combined, salt.length, iv, 0, iv.length); + System.arraycopy(combined, salt.length + iv.length, cipherText, 0, cipherText.length); + + SecretKey key = deriveKey(username, salt); + + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + GCMParameterSpec gcmSpec = new GCMParameterSpec(128, iv); + cipher.init(Cipher.DECRYPT_MODE, key, gcmSpec); + byte[] decrptedData = cipher.doFinal(cipherText); + + return new String(decrptedData); + } + + private static SecretKey deriveKey(String password, byte[] salt) throws Exception { + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_SIZE); + return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES"); + } + + public static String ReadFile(File file) + { + if(!file.exists()) + { + throw new RuntimeException(new FileNotFoundException(file.getPath()+" not found")); + } + + try { + BufferedReader br = new BufferedReader(new FileReader(file)); + StringBuilder sb = new StringBuilder(); + String line; + while((line = br.readLine()) != null) + { + sb.append(line); + } + return sb.toString(); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + public static void WriteFile(File file, String data) { + if(file.exists()) + { + file.delete(); + } + + try { + file.createNewFile(); + + BufferedWriter bw = new BufferedWriter(new FileWriter(file)); + bw.write(data); + bw.close(); + }catch (IOException e) + { + throw new RuntimeException(e); + } + } +}