Sitzung: Jeden Freitag ab 15 Uhr c. t. online. Falls ihr den Link haben wollt, schreibt uns. Manchmal auch im MAR 0.005 (Jemensch da?). Macht mit!

Javakurs/Übungsaufgaben/Cäsar-Chiffre/Musterloesung

Cäsar-Chiffre - Musterlösung

Fas Verschlüsseln und Entschlüsseln wurde der Übersichtlichkeit halber in zwei Klassen aufgeteilt, welche eigenständig lauffähig sind.

CaesarEncode.java (Cäsar-Verschlüsselung)

Diese Klasse liest eine Datei ein, verschlüsselt sie und speichert sie anschließend.

package caesar;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.LinkedList;

/**
 * Programm welches eine Textdatei mit der Caesar-Verschiebung verschlüsselt.<br>
 * Der Schluessel ist eine genze Zahl zwischen 0 und 26,
 * um die alle "normalen" Buchstaben verschoben werden.<br>
 * (nur <code>a..z</code> und <code>A..Z</code>)<br>
 * Der Originaltext wird aus einer Datei eingelesen und
 * verschlüsselt in eine andere geschrieben.<br>
 * (eine vorhandene Datei wird dabei ueberschrieben)<br>
 * Parameterliste: Dateiname (Eingabe), Schluessel, Dateiname (Ausgabe)
 * @author André Schulz
 * @version 1.0 (3/2009)
 */
public class CaesarEncode
{
	/**
	 * Main-Methode fuehrt das Programm aus.<br>
	 * Programm verschiebt alle Buchstaben um <code>Schluessel</code> (zwischen 0 und 26).
	 * @param args [Dateiname fuer Eingabe, Schluessel, Dateiname fuer Ausgabe]
	 * @throws IOException falls Fehler beim Lesen der Datei auftreten
	 */
	public static void main(String[] args) throws IOException
	{
		// bei falscher Argument-Länge wird die Parameterliste ausgegeben
		if (args.length != 3)
		{
			System.out.println("params: <fileIn> <key> <fileOut>");
			// beenden des Programms
			System.exit(0);
		}

		// ==++== lesen der Eingabedatei (Klartext)
		System.out.println("reading");// Fortschrittausgabe
		
		// erstellt Reader um aus Datei zu lesen
		BufferedReader br = new BufferedReader(new FileReader(args[0]));
		
		// liest eine Zeile aus der Datei ein
		String line = br.readLine();
		
		// Liste für die verschlüsselten Zeilen
		LinkedList<String> encodedLines = new LinkedList<String>();
		int key = -1;// initialisieren der Variable für die Verschiebung mit default-Wert
		
		// Ermitteln des Schluessels und Fehlerbehandlung
		try
		{
			// wandelt den String aus dem 2. Parameter in eine Zahl um
			key = Integer.parseInt(args[1]);
		}
		catch (NumberFormatException e)
		{
			// wenn der String nicht in eine ganze Zahl umgewandelt werden konnte
			// wird eine Fehlermeldung ausgegeben und beendet
			System.out.println("could not parse key ('" + args[1] + "' is no number!)");
			System.exit(0);
		}
		
		// Ueberpruefung ob der Schluessel im gueltigen Bereich liegt
		if (key < 0 || key > 26)
		{
			// Fehlermeldung wenn Schluessel ausserhlab Bereich
			System.out.println("key must be 0..26");
		}

		// es werden so lange Zeilen gelesen, wie vorhanden
		while (line != null)
		{
			// fuegt die verschluesselten Zeilen der Zeilen-Liste hinzu
			encodedLines.add(encode(line, key));
			line = br.readLine();// lesen der naechsten Zeile
		}
		
		// schliesst den Reader, da nicht mehr benoetigt
		// (Datei ist wieder fuer andere Programme frei)
		br.close();
		// ==--== lesen der Eingabedatei (Klartext)

		// ==++== schreiben der Ausgabedatei (verschluesselt)
		System.out.println("writing output...");// Fortschrittausgabe
		
		// erstellt Writer um in eine Datei zu schreiben (vorhandene wird ueberschrieben)
		BufferedWriter bw = new BufferedWriter(new FileWriter(args[2]));

		// Schleife lauft ueber alle Elemente in der Zeilen-Liste
		for (String lineOut : encodedLines)
		{
			bw.write(lineOut);// schreibt aktuelle Zeile in Datei
			bw.write("\n");// schreibt Zeilenumbruch in Datei
			// bw.write("\r\n");//schreibt Zeilenumbruch in Datei (Windows-Version)
		}
		
		// schliesst Writer, da nicht mehr benoetigt
		bw.close();
		// ==--== schreiben der Ausgabedatei (verschluesselt)
		
		System.out.println("finsh");// Fortschrittausgabe
	}// main

