/* Erzeugt zu dem Inhalt einer Datei den WEP (Wireless equivalent privacy)-Strom
   24 IV + 40 Schl = 64 Bit (WEP 1)
   24 IV + 104 Schl = 128 Bit (WEP 2)
   Autor: J. Gamenik, Feb. 2012
   Aufrufe: 
     Verschlüsseln: java WEP -v <Quelldatei> <Zieldatei> <Schlüssel byteweise>
     Entschlüsseln: java WEP -e <Quelldatei> <Zieldatei> <Schlüssel byteweise>
*/

import java.util.zip.CRC32;
import java.util.Random;
import java.io.*;

public class WEP
{
  private final static boolean ZUF = true;  // IV zufällig oder fest
  private final static boolean DEBUG = false; // erweit. Infos
  private byte schluesselFeld [] = new byte[256];

  // Haupt-Methode
  public static void main (String [] args) throws Exception
  {
    // Parameter auswerten
    boolean iO = true; boolean ver = false;
    String quelle = null, ziel = null; byte [] schluessel = new byte[5];  // erstmal fix 40 Bit
    if (args.length >= 8)
    {
      if (args[0].equals("-v")) ver = true;  // Modus verschlüsseln
      else if (args[0].equals("-e")) ver = false;  // Modus entschlüsseln
      else iO = false;
      quelle = args[1]; ziel = args[2];
      // Schlüssel einlesen, 5 (oder 13) Bytes durch Leerzeichen getrennt
      try
      {
        for (int i = 0; i < 5; ++i) schluessel[i] = (byte) Integer.parseInt(args[3+i]);
      } catch (Exception ex) { System.out.println ("Schlüssel nicht i.O"); iO = false; }
    }
    else iO = false;
    if (!iO) { System.out.println ("Aufruf: java WEP [-v|-e] Quelldatei Zieldatei <Schlüssel byteweise>");
      System.exit(1); }

    WEP inst = new WEP();
    // byte schluessel [] = {1, 2, 3, 4, 5};  // 40 Bit Testschlüssel
    if (ver) inst.verschluesseln(quelle, ziel, schluessel);
    else inst.entschluesseln(quelle, ziel, schluessel); 
  }

  // erzeugt zufällig Initialisierungsvektor und verschlüsselt
  public void verschluesseln(String datei, String ausgabe, byte [] schluessel) throws Exception
  {
    System.out.println ("Verschl.: " + datei + " --> " + ausgabe);
    if ((schluessel.length != 5) && (schluessel.length != 13))
    {
      throw new Exception ("Keine gültige Schlüssellänge");
    }
    long iv;
    if (ZUF)
    {
      Random r = new Random(System.currentTimeMillis());
      iv = (r.nextLong() & 0xffffff);
    }
    else
    {
      iv = 0x010203;
    }
    // System.out.println ("Schlüssel:");
    // for (int i = 0; i < schluessel.length; ++i) System.out.print(schluessel[i] + " ");
    System.out.println("\nInitialisierungsvektor generiert " + iv);
    // Initialisierungsmatrizen anlegen(füllen)
    matrix_berechnen (schluessel, iv);
    if (DEBUG) schluesselfeld_anzeigen();

    long psumme = berechnePruefsumme(datei);
    System.out.println ("Prüfsumme: " + psumme);
    sub_verentschluesseln(datei, ausgabe, iv, psumme, true, null);

  }

  public void entschluesseln(String datei, String ausgabe, byte [] schluessel) throws Exception
  {
    System.out.println ("Entschl.: " + datei + " --> " + ausgabe);
    sub_verentschluesseln(datei, ausgabe, 0, 0, false, schluessel);
  }

  // -- private Methoden --

