import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import javafx.application.Application;
import javafx.event.*;
import javafx.scene.control.*;
import javafx.scene.input.*;
import javafx.scene.layout.Pane;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.text.*;
import javafx.scene.text.Font;

/**
 *
 * Beschreibung;
 * Eine Visualisierung: sha256algorithm.com
 *
 * @version 1.0 vom 24.05.2022
 * @author Gerhard Röhner
 */

public class SHAimDetail extends Application {
  // Anfang Attribute
  private Label lText = new Label();
  private Label lPadding = new Label();
  private TextArea taText = new TextArea();
  private TextArea taPadding = new TextArea();
  private Label lSHA25664HexZeichenalso256Bit = new Label();
  private TextField tfSHA256 = new TextField();
  // Ende Attribute
  
  private Integer[] HashValue = new Integer[8];
  private Integer[] Constant = new Integer[64];
  private byte[] PaddedTextBytes;
  private boolean debug = false;
  
  public void start(Stage primaryStage) { 
    Pane root = new Pane();
    Scene scene = new Scene(root, 1007, 243);
    // Anfang Komponenten
    
    lText.setLayoutX(8);
    lText.setLayoutY(8);
    lText.setPrefHeight(20);
    lText.setPrefWidth(334);
    lText.setText("Text");
    root.getChildren().add(lText);
    lPadding.setLayoutX(8);
    lPadding.setLayoutY(96);
    lPadding.setPrefHeight(20);
    lPadding.setPrefWidth(326);
    lPadding.setText("Padding");
    root.getChildren().add(lPadding);
    taText.setLayoutX(8);
    taText.setLayoutY(32);
    taText.setPrefHeight(52);
    taText.setPrefWidth(984);
    taText.setOnKeyReleased(
      (event) -> {taText_KeyReleased(event);} 
    );
    taText.setFont(Font.font("Courier New", 12));
    taText.setWrapText(true);
    root.getChildren().add(taText);
    taPadding.setLayoutX(8);
    taPadding.setLayoutY(120);
    taPadding.setPrefHeight(52);
    taPadding.setPrefWidth(984);
    taPadding.setFont(Font.font("Courier New", 12));
    root.getChildren().add(taPadding);
    primaryStage.setOnShowing(
    (event) -> {SHAimDetail_Showing(event);} 
    );
    lSHA25664HexZeichenalso256Bit.setLayoutX(8);
    lSHA25664HexZeichenalso256Bit.setLayoutY(184);
    lSHA25664HexZeichenalso256Bit.setPrefHeight(20);
    lSHA25664HexZeichenalso256Bit.setPrefWidth(238);
    lSHA25664HexZeichenalso256Bit.setText("SHA256 (64 Hex-Zeichen, also 256 Bit)");
    root.getChildren().add(lSHA25664HexZeichenalso256Bit);
    tfSHA256.setLayoutX(8);
    tfSHA256.setLayoutY(208);
    tfSHA256.setPrefHeight(25);
    tfSHA256.setPrefWidth(984);
    tfSHA256.setFont(Font.font("Courier New", 12));
    root.getChildren().add(tfSHA256);
    // Ende Komponenten
    
    primaryStage.setOnCloseRequest(e -> System.exit(0));
    primaryStage.setTitle("SHA - Secure Hashing Algorithm");
    primaryStage.setScene(scene);
    primaryStage.show();
  } // end of public SHAimDetail
  
  // Anfang Methoden
  
  public static void main(String[] args) {
    launch(args);
  } // end of main
  