	/**
	 * Methode, welche einen String nach Caesar verschiebt.
	 * @param text Text der zu verschluesseln ist
	 * @param key Schluessel, um den Buchstaben verschoben werden sollen (0..26)
	 * @return verschluesselter Text
	 */
	private static String encode(String text, int key)
	{
		// erstellt neuen StringBuilder, um Ausgabe-String zu generieren
		StringBuilder sb = new StringBuilder();

		int newC;// Code eines alten buchstaben
		int oldC;// Code des Buchstaben in neu

		// Schleife durchlauft alle Zeichen
		while (!text.isEmpty())
		{
			// der erste Buchstabe wird gelesen
			newC = oldC = text.charAt(0);

			// Ueberpruefung ob Zeichen ein Buchstabe ist,
			// dann wird umgewandelt (ansonsten bleibt Zeichen erhalten)
			if (oldC >= 'a' && oldC <= 'z')
			{
				// Kleinbuchstabe
				newC = oldC + key;// Verschluesselung

				if (newC > 'z')//Behandlung von Overflows
				{
					newC -= 26;// anpassen des Buchstaben
				}// if
			}
			else if (oldC >= 'A' && oldC <= 'Z')
			{
				// Grossbuchstabe
				newC = oldC + key;// Verschluesselung
				
				if (newC > 'Z')//Behandlung von Overflows
				{
					newC -= 26;// anpassen des Buchstaben
				}// if
			}// if
			else
			{
				// nix, da nur "normale" Buchstaben verschluesselt werden
			}

			// das aktuelle Zeichen (der verschluesselte Buchstabe)
			//wird dem String hinzugefuegt
			sb.append((char) newC);
			
			// der momentane Text wird auf den Rest (one erstes Zeichen) gesetzt
			text = text.substring(1);
		}// while

		return sb.toString();// verschluesselter String wird zurueckgegeben
	}// encode
}//classs

CaesarEncode.java (Cäsar-Entschlüsselung)

Diese Klasse entschlüsselt eine verschlüsselte Datei. Dazu gibt es zwei Möglichkeiten:

  1. man gibt den Schlüssel an nach dem entschlüsselt werden soll (wenn bekannt)
  2. man gibt keinen Schlüssel an und das Programm ermittelt den Schlüssel.

Bei beiden Versionen wird die entschlüsselte Datei anschließend gespeichert.

package caesar;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.LinkedList;

/**
 * Programm welches eine mit der Caesar-Verschiebung verschlüsselte Textdatei entschluesselt.<br>
 * Der Schluessel wird entweder als Parameter uebergeben oder durch das Programm ermittelt.<br>
 * Bei der eigenstaendigen Ermittlung wird nach dem haefigsten Buchstaben gesucht,
 * welcher dann das <code>e</code> sein muesste.<br>
 * Parameterliste:<br>
 * entweder: Dateiname (Eingabe), Schluessel, Dateiname (Ausgabe)<br>
 * oder: Dateiname (Eingabe), Dateiname (Ausgabe)
 * @author André Schulz
 * @version 1.0 (3/2009)
 */
