
/* Advanced Encryption Standard - AES
   Seit 02. Oktober 2000 das Verfahren Rijndael
   Es ist eine iterierte Blockchiffre, deren Blocklänge b und
   Schlüssellänge k unahängig auf einen der Werte 128, 192 oder
   256 Bit gesetzt werden können. Die Zahl r der Runden variiert,
   abhängig von diesen Werten zwischen zehn und 14.
   
   Mai 2013: Entschlüsselung eingebaut
   Oktober 2013: per Kommandozeile mit -v und -e aufrufbar
*/

import java.io.*;
// siehe auch abstrakte Klasse/Schnittstelle für AES-Klassenentwurf


public /* nicht final */ class AES extends AES_Ss
{
  private static short blockLaenge;  /* 128, 192, 256 Bit */
  private static short schluesselLaenge; /* 128, 192, 256 Bit */
  private short anzahlRunden;
  private short spalten;  // Anzahl Spalten in Zustandsmatrix
  private static boolean DEBUG = true;
  protected static final int subst [] =  // zu tun: genauer byte []
  {
    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
    0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
    0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
    0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
    0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
    0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
    0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
    0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
    0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
    0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
    0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
    0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
    0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
    0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
    0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 
    0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
    0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 
    0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
    0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 
    0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
    0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 
    0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
    0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 
    0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 
    0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 
    0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
    0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 
    0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
    0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 
    0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
    0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 
    0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
  };


  protected static final int substM1 [] =  // zu tun: genauer byte []
  {
    0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 
    0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
    0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 
    0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
    0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 
    0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
    0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 
    0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
    0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 
    0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
    0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 
    0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
    0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 
    0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
    0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 
    0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
    0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 
    0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
    0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 
    0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
    0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 
    0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
    0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 
    0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
    0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 
    0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
    0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 
    0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
    0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 
    0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
    0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 
    0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
  };

/*
  AES arbeitet mit Spalten eines Vektors von 4 Elementen. Dies wird bei
  MixColumn deutlich. Es gibt es damit 4, 6 oder 8 Spalten.
*/
  private byte [] [] zustand;  // Feld von Schlüssel
  private byte [] [] schluessel;  // alle gebrauchten Schlüssel


  // Konstruktor
  public AES (short bl, short sl) throws Exception
  {
  	//  Noch Prüfung auf gültige Block- und Schlüssellänge
    for (short runde = 0; runde < 2; ++runde)
    {
      short ak = (runde == 0) ? bl : sl;
      if ((ak != 128) && (ak != 192) && (ak != 256))
      {
        throw new Exception ("Ungültige Länge " + ak);
      }
    }
    if (subst.length != 256 || substM1.length != 256) 
      throw new Exception("S-Box nicht geeignet");
    blockLaenge = bl; schluesselLaenge = sl;
    spalten = (short) (blockLaenge / 32);  // 4, 6 oder 8 Spalten
    zustand = new byte [4][spalten];
    bestimmeAnzahlRunden ();
    // die Schlüsselexpansion erzeugt r+1 Schlüssel, welche die
    // gleiche Länge haben wie der jeweilige Zustand
    schluessel = new byte [anzahlRunden+1][blockLaenge / 8];
    // Schlüssel wird hier noch nicht berechnet
  }


  // Ermittelt, wie viele Runden gemacht werden
  protected void bestimmeAnzahlRunden ()
  {
    if ((blockLaenge == 128) && (schluesselLaenge == 128))
    {
      anzahlRunden = 10;
    }
    else if ((blockLaenge == 256) || (schluesselLaenge == 256))
    {
      anzahlRunden = 14;
    }
    else
    {
      anzahlRunden = 12;
    }
  }

  // Blockverschlüsselung:
  // Die Schlüsselexpansion hat r + 1 Rundenschlüssel K0,K1,..Kr
  // der Länge b aus dem Schlüssel der Länge k erzeugt. Die Runden-
  // schlüssel haben die gleiche Länge wie der jeweilige Zustand
  // Vor der ersten Runde und nach jeder Runde wird der Schlüssel
  // XOR-verknüpft mit dem aktuellen Zustand. Das Ergebnis dient als
  // Eingabe für die nächste Runde bzw. als Chiffretext. Jede Runde
  // ausser der letzten besteht aus den unten beschriebenen Funktionen
  // BYTESUB, SHIFTROW, MIXCOLUMN. In der letzten Runde fällt die
  // Funktion MIXCOLUMN weg. Bemerkenswert ist, dass alle Transformationen
  // in den Runden ausser der XOR-Verknüpfung schüsselunabhängig sind.
  public void verschluesseln (byte [] block, byte [] erg) throws Exception
  {
    int i, j; test_intro(block, erg);

    // vor der ersten Runde: mache ein XOR mit dem ersten Schlüssel
    macheXORMitSchluessel(schluessel[0]);

    // mache Anzahl an Runden
    for (int runde = 1; runde <= anzahlRunden; ++runde)
    {
      // 1. ByteSub des Zustandes
      for (i = 0; i < 4; ++i)
      {
        for (j = 0; j < spalten; ++j)
        {
          zustand[i][j] = berechneByteSub (zustand[i][j], true);
        }
      }
      // 2. Jede Zeile zyklisch schieben (in der ersten eig. nicht)
      for (i = 0; i < 4; ++i) berechneShiftRow (i, true);
      // 3. Jede Spalte transformieren, ausser in letzter Runde
      if (runde < anzahlRunden)
      {
        for (i = 0; i < spalten; ++i)
        {
          berechneMixColumn (i, true);
        }
      }
      // 4. XOR mit dem aktuellen Rundenschlüssel
      macheXORMitSchluessel(schluessel[runde]);
    }  // Ende Runde

    // Jetzt noch ins Ergebnis kopieren - s.o.
    extro(erg);
  } // Ende sub verschluesseln

  // Das Entschlüsseln - spiegelbildlich
  public void entschluesseln (byte [] block, byte [] erg) throws Exception
  {
    int i, j; test_intro(block, erg);

    // mache Anzahl an Runden
    for (int runde = anzahlRunden; runde >= 1; --runde)
    {
      // 4. XOR mit dem aktuellen Rundenschlüssel
      macheXORMitSchluessel(schluessel[runde]);

      // 3. Jede Spalte transformieren, ausser in letzter Runde
      if (runde < anzahlRunden)
      {
        for (i = 0; i < spalten; ++i)
        {
          berechneMixColumn (i, false);
        }
      }

      // 2. Jede Zeile zyklisch schieben (in der ersten eigentlich nicht)
      for (i = 0; i < 4; ++i) berechneShiftRow (i, false);

      // 1. ByteSub des Zustandes
      for (i = 0; i < 4; ++i)
      {
        for (j = 0; j < spalten; ++j)
        {
          zustand[i][j] = berechneByteSub (zustand[i][j], false);
        }
      }
    }  // Ende Runde

    // vor der ersten Runde: mache ein XOR mit dem ersten Schlüssel
    macheXORMitSchluessel(schluessel[0]);

    // Jetzt noch ins Ergebnis kopieren - s.o.
    extro (erg);
  }


  // bei Ver-/Entschlüsseln: Testet Eingabewerte und übernimmt diese
  private void test_intro (byte [] block, byte [] erg) throws Exception
  {
    if ((block == null) || (erg == null)) 
    {
      throw new Exception ("Ein-/Ausgabefeld leer");
    }
    if (((block.length * 8) != blockLaenge) ||
        (block.length != erg.length))
    {
      throw new Exception ("Länge der Ein-/Ausgabevektoren passt nicht");   // Pech 

gehabt
    }
    // Vorzustand: die Rundenschlüssel wurden schon berechnet

    // Klartextblock in Zustand kopieren - erst über Zeilen
    int z = 0; int s = 0;
    for (int i = 0; i < block.length; ++i)
    {
      zustand[z][s] = block[i]; ++z; 
      if (z == 4) { z = 0; ++s; }
    }
  }
 

  // Übernimmt bei Ver-/Entschlüsseln Ausgabewerte ins Ergebnis
  private void extro(byte [] erg)
  {
    int z = 0; int s = 0;
    for (int i = 0; i < erg.length; ++i)
    {
      erg[i] = zustand[z][s]; ++z;
      if (z == 4) { ++s; z = 0; } 
    }
  }


  // Addiert ein XOR mit dem Schlüssel zum Zustand
  protected void macheXORMitSchluessel(byte [] schl) throws Exception
  {
    if ((schl == null) || ((schl.length * 8) != blockLaenge))
    {
      throw new Exception ("Schlüssel leer oder Länge unpassend");
    }

    int z = 0; int s = 0;
    for (int i = 0; i < schl.length; ++i)  // Wie bei Eingabe erst über Zeilen
    {
      zustand[z][s] = (byte) (zustand[z][s] ^ schl[i]); ++z; 
      if (z == 4) { z = 0; ++s; }
    }
  }


  // Diese Transformation stellt die nichtlineare S-Box von AES dar.
  // Sie wird auf jedes Byte des Zustandes angewendet
  // Sie kann als Vorausschau-Tabelle implementiert werden, s.o.
  protected byte berechneByteSub (byte x, boolean hin)
  {
    int ind = x;
    if (ind < 0) ind += 256;
    return (byte) (hin ? subst[ind] : substM1[ind]);  // wegen Zugriff in Tabelle 

auf 0...255 abb.
  }

  // Die ShiftRow-Transformation
  // Die Zustandsmatrix besteht aus 4 Zeilen, und Blocklänge/32 Spalten
  // Die Operation verschiebt die Zeilen 1 bis 3 der Zustandsmatrix
  // zyklisch nach links. Zeile 0 wird nicht verändert. Zeile 1 wird
  // um c1 Bytes, Zeile 2 um c2 Bytes und Zeile 3 um c3 Bytes verschoben.
  // Die von der Blockgröße abhängigen Verschiebungen sind angegeben:
  /*
    c\b |   128   192    256
    ------------------------
    c1  |   1      1      1
    c2  |   2      2      3
    c3  |   3      3      4
  */
  protected void berechneShiftRow (int nr, boolean hin)
  {
    if ((nr < 1) || (nr > 3)) return;

    short anz = 0;
    if (nr == 1) anz = 1;
    else if (nr == 2) anz = (blockLaenge < 256) ? (short) 2 : (short) 3;
    else if (nr == 3) anz = (blockLaenge < 256) ? (short) 3 : (short) 4;
    byte neu [] = new byte [spalten];
    for (short j = 0; j < spalten; ++j)
    {
      // hin: linksschieben, rück: rechtsschieben
      int woher =  hin ? (j + anz) : (j - anz);
      if (woher < 0) woher += spalten;
      if (woher >= spalten) woher -= spalten;
      neu[j] = zustand[nr][woher];
    }
    zustand[nr] = neu;
  }

  // Die MixColumn-Transformation
  // Diese Operation bildet jede Spalte (a0, a1, a2, a3) des Zustands
  // ab durch die Matrixmultiplikation, modulo x^4 + 1 (= zykl. Versch.)
  /*
     Hin:
    (a0') = (02  03  01  01) * (a0)
    (a1') = (01  02  03  01) * (a1)
    (a2') = (01  01  02  03) * (a2)
    (a3') = (03  01  01  02) * (a3)
     Rueck:
    (a0) = (0e  0b  0d  09) * (a0')
    (a1) = (09  0e  0b  0d) * (a1')
    (a2) = (0d  09  0e  0b) * (a2')
    (a3) = (0b  0d  09  0e) * (a3')  
    Es ist also ein Produkt des Polynoms der Spalte mit einem konst. Polynom
  */
  protected void berechneMixColumn (int nr, boolean hin)
  {
    if ((nr < 0) || (nr >= spalten)) return;
    // Spalte kopieren (wird als Polynom vom Grad 3 aufgefasst,
    // jeder Koeff wieder aus GF(2^8)
    byte lauf [] = new byte [4];
    int i, j;
    for (i = 0; i < 4; ++i) lauf[i] = zustand[i][nr];
    final byte polyA [] =  {2, 1, 1, 3} ; // 03*x^3 + 01*x^2 + 01*x + 02
    final byte polyAM1 []=  {14, 9, 13 ,11};  // 0b*x^3 + 0d*x^2 + 09*x + 0e
    final byte faktor [] = hin ? polyA : polyAM1;
    byte y [] = {0, 0, 0, 0};
    for (i = 0; i < 4; ++i)
    {
      for (j = 0; j < 4; ++j) 
      {
        y[j] = (byte) (multGF28(lauf[j], faktor[0]/* niedr. Koeff */) ^ y[j]);
      }
      // 2) lauf[] wird erhöht, 1) faktor[] gesenkt
      for (j = 0; j < (3 - i); ++j) faktor[j] = faktor[j+1];
      faktor[3 - i] = (byte) 0;
      byte hilf = lauf[3];
      for (j = 3; j > 0; --j) lauf[j] = lauf[j-1];
      lauf[0] = hilf;
    }
    // faktor ist jetzt das Polynom der 0, wenn der oberst. Koeff 0 ges. wird

    // Ergebnispolynom y wieder zurückkopieren
    for (i = 0; i < 4; ++i) zustand[i][nr] = y[i];
  }

  

  // Die Schlüsselexpansion.
  // Der Schlüssel wird so aufgeweitet, dass sich r + 1 Teilschlüssel
  // mit je b Bit daraus bilden lassen. Bei einer Blocklänge von 128 Bit
  // und zwölf Runden werden 128 * 13 = 1664 Schlüsselbits benötigt.
  // Die ersten k Bits des expandierten Schlüssels sind eine Kopie
  // des Schlüssels. Jedes folgende 32-Bit-Wort W(i) ist gleich dem
  // XOR des vorhergehenden Wortes W(i-1) und des Wortes W(i-k/32),
  // welches eine Schlüssellänge früher beginnt. Für Worte, die bei
  // einem Vielfachen der Schlüssellänge beginnen, wird vor dem XOR
  // eine nichtlineare Substitution auf W(i-1) angewendet, welche unter
  // anderem die S-Box aufruft. Die Auswahl der Rundenschlüssel
  // aus dem expandiertem Schlüssel erfolgt wortweise sequentiell.
  // siehe Beschreibung bei Dietmar Wätjen Kapitel 12
  public void rundenSchluesselBerechnen (byte [] initial) throws Exception
  {
    if ((initial == null) || ((initial.length * 8) != schluesselLaenge)) 
    {
      throw new Exception ("Schlüssel leer oder Länge unpassend");
    }

    int i, j;
    int anz = (blockLaenge / 8) * (anzahlRunden + 1);  // Anzahl gebrauchte Bytes
    byte tempFeld [] = new byte [anz];
    // 1) solange Schlüsselbytes da sind, übernehmen
    for (i = 0; i < initial.length; ++i) tempFeld [i] = initial[i];
    
    // Rcon nennt Wätjen eine Rundenkonstante, fängt unten mit Index 1 an und wird 

um eines beim nächsten
    // Mal erhöht
    byte rcon = 1;  // RCon(i) = (x^(i-1),0,0,0); erstes i immer 1, siehe unten, x 

= 02

    // 2) 32-Bit wortweise durchgehen, und jeweils W(i-1) und W(i-schllaenge/32) 
    byte kopie [] = new byte [4];
    for (i = initial.length; i < anz; i += 4)  // Wort = 32-Bit
    {
      for (j = 0; j < 4; ++j) kopie[j] = tempFeld[i - 4 + j];

      int diskr = (i % initial.length);
      if (diskr == 0)  // Beginn ganzzahlige Schlüssellänge ?
      {
        // kopie := SubWord(RotWord(kopie)) xor Rcon(i/Nk); NK=4,6,8
      	RotWord(kopie);
        SubWord(kopie);
	    kopie[0] = (byte) (kopie[0] ^ rcon);  // die anderen 3 Bytes bleiben 0
        // Rcon weiterschalten, Polynom m(x) beachten;
	    rcon = multGF28(rcon, (byte)02); 
      }
      else if (schluesselLaenge == 256 && (diskr == 16))  // (i % nk == 4, wobei nk 

hier 8)
      {
        // kopie := SubWord(kopie); // ruft nur die SBox auf
	    if (DEBUG) System.out.println("Einschub2 an " + i);
      	SubWord(kopie); 
      }
      // else kopie bleibt

      // wi := wi-NK xor kopie
      int ab = i - initial.length;
      for (j = 0; j < 4; ++j)
      {
        tempFeld[i+j] = (byte) (tempFeld[ab + j] ^ kopie[j]);
      }
    }    

    // Jetzt alle temporär ermittelten zu Rundenschlüsseln kopieren
    // Jeder Rundenschlüssel ist solange wie der aktuelle Block
    // Jede Zeile des Schlüsselfeldes hat initial.length Bytes, für 
    // anzahlRunden + 1 Zeilen !
    if (DEBUG) System.out.println("Beginn Ausgabe der Rundenschlüssel:");
    int amStueck = blockLaenge / 8; int zeile = 0, spalte = 0;
    for (i = 0; i < anz; ++i)
    {
      schluessel[zeile][spalte] = tempFeld[i];
      if (DEBUG) System.out.print(tempFeld[i] + " ");
      ++spalte; 
      if (spalte == amStueck) 
      { 
        ++zeile; spalte = 0; if (DEBUG) System.out.println();
      }
    }
    if (DEBUG) System.out.println("Ende Ausgabe der Rundenschlüssel");    
  }


  // SubWord für die Schlüsselberechnung
  private void SubWord(byte [] temp) throws Exception
  {
    if (temp.length != 4) throw new Exception ("Kein 32-Bit Wort"); 
 
    for (int i = 0; i < 4; ++i)
    {
      temp[i] = berechneByteSub(temp[i], true/*hin*/);
    }
  }

  // RotWord für die Schlüsselberechnung
  private void RotWord(byte [] temp) throws Exception
  {
    if (temp.length != 4) throw new Exception ("Kein 32-Bit Wort"); 

    byte hilfe = temp[0];
    for (int i = 0; i < 3; ++i) temp[i] = temp[i+1];
    temp[3] = hilfe;
  }  

  // Multiplikation von Bytes über GF(2^8).
  // mod-Polynom = x^8 + x^4 + x^3 + x + 1 (283), d.h. nach XOR max. 8 Bits
  private static byte multGF28 (byte w1, byte w2)
  {
    // 3 Sonderfälle:
    if (w1 == 0 || w2 == 0) return 0; 
    if (w1 == 1) return w2;
    if (w2 == 1) return w1;
    
    int lauf = w1 & 0xff; int y = 0; int faktor = w2 & 0xff;
    while (true)
    {
      // Kommutativität ausnützen: faktor soll immer das kleinere sein
      if (faktor > lauf) { int hilf = faktor; faktor = lauf; lauf = hilf; }
      if (faktor == 0) break;
      if ((faktor & 1) == 1) y = y ^ lauf;
      faktor >>= 1; lauf <<= 1;
      if ((lauf & 256) != 0) lauf = lauf ^ 283;  // mod Polynom
    }

    return (byte) y;
  }

  // holt den Schlussel aus der Datei (hexadezimal, Trenner leer od. Komma)
  private static byte [] holeSchluessel() throws Exception
  {
	byte [] hilf = new byte[32];

    FileInputStream fis = new FileInputStream("key.txt");
	byte puffer [] = new byte[2048];
	int anz = fis.read(puffer);
	int index = 0; int ak = 0;
	for (int i = 0; i < anz && index < hilf.length; ++i)
	{
	  if (puffer[i] == ' ' || puffer[i] == ',' || puffer[i] == (byte)10 || 

puffer[i] == (byte)13) { 
		  hilf[index] = (byte) ak; ++index; ak = 0; 
		  if (puffer[i] <= 13) break; else continue; } 
	  if (puffer[i] >= 'A' && puffer[i] <= 'F')
	  {
		ak = ak * 16 + (puffer[i] - 'A' + 10);  
	  }
	  else if (puffer[i] >= '0' && puffer[i] <= '9')
	  {
		 ak = ak * 16 + (puffer[i] - '0'); 
	  }
	  else
	  { 
		  throw new Exception((int) puffer[i] + " nicht hexadezimal");
	  }
	  if (ak >= 256) throw new Exception("Schlüssel nicht hexadezimal");
	}

	if (index == 16)
	{
	  schluesselLaenge = 128;	
	}
	else if (index == 24)
  	{
	  schluesselLaenge = 192;
	}  
	else if (index == 32)
	{
	  schluesselLaenge = 256;
	}
	else
	{
	  throw new Exception (index + " ist keine Schlüssellänge");	
  	}
	System.out.println("Modus AES-" + schluesselLaenge);
	byte [] rueck = new byte[index];
	for (int i = 0; i < index; ++i) rueck[i] = hilf[i];

	return rueck;
	
  }
  
  static
  {
    // Test der Multiplikation, siehe Beispiel Wätjen Kap. 12.1
    byte x1 = (byte) 0x57; byte x2 = (byte) 0x13; byte erg = multGF28(x1, x2);
    if (erg != (byte) 0xfe) System.out.println ("Multiplikation stimmt nicht");
    /* AES mit mysql:
    SET @str =      'aaaaaaaaaaaaaaaa';
    SET @schl = md5('bbbbbbbbbbbbbbbb');
    SELECT hex(aes_encrypt(@str, @schl)) = 57,95,AA,...;  keine Übereinstimmung!!!
    */
  }


  // -------------------------------------------------------
  
  // Hauptmethode für VM
  public static void main (String [] args) throws Exception
  {
    if (args.length < 1 || (!args[0].equals("-v") && !args[0].equals("-e")))
    {	
	  System.out.println("java AES -v|-e [<eingabe.txt>] [<ausgabe.txt>]");
      System.out.println("wobei der 128/192/256-Bit-Schluessel hexadezimal in 

key.txt");    
	  System.out.println("-v macht verschluesseln, -e entschlüsseln"); 

System.exit(1);
    }
    String datEingabe = "eingabe.txt"; String datAusgabe = "ausgabe.txt";
    boolean vers = args[0].equals("-v");
    if (args.length >= 2)
    {
      datEingabe = args[1];
    }
    if (args.length >= 3)
    {
      datAusgabe = args[2];	
    }
    File dat = new File(datEingabe);
    if (! dat.exists())
    {
      System.out.println(datEingabe + " wurde nicht gefunden"); System.exit(1);
    }
    byte [] in = holeSchluessel(); blockLaenge = 128;
    AES a = new AES (blockLaenge, schluesselLaenge);
    a.rundenSchluesselBerechnen (in);

    // Nehme eine Datei, und verschlüssele den Inhalt
    byte bl [] = new byte [blockLaenge / 8];
    byte neu [] = new byte [blockLaenge / 8];
    // zu tun: Einlesen mit größeren Block z.B. 4096 und dann aufteilen

    boolean ausgabe = true;
    try
    {
      long anzahl = 0;
      long start = System.currentTimeMillis();

      FileInputStream fis = new FileInputStream(datEingabe);
      FileOutputStream fos = new FileOutputStream(datAusgabe);
      do
      {
        int anz = fis.read(bl);  // falls weniger gel., evt. mit 0 auff.
        if (anz < 0) break;
        if (ausgabe) 
        {
        	System.out.println("Eingabe:");
        	for (int i = 0; i < anz; ++i) System.out.print ((int) bl[i] + " ");
        	System.out.println("");
        }

        if (vers) a.verschluesseln (bl, neu); 
        else a.entschluesseln(bl, neu);
        ++anzahl; fos.write(neu);
	    for (int i = 0; i < bl.length; ++i) bl[i] = 0;  // löschen
        if (ausgabe) 
       	{
        	System.out.println("Ausgabe:");
        	for (int i = 0; i < anz; ++i) System.out.print ((int) neu[i] + " 

");
        	System.out.println("");
       	}

	    if (anz < bl.length) break;  // fertig mit lesen
      } 
      while (true);
      fis.close();
      fos.close();

      long ende = System.currentTimeMillis();
      System.out.println (anzahl + " Blöcke mit " + blockLaenge + " Bits in ");
      System.out.println ((ende - start) + " Millisekunden");
    }
    catch (Exception ex) { ex.printStackTrace(); }
  }
 

  // -------------------------------------------------------

}
