Szyfr XOR/Vernama/Vigenere'a (2)
Szyfr Vigenere’a
Szyfr Vigenere’a w wersji klasycznej stosuje 26 znaków alfabetu angielskiego. Do szyfrowania używa się klucza, który jest pewnym słowem złożonym z powyższych liter.
Tworzy się tablicę, która określa zasady podstawień (Rys. 167):
Szyfrowanie
Szukamy litery wiadomości w rzędzie np. B, szukamy litery klucza w kolumnie np. C i odczytujemy literę zaszyfrowanej wiadomości, w tym przypadku D na przecięciu wiersza z kolumną. Oczywiście znaki mogą być przesuwane np. o dwie litery w każdym wierszu, etc.
Odszyfrowywanie
Odszyfrowywanie przebiega odwrotnie. Wyszukujemy literę wiadomości w kolumnie klucza np. C i odczytujemy literę w wierszu wiadomości.
Jeżeli do tabeli podstawień dodasz inne znaki, szczególnie narodowe, musisz pamiętać żeby do odszyfrowywania wiadomości użyć tej samej tablicy kodowej co nadawca wiadomości. W przypadku wiadomości szyfrowanej ręcznie nie ma to żadnego znaczenia, ale w wiadomości szyfrowanej na komputerze ma istotne znaczenie.
Klasa Vigenere.java
package crypto.vigenere; public class Vigenere{ public final static String[] CHARS = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\\", "]", "^", "_", "`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s"}; private final static int len = CHARS.length; private final static String REGEX = "[^A-s0-9ąćęłńóśżź]"; private final static int div = 63; private String klucz; private String wiadomosc; private final String kluczKompl; private final String wiadomoscZaszyfr; private final String[][] tablZnakow; private String wiadomoscOdszyfr; //Znaki dozwolone: litery łacińskie, litery polskie, cyfry, //spacja, przecinek, kropka, średnik, dwukropek, cudzysłów //inne znaki zostaną z tekstu usunięte. public Vigenere(String key, String message){ klucz = key.replace(' ', '['); wiadomosc = message.replace(' ', '['); klucz = klucz.replace(',', '\\'); wiadomosc = wiadomosc.replace(',', '\\'); klucz = klucz.replace('.', ']'); wiadomosc = wiadomosc.replace('.', ']'); klucz = klucz.replace(';', '^'); wiadomosc = wiadomosc.replace(';', '^'); klucz = klucz.replace(':', '_'); wiadomosc = wiadomosc.replace(':', '_'); klucz = klucz.replace('"', '`'); wiadomosc = wiadomosc.replace('"', '`'); klucz = oczysc(klucz); wiadomosc = oczysc(wiadomosc); wiadomosc = wiadomosc.replace('0', 'a'); klucz = klucz.replace('0', 'a'); wiadomosc = wiadomosc.replace('1', 'b'); klucz = klucz.replace('1', 'b'); wiadomosc = wiadomosc.replace('2', 'c'); klucz = klucz.replace('2', 'c'); wiadomosc = wiadomosc.replace('3', 'd'); klucz = klucz.replace('3', 'd'); wiadomosc = wiadomosc.replace('4', 'e'); klucz = klucz.replace('4', 'e'); wiadomosc = wiadomosc.replace('5', 'f'); klucz = klucz.replace('5', 'f'); wiadomosc = wiadomosc.replace('6', 'g'); klucz = klucz.replace('6', 'g'); wiadomosc = wiadomosc.replace('7', 'h'); klucz = klucz.replace('7', 'h'); wiadomosc = wiadomosc.replace('8', 'i'); klucz = klucz.replace('8', 'i'); wiadomosc = wiadomosc.replace('9', 'j'); klucz = klucz.replace('9', 'j'); wiadomosc = wiadomosc.replace('Ą', 'k'); klucz = klucz.replace('Ą', 'k'); wiadomosc = wiadomosc.replace('Ć', 'l'); klucz = klucz.replace('Ć', 'l'); wiadomosc = wiadomosc.replace('Ę', 'm'); klucz = klucz.replace('Ę', 'm'); wiadomosc = wiadomosc.replace('Ł', 'n'); klucz = klucz.replace('Ł', 'n'); wiadomosc = wiadomosc.replace('Ń', 'o'); klucz = klucz.replace('Ń', 'o'); wiadomosc = wiadomosc.replace('Ó', 'p'); klucz = klucz.replace('Ó', 'p'); wiadomosc = wiadomosc.replace('Ś', 'q'); klucz = klucz.replace('Ś', 'q'); wiadomosc = wiadomosc.replace('Ż', 'r'); klucz = klucz.replace('Ż', 'r'); wiadomosc = wiadomosc.replace('Ź', 's'); klucz = klucz.replace('Ź', 's'); wiadomosc = oczysc2(wiadomosc); klucz = oczysc2(klucz); kluczKompl = podstawKlucz(klucz, this.wiadomosc); tablZnakow = fillTable(); wiadomoscZaszyfr = zaszyfruj(tablZnakow, kluczKompl, wiadomosc); wiadomoscOdszyfr = odszyfruj(tablZnakow, kluczKompl, wiadomoscZaszyfr); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('[', ' '); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('\\', ','); wiadomoscOdszyfr = wiadomoscOdszyfr.replace(']', '.'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('^', ';'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('_', ':'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('`', '"'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('a', '0'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('b', '1'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('c', '2'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('d', '3'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('e', '4'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('f', '5'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('g', '6'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('h', '7'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('i', '8'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('j', '9'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('k', 'Ą'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('l', 'Ć'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('m', 'Ę'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('n', 'Ł'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('o', 'Ń'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('p', 'Ó'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('q', 'Ś'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('r', 'Ż'); wiadomoscOdszyfr = wiadomoscOdszyfr.replace('s', 'Ź'); } public String getWiadomoscZaszyfr() { return wiadomoscZaszyfr; } public String getWiadomoscOdszyfr() { return wiadomoscOdszyfr; } public String[][] getTablZnakow() { return tablZnakow; } public String getKluczKompl() { return kluczKompl; } public static String oczysc(String toClean) { String str = toClean; str = str.toUpperCase(); return str; } public static String oczysc2(String toClean) { String str = toClean; str = str.replaceAll(REGEX, ""); return str; } public static String podstawKlucz(String key, String message) { StringBuilder sb = new StringBuilder(); int counter = 0; for(int i = 0; i < message.length(); i++){ if(counter == key.length()){ counter = 0; } sb.append(key.charAt(counter)); counter++; } return sb.toString(); } private static String[][] fillTable() { String[][] tablica = new String[len][len]; int counter = 0; for(int i = 0; i < len; i++){ for(int j = 0; j < len; j++){ tablica[i][j] = CHARS[(i + counter) % len]; counter++; } } return tablica; } public String getKlucz() { return klucz; } public String getWiadomosc() { return wiadomosc; } public static void display(String[][] temp) { for(int i = 0; i < temp.length; i++){ System.out.print((i + 1) + "\t"); for(int j = 0; j < temp[i].length; j++){ System.out.print(temp[i][j] + " "); } System.out.println(); } System.out.println(); } private static String zaszyfruj(String[][] tablica, String key, String message) { String zaszyfr; StringBuilder sb = new StringBuilder(); for(int i = 0; i < message.length(); i++){ sb.append(tablica[(message.charAt(i) & div) - 1][(key.charAt(i) & div) - 1]); } zaszyfr = sb.toString(); return zaszyfr; } private static String odszyfruj(String[][] tablica, String key, String message) { StringBuilder sb = new StringBuilder(); for(int j = 0; j < message.length(); j++){ int kol = (key.charAt(j) & div) - 1; String str = message.substring(j, j + 1); for(int i = 0; i < len; i++){ String str2 = tablica[i][kol]; if(str2.equals(str)){ sb.append(tablica[i][0]); break; } } } return sb.toString(); } }
Wyniki
Klasa Crypto02.java
package crypto.vigenere; public class Crypto02 { public static void main(String[] args) { String klu = VigUtil.readFile("crypto/src/crypto/vigenere/assets/klucz1.txt"); String wiad = VigUtil.readFile("crypto/src/crypto/vigenere/assets/wiadomosc1.txt"); Vigenere ig = new Vigenere(klu, wiad); System.out.println("klucz: " + klu); System.out.println("wiadomosc: " + wiad); System.out.println("klucz wstepnie prztworzony: " + ig.getKlucz()); System.out.println("wiadomosc wstepnie przetworzona: " + ig.getWiadomosc()); System.out.println("klucz kompletnie przetworzony: " + ig.getKluczKompl()); System.out.println("wiadZaszyfr: " + ig.getWiadomoscZaszyfr()); System.out.println("wiadOdszyfr: " + ig.getWiadomoscOdszyfr()); VigUtil.writeFile(ig.getWiadomoscZaszyfr(), "crypto/src/crypto/vigenere/assets/zaszyfr1.txt"); VigUtil.writeFile(ig.getWiadomoscOdszyfr(), "crypto/src/crypto/vigenere/assets/odszyfr1.txt"); } }
Po uruchomieniu klasy Crypto02.java
otrzymujemy:
title="Szyfr XOR/Vernama/Vigenere'a" klucz: Litwo Ojczyzno moja Ty jesteś jak zdrowie. Ile cię trzeba cenić, tylko się wiadomosc: W Strzebrzeszynie chrząszcz brzmi w trzcinie i Strzebrzeszyn z tego słynie. klucz wstepnie prztworzony: LITWO[OJCZYZNO[MOJA[TY[JESTEq[JAK[ZDROWIE][ILE[CIm[TRZEBA[CENIl\[TYLKO[SIm wiadomosc wstepnie przetworzona: W[STRZEBRZESZYNIE[CHRZkSZCZ[BRZMI[W[TRZCINIE[I[STRZEBRZESZYN[Z[TEGO[SnYNIE] klucz kompletnie przetworzony: LITWO[OJCZYZNO[MOJA[TY[JESTEq[JAK[ZDROWIE][ILE[CIm[TRZEBA[CENIl\[TYLKO[SImL wiadZaszyfr: bcfj`ASKTs]lgghUSdCberR\^Um_rlcMSBp^e`pKMjcMfMBU\KAXSk^FSA[RhbSo_Zgf]Is`Qqh wiadOdszyfr: W STRZEBRZESZYNIE CHRZĄSZCZ BRZMI W TRZCINIE I STRZEBRZESZYN Z TEGO SŁYNIE.
Jak widzimy, wszystko się zgadza, z tym małym wyjątkiem, że wynik jest wyświetlony z użyciem wyłącznie dużych liter, chociaż treść jest poprawna. Przerobienie klasy tak, żeby wyświetlała poprawnie małe i duże litery – pozostawiam Czytelnikowi.
Tablica podstawień
Klasa Crypto03.java
package crypto.vigenere; public class Crypto03 { public static void main(String[] args) { String klu = VigUtil.readFile("crypto/src/crypto/vigenere/assets/klucz1.txt"); String wiad = VigUtil.readFile("crypto/src/crypto/vigenere/assets/wiadomosc1.txt"); Vigenere ig = new Vigenere(klu, wiad); Vigenere.display(ig.getTablZnakow()); } }
Po uruchomieniu tej klasy zobaczymy tablicę podstawień (Rys 168):
Klasy uzupełniające
Klasa FrequencyMap.java
package crypto.vigenere; import java.awt.*; import java.io.*; import java.util.*; /** * Klasa oblicza częstość występowania elementu typu T * * * @param <T> obiekt dowolnej klasy */ public class FrequencyMap<T> { private TreeMap<T, Integer> map; private PrintWriter pw; private int elementy; public FrequencyMap() { map = new TreeMap<>(); } public Point[] getAllPoints() { Set<Map.Entry<T, Integer>> set = map.entrySet(); int size = set.size(); Point[] points = new Point[size]; int i = 0; for (Map.Entry<T, Integer> entry : set) { points[i] = new Point((Integer) entry.getKey(), entry.getValue()); i++; } return points; } /** * Dodaje obiekt do kolekcji, a jeśli element już w niej jest * dodaje wystąpienie danego elementu. * * @param obj - dodawany element */ public void add(T obj) { Integer count = map.get(obj); if (count == null) { map.put(obj, 1); } else { map.put(obj, ++count); } elementy++; } /** * Usuwa wystąpienie danego elementu z kolekcji. Jeśli pozostał * tylko jeden element usuwa go z kolekcji * * @param obj - usuwany obiekt */ public void remove(T obj) { Integer count = map.get(obj); if (count != null) { if (count == 1) { map.remove(obj); } else { map.put(obj, --count); } } elementy--; } /** * drukuje liczbę wystąpień (częstość) danego obiektu * na konsoli * * @param obj - obiekt, którego liczbę wystąpień chcemy poznać */ public void print(T obj) { if (map.containsKey(obj)) { Set<Map.Entry<T, Integer>> set = map.entrySet(); for (Map.Entry<T, Integer> entry : set) { if ((entry.getKey()).equals(obj)) { System.out.println(entry.getKey() + " " + entry.getValue()); break; } } } } public long[] getAllAsLongs() { Tuple2L[] points = getAllAsTuplesL(); int sum = 0; int counter = 0; for (Tuple2L tuple2L : points) { sum += tuple2L.getY(); } long[] liczby = new long[sum]; for (Tuple2L point : points) { int licznik = (int) point.getY(); int liczba = (int) point.getX(); for (int m = 0; m < licznik; m++) { liczby[counter] = liczba; counter++; } } return liczby; } public Tuple2L[] getAllAsTuplesL() { Set<Map.Entry<T, Integer>> set = map.entrySet(); int size = set.size(); Tuple2L[] points = new Tuple2L[size]; int i = 0; for (Map.Entry<T, Integer> entry : set) { points[i] = new Tuple2L((Long) entry.getKey(), entry.getValue()); i++; } return points; } /** * drukuje na konsoli obiekty i ich częstości, w takiej kolejności * w jakiej są uporządkowane w mapie */ public int[] printAll() { Set<Map.Entry<T, Integer>> set = map.entrySet(); int i = 0; int[] tabl = new int[set.size()]; for (Map.Entry<T, Integer> entry : set) { System.out.println(entry.getKey() + " " + entry.getValue()); tabl[i] = entry.getValue(); i++; } return tabl; } /** * drukuje na konsoli obiekty i ich częstości uporządkowane * według malejącej częstości. */ public void printAllSorted() { ArrayList<Freq<T, Integer>> al = new ArrayList<>(); Set<Map.Entry<T, Integer>> set = map.entrySet(); for (Map.Entry<T, Integer> entry : set) { al.add(new Freq<>(entry.getKey(), entry.getValue())); } Collections.sort(al); for (Freq<T, Integer> f : al) { System.out.println(f); } } /** * Drukuje do wskazanego pliku obiekty i ich częstości uporządkowane * według malejącej częstości. * * @param plik - ścieżka do pliku. */ public void printAllSortedToFile(String plik) { try { pw = new PrintWriter(plik); } catch (FileNotFoundException e) { e.printStackTrace(); } ArrayList<Freq<T, Integer>> al = new ArrayList<>(); Set<Map.Entry<T, Integer>> set = map.entrySet(); for (Map.Entry<T, Integer> entry : set) { al.add(new Freq<>(entry.getKey(), entry.getValue())); } Collections.sort(al); for (Freq<T, Integer> f : al) { pw.println(f); } pw.close(); } public void setMap(TreeMap<T, Integer> map) { this.map = map; } /** * @return liczbę zsumowaną liczbę elementów w kolekcji. W przypadku * np. słów podaje łączną ogólną liczbę słów w kolekcji */ public int getElementy() { return elementy; } /** * Zwraca częstość występowania obiektu obj. W przypadku słów zwraca * liczbę wystąpień danego słowa * * @param obj - element, kórego częstość chcemy sprawdzić * @return - częstość (liczba wystąpień) elementu obj */ public int getFrequency(T obj) { int freq = 0; if (map.containsKey(obj)) { Set<Map.Entry<T, Integer>> set = map.entrySet(); for (Map.Entry<T, Integer> entry : set) { if ((entry.getKey()).equals(obj)) { freq = entry.getValue(); break; } } } return freq; } public int razem() { int a = 0; Set<Map.Entry<T, Integer>> set = map.entrySet(); for (Map.Entry<T, Integer> entry : set) { a += entry.getValue(); } return a; } /** * Podaje wielkość kolekcji. W przypadku słów jest to liczba różnych * słów w kolekcji * * @return - wielkość kolekcji */ public int getSize() { return map.size(); } /** * Getter * * @return - zwraca mapę */ public TreeMap<T, Integer> getMap() { return map; } /** * Metoda pomocnicza do wczytywania tekstów i podziału na tokeny (słowa) * * @param file - ścieżka do pliku * @return - mapę częstości słów w pliku file */ public static FrequencyMap<String> read(String file) { FrequencyMap<String> map = new FrequencyMap<>(); try { try (BufferedReader in = new BufferedReader( new FileReader(new File(file)))) { String s; while ((s = in.readLine()) != null) { StringTokenizer st = new StringTokenizer(s, " ,;."); while (st.hasMoreTokens()) { map.add(st.nextToken()); } } } } catch (IOException ioe) { throw new RuntimeException(ioe); } return map; } public void printAll2() { Set<Map.Entry<T, Integer>> set = map.entrySet(); for (Map.Entry<T, Integer> entry : set) { System.out.println(entry.getKey() + " " + entry.getValue()); } } } /** * Klasa pomocnicza pozwalająca na umieszczanie elementów * w arrayliście */ class Freq<K, V> implements Comparable<Freq<K, V>> { private K key; private V value; public Freq(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public void setKey(K key) { this.key = key; } public V getValue() { return value; } public void setValue(V value) { this.value = value; } @Override public String toString() { return "[" + key + ", " + value + "]"; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } if (!(obj instanceof Freq<?, ?>)) { return false; } Freq<?, ?> fr = (Freq<?, ?>) obj; return key.equals(fr.getKey()) && value.equals(fr.getValue()); } @Override public int hashCode() { return 17 * key.hashCode() + 19 * value.hashCode(); } @Override public int compareTo(Freq<K, V> o) { Integer i1 = (Integer) this.getValue(); Integer i2 = (Integer) o.getValue(); return i2.compareTo(i1); } }
Klasa Tuple2L.java
package crypto.vigenere; public class Tuple2L { private long x; private long y; public Tuple2L() { this(0L, 0L); } public Tuple2L(long x, long y) { this.x = x; this.y = y; } public double distance(Tuple2L tuple) { return Math.sqrt(x * tuple.getX() + y * tuple.getY()); } @Override public String toString() { return "[" + x + ", " + y + "]"; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } if (!(obj instanceof Tuple2L)) { return false; } Tuple2L t2 = (Tuple2L) obj; return (x == t2.getX()) && (y == t2.getY()); } @Override public int hashCode() { return 17 * Long.valueOf(x).hashCode() + 19 * Long.valueOf(y).hashCode(); } public long getX() { return x; } public void setX(long x) { this.x = x; } public long getY() { return y; } public void setY(long y) { this.y = y; } }
Klasa VigUtil.java
package crypto.vigenere; import java.io.*; import java.nio.*; import java.nio.charset.*; public class VigUtil { private VigUtil() { } public static String readFile(String file) { StringBuilder buf = new StringBuilder(); BufferedReader we = null; try { we = new BufferedReader(new FileReader(file)); } catch (FileNotFoundException e) { e.printStackTrace(); } String linia; try { while ((linia = we.readLine()) != null) { buf.append(linia); } } catch (IOException e) { e.printStackTrace(); } try { we.close(); } catch (IOException e) { e.printStackTrace(); } return buf.toString(); } public static String byteArrayToString(byte[] tabl) { Charset charset = Charset.forName("UTF-8"); ByteBuffer buf = ByteBuffer.wrap(tabl); CharBuffer cbuf = charset.decode(buf); return cbuf.toString(); } public static void writeFile(String wiad, String plik) { File file = new File(plik); BufferedWriter bw = null; try { bw = new BufferedWriter(new FileWriter(file)); } catch (IOException e1) { e1.printStackTrace(); } try { bw.write(wiad); bw.close(); } catch (IOException e) { e.printStackTrace(); } } }