public class CaesarDecode
{
	public static void main(String[] args) throws IOException
	{
		// Ueberpruefung ob Parameteranzahl falsch ist
		if (args.length < 2 || args.length > 3)
		{
			// Ausgabe der parameterliste und beenden des Programms
			System.out.println("params: <fileIn> [<key>] <fileOut>");
			System.exit(0);
		}

		int key = -1;// Schluessel (Initialisierung mit default-Wert)
		String fileNameout;// Name der Ausgabedatei
		
		// erstellt Reader um aus verschluesselter Datei zu lesen
		BufferedReader br = new BufferedReader(new FileReader(args[0]));
		
		System.out.println("reading");// Fortschrittausgabe
		String line = br.readLine();// liest erste Zeile
		
		// Liste fuer die gelesenen Zeilen
		LinkedList<String> lines = new LinkedList<String>();
		
		// Schleife so lange, wie gelesene Zeilen Text enthalten
		while (line != null)
		{
			lines.add(line);// hinzufuegen der Zeile zur Liste
			line = br.readLine();// lesen der naechsten Zeile
		}
		
		// Schliessen der Datei
		br.close();

		// Auswahl je nach Parameteranzahl
		if (args.length == 3)
		{
			// 3 Parameter: Schluessel vom Benutzer gegeben

			try
			{
				// wandelt den String aus dem 2. Parameter in eine Zahl um
				key = Integer.parseInt(args[1]);
			}
			catch (NumberFormatException e)
			{
				// wenn der String nicht in eine ganze Zahl umgewandelt werden konnte
				// wird eine Fehlermeldung ausgegeben und beendet
				System.out.println("could not parse key ('" + args[1] + "' is no number!)");
				System.exit(0);
			}

			// Ueberprueft Schluessel auf Gueltigkeit
			if (key < 0 || key > 26)
			{
				// Fehlerausgabe wenn ungueltig und beendung des Programms
				System.out.println("key must be 0..26");
				System.exit(0);
			}

			// setzt Dateiname fuer Ausgabe mit Wert aus 3. Parameter
			fileNameout = args[2];
		}
		else
		{
			// nicht 3 Parameter => 2 Parameter
			// Schluessel ist niciht gegeben uns muss ermittelt werden

			// Schluesselermittlung an Hand der gelesenen Zeilen
			key = determineKey(lines);
			fileNameout = args[1];// Dateiname fuer Ausgabe
			System.out.println("key:" + key);// Ausgabe des gefundenen Schluessels
		}// else

		System.out.println("writing output...");// Fortschrittausgabe
		
		// Writer zum Schreiben der entschluesselten Datei
		BufferedWriter bw = new BufferedWriter(new FileWriter(fileNameout));

		// Schleife geht alle gelesenen Zeilen durch
		for (String lineOut : lines)
		{
			// jede Linie wird separat decodiert und dann in Datei geschrieben
			bw.write(decode(lineOut, key));
			bw.write("\n");// Zeilenumbruch
			// bw.write("\r\n");//schreibt Zeilenumbruch in Datei (Windows-Version)
		}
		
		// Schliessen der Datei
		bw.close();
		System.out.println("finsh");// Fortschrittausgabe
	}// main