  public void ErzeugePadding() {
    String Text = taText.getText();
    
    // UTF-8 Zeichen können mehrere Bytes lang sein
    // daher geht Text.length() nicht
    byte[] TextBytes = Text.getBytes(StandardCharsets.UTF_8);
    int Länge = TextBytes.length;
    lText.setText("Text (" + Länge + " UTF-8-Zeichen zu je 8 Bit)");
    
    int PaddedTextLänge = 64;
    int k = 448 - (8*Länge + 1);
    while (k < 0) { 
      k = k + 512;
      PaddedTextLänge = PaddedTextLänge + 64;
    } 
    int N = PaddedTextLänge/64;
    if (N > 1)
      lPadding.setText("Padding (" + N + " message blocks der Größe 64 Byte, also 512 Bit)");
    else
      lPadding.setText("Padding (" + N + " message block der Größe 64 Byte also 512 Bit)");
    PaddedTextBytes = new byte[PaddedTextLänge];
    for (int i = 0; i < Länge; i++) 
      PaddedTextBytes[i] = TextBytes[i];
    PaddedTextBytes[Länge] = (byte) 0b10000000;
    
    // Länge des Textes in Bit als 64-Bit-Wert an das Ende schreiben
    long l = Long.valueOf(Länge)*8;
    for (int i = PaddedTextLänge - 1; i >= PaddedTextLänge - 1 - 7; i--) {
      PaddedTextBytes[i] = (byte)(l & 0xFF);
      l >>= 8;
    }
    
    // byte array hexadezimal ausgeben
    String hexString = ByteArrayAsHexString(PaddedTextBytes);
    taPadding.setText("");
    while (hexString.length() > 128) { 
      taPadding.appendText(hexString.substring(0, 128) + "\n");
      hexString= hexString.substring(128);
    } 
    taPadding.appendText(hexString);
  }
  
  public Integer ROTR(Integer x, int distance) {
    return Integer.rotateRight(x, distance);
  }
  
  public Integer SHR(Integer x, int distance) {
    // >>> to shift a zero into the leftmost position
    return x >>> distance;
  }
  
  public Integer Ch(Integer x, Integer y, Integer z) {
    return (x & y) ^ (~x & z);
  }
  
  public Integer Maj(Integer x, Integer y, Integer z) {
    return (x & y) ^ (x & z) ^ (y & z);
  }
  