  // Berechnet die CRC32-Summe zur ang. Datei
  private long berechnePruefsumme(String datei) throws Exception
  {
    File f = new File(datei);
    if (! f.canRead())
    {
      throw new Exception (datei + " kann nicht gelesen werden");
    }

    FileInputStream fis = null;
    try
    {
      fis = new FileInputStream(f);
    } 
    catch (FileNotFoundException ex) 
    { 
      throw new Exception("Inputstream konnte nicht erzeugt werden");
    }

    CRC32 crc = new CRC32();  // implementiert Checksumme
    try
    {
      byte[] puffer = new byte[2048]; int insgesamt = 0; int gel;
      do
      {
        gel = fis.read(puffer); if (gel > 0) insgesamt += gel;
        crc.update (puffer, 0, gel);

      } while (gel == 2048);
      if (DEBUG) System.out.println ("Gesamtzahl Bytes: " + insgesamt);
    } 
    catch (IOException ex) { throw new Exception ("Es ist ein Fehler beim Lesen aufgetreten"); }

    try
    {
      fis.close();
    } 
    catch (IOException ex) {}

    return crc.getValue();
  }


  // RC4 Eingabedatei in Ausgabedatei ver-/entschlüsseln (PRGA ,Pseudo Random Generation Algorithm)
  private void sub_verentschluesseln(String eingabe, String ausgabe, long iv /* bei v */, 
      long psumme /* bei v */, boolean ver, byte schluessel [] /* bei e */) throws Exception
  {
    File f1 = new File(eingabe);
    if (! f1.canRead())
    {
      throw new Exception (eingabe + " kann nicht gelesen werden");
    }
    File f2 = new File(ausgabe);
    if (! f1.canWrite())
    {
      throw new Exception (ausgabe + " kann nicht gelesen werden");
    }


    FileInputStream fis1 = null; FileOutputStream fis2 = null;
    try
    {
      fis1 = new FileInputStream(f1); fis2 = new FileOutputStream(f2);
    } 
    catch (FileNotFoundException ex) 
    { 
      throw new Exception("Input-/outputstream konnte nicht erzeugt werden");
    }
    byte[] puffer = new byte[2048]; int insgesamt = 0;

    try
    {
      if (ver)
      {
        // IV zuerst unverschlüsselt übergeben
        puffer[0] = (byte) (iv & 0xff); puffer[1] = (byte) ((iv & 0xff00) >> 8);
        puffer[2] = (byte) ((iv & 0xff0000) >> 16);
        fis2.write(puffer,0, 3);
      }
      else
      {
        // IV zurückgewinnen
        int gel = fis1.read(puffer,0,3); if (gel < 3) throw new Exception("Datei zu kurz");
        iv = (b2i(puffer[0]) | (b2i(puffer[1]) << 8) | (b2i(puffer[2]) << 16));
        System.out.println ("Der Initialisierungsvektor ist " + iv);
        // Schlüsselmatrix hier noch berechnen
        matrix_berechnen (schluessel /* original Schlüssel*/, iv);
        if (DEBUG) schluesselfeld_anzeigen();
      }

      WEPSchluesselStrom strom = new WEPSchluesselStrom(schluesselFeld);
      int gel;
      do
      {
        gel = fis1.read(puffer); if (gel > 0) insgesamt += gel;
        if (!ver && gel < 4) throw new Exception("Interner Fehler bei Entschl.: Pruefsumme bricht an Blockgroesse um");
        for (int i = 0; i < gel; ++i) puffer[i] = (byte) (puffer[i] ^ strom.naechstes());  // XOR
        int weiter = gel;  // Bei Entschl. möglichst anhängende Prüfsumme nicht mit raus
        if (weiter < 2048 && !ver) weiter -= 4;  // ACHTUNG: evtl. wird doch was von Prüfsumme rausge.
        fis2.write(puffer, 0, weiter);
      } while (gel == 2048);

      if (ver)
      {
        // Prüfsumme 32 Bit noch verschlüsseln und übergeben
        puffer[0] = (byte) (psumme & 0xff); puffer[1] = (byte) ((psumme & 0xff00) >> 8);
        puffer[2] = (byte) ((psumme & 0xff0000) >> 16); puffer[3] = (byte) ((psumme & 0xff000000) >> 24);
        for (int i = 0; i < 4; ++i) puffer[i] = (byte) (puffer[i] ^ strom.naechstes());  // XOR
        fis2.write(puffer,0, 4);
      }
      else
      {
        // Prüfsumme 32 Bit zurückgewinnen
        psumme = (b2i(puffer[gel-4]) | (b2i(puffer[gel-3]) << 8) | 
                  (b2i(puffer[gel-2]) << 16) | (b2i(puffer[gel-1]) << 24));
        System.out.println ("Prüfsumme ist " + psumme);  // weiter zur Integrität des Dok. verwenden
      }
    } 
    catch (IOException ex) { throw new Exception ("Es ist ein Fehler beim Lesen/Schreiben aufgetreten"); }

    try
    {
      fis1.close(); fis2.close();
    } 
    catch (IOException ex) {}

    if (!ver)
    {
      long vergleich = berechnePruefsumme(ausgabe);
      if ((psumme ^ vergleich) != 0)
      {
        System.out.println("Differenz: Soll-Prüfsumme: " + psumme + ", Ist-Pruefsumme: " + vergleich);
      }
      else System.out.println ("Pruefsumme passt");
    }

  }