	/**
	 * Methode ermittelt den Schluessel an Hand des Textes.
	 * @param lines Text als Liste der Zeilen
	 * @return Schluessel mit dem verschluesselt wurde
	 */
	private static int determineKey(LinkedList<String> lines)
	{
		int i;// Zaehlvariable (wird mehr mahls verwendet)
		int[] lower = new int[26];// Array fuer Haufigkeit der Kleinbuchstaben
		int[] upper = new int[26];// Array fuer Haufigkeit der Grossbuchstaben
		char c;// aktuelles Zeichen

		// ==++== Ermittlung der einzelnen Haeufigkeiten
		// Schleife durchlauft alle eingelesenen Zeilen
		for (String line : lines)
		{
			// Zeile wird Zeichen fuer Zeichen durchlaufen
			for (i = 0; i < line.length(); i++)
			{
				c = line.charAt(i);// Zeichen wird gelesen

				// wenn Zeichen ein buchstabe ist, wird Stelle im Array incrementiert
				if (c >= 'a' && c <= 'z')
				{
					// Kleinbuchstabe
					lower[c - 'a']++;
				}
				else if (c >= 'A' && c <= 'Z')
				{
					// Grossbuchstabe
					upper[c - 'A']++;
				}
			}// for
		}// for
		// ==--== Ermittlung der einzelnen Haeufigkeiten

		// Ausgabe der einzelnen Haeufigkeiten
		c = 'a';// angefangen bei "a"...
		for (i = 0; i < 26; i++)
		{
			// Ausgabe des Buchstaben und der Haeufigkeit bei Klein- und Grossbuchstaben
			System.out.println(c + "\t" + lower[i] + "\t" + upper[i]);
			c++;// weiter zum naechsten Buchstaben
		}

		//==++== Suche nach dem haeufigsten Buchstaben
		c = ' ';// default "Buchstabe"
		int max = -1;// groesste gefundene Haeufigkeit
		// Durchlaufen beider Arrays parallel
		for (i = 0; i < 26; i++)
		{
			// wenn aktueller Buchstabe haeufiger als anderer zuvor
			if (lower[i] + upper[i] > max)
			{
				// merken der groessten Haeufigkeit
				max = lower[i] + upper[i];
				
				// merken des Buchstaben
				c = (char) ('a' + i);// Rechnung mit Zeichen (z.B.: 'a'+2='c')
			}
		}// for
		//==--== Suche nach dem haeufigsten Buchstaben

		// theoretisch steht jetzt in der Variable c der Buchstabe mit der
		// groessten Haeufigkeit im eingelesen Text, da bei deutschen Texten das 'e'
		// am Haeufigsten auftritt muss dies ein verschluesseltes 'e' sein

		int key = c - 'e';// ausrechnen des Schluessels
		
		// anpassen des Schluessels bei einem Overflow
		if (key < 0)
		{
			key += 26;
		}

		return key;// ermittelter Schluessel wird zurueckgegeben
	}// countLetter

	/**
	 * Methode decodiert einen Text bei gegebenen Schluessel.
	 * @param text zu entschluesselnder Text
	 * @param key Schluessel, mit dem verschluesslt wurde
	 * @return entschluesselter Text
	 */
	private static String decode(String text, int key)
	{
		// neuer StringBuilder um Ausgabetext zu "bauen"
		StringBuilder sb = new StringBuilder();

		int newC;// Code eines alten buchstaben
		int oldC;// Code des Buchstaben in neu

		// Schleife so lange der Text noch Zeichen enthaelt
		while (!text.isEmpty())
		{
			// einlesen des ersten Zeichens
			newC = oldC = text.charAt(0);

			// Ueberpruefung ob Zeichen ein normaler Buchstabe ist
			if (oldC >= 'a' && oldC <= 'z')
			{
				// Kleinbuchstabe
				newC = oldC - key;// Entschluesselung
				
				if (newC < 'a')// Behandlung von Overflows
				{
					newC += 26;// anpassen des Buchstaben
				}// if
			}
			else if (oldC >= 'A' && oldC <= 'Z')
			{
				// Grossbuchstabe
				newC = oldC - key;// Entschluesselung
				
				if (newC < 'A')// Behandlung von Overflows
				{
					newC += 26;// anpassen des Buchstaben
				}// if
			}// if
			else
			{
				// nix, da nur "normale" Buchstaben verschluesselt werden
			}

			// entschluesselter Buchstabe wird Text hinzugefuegt
			sb.append((char) newC);
			
			// der momentane Text wird auf den Rest (one erstes Zeichen) gesetzt
			text = text.substring(1);
		}// while

		return sb.toString();// entschluesselter text wird zurueckgegeben
	}// encode
}//class