  public Integer großSigma0(Integer x) {
    return ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22);  // bitwise xor
  }

  public Integer großSigma1(Integer x) {
    return ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25);
  }  

  public Integer kleinSigma0(Integer x) {
    return ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3);  
  }

  public Integer kleinSigma1(Integer x) {
    return ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10);
  }
  
  public Integer[] getMessageBlock(int N) {
    Integer[] MessageBlock = new Integer[16];
    int offset = (N - 1) * 64;
    for (int t = 0; t < 16; t++) {
      int val = (PaddedTextBytes[3 + offset] & 0xFF) | ((PaddedTextBytes[2 + offset] & 0xFF) << 8) |
                ((PaddedTextBytes[1 + offset] & 0xFF) << 16) | ((PaddedTextBytes[0 + offset] & 0xFF) << 24); 
      MessageBlock[t] = Integer.valueOf(val);
      offset += 4;
    }
    return MessageBlock;
  }  
  
  public void BerechneHash() {
    setInitialHashValues();
    Integer[] MessageSchedule = new Integer[64];
    int N = PaddedTextBytes.length / 64; // Anzahl der message blocks
    
    for (int i = 1; i <= N; i++) {
      // 1. prepare the message schedule
      Integer[] MessageBlock = getMessageBlock(i);  
      for (int t = 0; t < 16; t++) 
        MessageSchedule[t] = MessageBlock[t];
      for (int t = 16; t <= 63; t++) 
        MessageSchedule[t] = kleinSigma1(MessageSchedule[t-2]) + MessageSchedule[t-7] +
                             kleinSigma0(MessageSchedule[t-15]) + MessageSchedule[t-16];
        
      // 2. initialize the working variables
      Integer a = HashValue[0];
      Integer b = HashValue[1]; 
      Integer c = HashValue[2]; 
      Integer d = HashValue[3]; 
      Integer e = HashValue[4]; 
      Integer f = HashValue[5]; 
      Integer g = HashValue[6]; 
      Integer h = HashValue[7]; 
      
      // 3. 
      Integer T1;
      Integer T2;
      for (int t = 0; t <= 63; t++) {
        T1 = h + großSigma1(e) + Ch(e, f, g) + Constant[t] + MessageSchedule[t];
        T2 = großSigma0(a) + Maj(a, b, c);
        h = g;
        g = f;
        f = e;
        e = d + T1;
        d = c;
        c = b;
        b = a;
        a = T1 + T2;          
      }
      
      // 4. compute the i-th intermediate hash
      HashValue[0] = a + HashValue[0];
      HashValue[1] = b + HashValue[1];
      HashValue[2] = c + HashValue[2];
      HashValue[3] = d + HashValue[3];
      HashValue[4] = e + HashValue[4];
      HashValue[5] = f + HashValue[5];
      HashValue[6] = g + HashValue[6];
      HashValue[7] = h + HashValue[7];
    }
    
    String s = "";
    for (int i = 0; i <= 7; i++) 
      s = s + Integer.toUnsignedString(HashValue[i], 16);
    tfSHA256.setText(s);
  }
  
  public void setInitialHashValues() {
    String[] InitialValues = {
      "6a09e667", "bb67ae85", "3c6ef372", "a54ff53a", "510e527f", "9b05688c", "1f83d9ab", "5be0cd19"};
    for (int i = 0; i < 8; i++) 
      try {
        HashValue[i] = Integer.parseUnsignedInt(InitialValues[i], 16);
      } catch(NumberFormatException e) {
        System.out.println(e.getMessage());
      }     
  }
        
  public void SHAimDetail_Showing(Event evt) {
    String[] Konstanten = {
      "428a2f98", "71374491", "b5c0fbcf", "e9b5dba5", "3956c25b", "59f111f1", "923f82a4", "ab1c5ed5",
      "d807aa98", "12835b01", "243185be", "550c7dc3", "72be5d74", "80deb1fe", "9bdc06a7", "c19bf174",
      "e49b69c1", "efbe4786", "0fc19dc6", "240ca1cc", "2de92c6f", "4a7484aa", "5cb0a9dc", "76f988da",
      "983e5152", "a831c66d", "b00327c8", "bf597fc7", "c6e00bf3", "d5a79147", "06ca6351", "14292967",
      "27b70a85", "2e1b2138", "4d2c6dfc", "53380d13", "650a7354", "766a0abb", "81c2c92e", "92722c85",
      "a2bfe8a1", "a81a664b", "c24b8b70", "c76c51a3", "d192e819", "d6990624", "f40e3585", "106aa070",
      "19a4c116", "1e376c08", "2748774c", "34b0bcb5", "391c0cb3", "4ed8aa4a", "5b9cca4f", "682e6ff3",
      "748f82ee", "78a5636f", "84c87814", "8cc70208", "90befffa", "a4506ceb", "bef9a3f7", "c67178f2"};      
    for (int i = 0; i < 64; i++) 
      try {
        Constant[i] = Integer.parseUnsignedInt(Konstanten[i], 16);
      } catch(NumberFormatException e) {
        System.out.println(e.getMessage());
      }      
    ErzeugePadding();  
    BerechneHash();
  }
  
  public void gibByteArrayAus(byte[] arr) {
    for (int i = 0; i < arr.length; i++) 
      System.out.print(String.format("%02X ", arr[i]));
    System.out.println("");
  }
  
  public String ByteArrayAsHexString(byte[] arr) {
    String s = "";
    for (int i = 0; i < arr.length; i++) 
      s = s + String.format("%02X", arr[i]);
    return s;
  }
  
  public void gibIntegerArrayAus(Integer[] arr) {
    for (int i = 0; i < arr.length; i++) 
      System.out.print(String.format("%08X ", arr[i]));
    System.out.println("");
  }  
  
  public String IntegerToBinary(Integer i) {
    String s = Integer.toBinaryString(i);
    while (s.length() < 32) 
      s = "0" + s;
    return s;
  }   
  public void taText_KeyReleased(KeyEvent evt) {
    ErzeugePadding();  
    BerechneHash();
  } 

  // Ende Methoden
} 
        