  // berechnet RC4-Schlüsselmatrix (KSA, Key Scheduling Algorithm)
  private void matrix_berechnen(byte [] schluessel, long iv)
  {
    byte aussaat [] = new byte[256]; int i, j, lauf, runde;

    for (i = 0; i < 256; ++i) schluesselFeld[i] = (byte) i;        
    // (IV + schluessel) auf das Feld aussaat aufteilen
    for (i = 0, lauf = 0, runde=0; i < 256; ++i)
    {
      if (lauf == 0) aussaat[i] = (byte) (iv & 0xff);
      else if (lauf == 1) aussaat[i] = (byte) ((iv & 0xff00) >> 8);
      else if (lauf == 2) aussaat[i] = (byte) ((iv & 0xff0000) >> 16);
      else aussaat[i] = schluessel[lauf-3];
      ++lauf; if (lauf == (3 + schluessel.length)) { ++runde; lauf = 0; }
    }
    if (lauf != 0) { System.out.println ("Schlüsselsaat nicht gleichmäßig verteilt"); System.exit(1); }
    System.out.println (runde + " Runden in KSA");
    // das Schlüsselfeld durchmischen
    for (i = 0, j = 0; i < 256; ++i)
    {
      j = j + schluesselFeld[i] + aussaat[i]; // kann < 0 sein
      if (j < 0) j += 256; if (j > 0xff) j -= 256;
      if (j != i)
      { 
         byte hilfe = schluesselFeld[i]; schluesselFeld[i] = schluesselFeld[j]; schluesselFeld[j] = hilfe;
      }
    }
    // Nachzustand: Jeder Wert in [0; 255] steht genau einmal in schluesselFeld drin.
  }

  private void schluesselfeld_anzeigen()
  {
    System.out.println ("Schlüsselfeld:");
    for (int i = 0; i < 256; ++i) System.out.print ((int) schluesselFeld[i] + " ");
    System.out.println("");
  }

  // Wegen Vorzeichenproblematik. Was passiert mit dem Vorzeichen bei Modulo bzw. nach rechts schieben
  private static int b2i(byte wert)
  {
    return (((int) wert) & 0xff);  // bei neg. byte, kein Vz erweitern
  }
}

// Liefert Schlüsselstromdaten
class WEPSchluesselStrom
{
  private int i, j; // Index
  private byte [] schl;  // Verweis auf Schlüssel-Matrix
 
  public WEPSchluesselStrom (byte [] p_schl) throws Exception
  { 
    if (p_schl.length != 256) throw new Exception("Feldgröße passt nicht");
    i = 0; j = 0; schl = p_schl;
  }

  // realisiert PRGA. Periodenlänge max. 256^2 * 256!(Fakultaet)
  public byte naechstes()
  {
    i = (i + 1) & 0xff; j = (j + schl[i]) & 0xff;
    if (i != j) { byte hilfe = schl[i]; schl[i] = schl[j]; schl[j] = hilfe; }
    int neu = (schl[i] + schl[j]) & 0xff;
    // System.out.print ((int) schl[neu] + " ");
    return schl[neu];
  }
}
