Die Tests und der Aufbau der Testdateien sind nicht in allen Fällen sofort zu durchschauen, daher erläutere ich Euch die Tests am Beispiel des SHA-1 KAT.
Die Testdateien liegen in Form einer „Response“ (=“Antwort“) Datei vor, wobei vielfach für ähnliche Verfahren eigene Dateien vorhanden sind (beispielsweise für den SHA-256 und SHA-512-Test).
Diese Dateien haben nach dem „Header“ (=“Kopf“) Zeilen mit Konstanten – entweder für die gesamte Datei oder für einen Block von Testdaten. Danach folgen die Datensätze, welche Zeile für Zeile aufgeführt sind. Schauen wir uns den Anfang der Datei „SHA1ShortMsg.rsp“ an:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# CAVS 11.0 # "SHA-1 ShortMsg" information # SHA-1 tests are configured for BYTE oriented implementations # Generated on Tue Mar 15 08:23:35 2011 [L = 20] Len = 0 Msg = 00 MD = da39a3ee5e6b4b0d3255bfef95601890afd80709 Len = 8 Msg = 36 MD = c1dfd96eea8cc2b62785275bca38ac261256e278 Len = 16 Msg = 195a MD = 0a1c2d555bbe431ad6288af5a54f93e0449c9232 |
Die Konstante in der Zeile [L = 20] gibt an, das der Ausgabewerte eine Länge von (hexadezimal) „x20“ = 32 Byte hat. Die folgenden Datensätze haben folgende Bedeutung: „Len“ = Länge der Eingabedaten in die Berechnung (in Bit !), d.h. „Len = 8“ bedeutet eine Länge von einem Byte. „Msg = 36“ ist der Eingabewert für unsere SHA-1 Berechnung (als Hex-String „x36“ zu lesen). Die letzte Zeile „MD = c1dfd96eea8cc2b62785275bca38ac261256e278“ ist die Ausgabe der SHA-1 Berechnung, ebenfalls als Hex-String.
Eine Besonderheit bilden Datensätze mit der Länge „0“, denn dort ist der Eingabewert nicht „x00“, sondern „“. In den Kat-Einlesedateien (hier „KAT_SHA1.java“) bilde ich das brutal durch eine Abfrage ab (im Sourcecode unten in den Zeilen 92 sowie 10-104).
Auf diese Weise werde zuerst alle Datensätze in List-String-Arrays eingelesen und dann vom Hauptprogramm „Run_Sha1_Kat.java“ ausgewertet. Die Auswertung geschieht in der Zeile 71 („String mdCalculated = bytesToHex(sha_1(hexStringToByteArray(KAT_SHA1.msg.get(i))));„), wobei die Eingabe-Hex-Strings in ein Byte Array umgewandelt und das Ergebnis später wieder in einen Hex-String verwandelt wird. Die „eigentliche“ SHA-1-Berechnung findet sich in den Zeilen 127 bis 137 (Methode „public static byte[] sha_1 (byte[] input)„).
Viele Testverfahren arbeiten zusätzlich mit einem „Monte Carlo“ genannten Test, der gesondert durchzuführen ist. Hierbei enthalten die Response-Dateien nicht die Ergebnisse einer einzelnen Berechnung, sondern die Zwischenschritte nach jeweils 1.000 Berechnungen. Sehr hilfreich ist es dafür, sich die Beschreibung zu den Tests anzuschauen, im Beispiel sind es die für die „Secure Hash“-Verfahren – ladet Euch die Datei „http://csrc.nist.gov/groups/STM/cavp/documents/shs/SHAVS.pdf“ herunter und schaut auf die Seiten 12-14:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
6.4 The Pseudorandomly Generated Messages (Monte Carlo) Test The SHAVS tests the correctness of message digests generated from pseudorandomly generated messages by supplying a seed, Seed, of length n bits. This seed is used by a pseudorandom function to generate 100,000 message digests. 100 of the 100,000 message digests, once every 1,000 hashes, are recorded as checkpoints to the operation of the generator. The IUT uses the same procedure to generate the same 100,000 message digests and 100 checkpoint values. The SHAVS compares each of the recorded 100 message digests with those generated by the IUT. The procedure used to generate the 100 checkpoint messages digest is as follows: 1) 100,000 pseudorandom messages are generated by using previous message digests as the input to the hash algorithm; and, 2) After every 1,000 hashes a sample is taken and is provided as a checkpoint. INPUT: Seed - A random seed n bits long { for (j=0; j<100; j++) { MD 0 = MD 1 = MD 2 = Seed; for (i=3; i<1003; i++) { M i = MD i-3 || MD i-2 || MD i-1 ; MD i = SHA(M i ); } MD j = Seed = MD 1002 ; OUTPUT: MD j } } These checkpoints are denoted MD j in Figure 1. |
Diese Routine habe ich in der KAT-Auswertungsdatei „Run_Sha1_Kat_MonteCarlo.java“ so umgesetzt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
String initSeed = KAT_SHA1_MonteCarlo.seed.get(0); for (int j = 0; j < counterSize; j++) { // outer loop String md1 = initSeed; String md2 = initSeed; String md3 = initSeed; String mdCalculated = ""; for (int i = 3; i < 1003; i++) { // inner loop String mdCombined = md1 + md2 + md3; mdCalculated = bytesToHex(sha_1(hexStringToByteArray(mdCombined))); if (verbose) System.out.println("mdCalculated for i = " + i + " j = " + j + " md = " + mdCalculated); md1 = md2; md2 = md3; md3 = mdCalculated; } initSeed = mdCalculated; boolean testPassed = mdCalculated.contentEquals(KAT_SHA1_MonteCarlo.md.get(j)); if (verbose) System.out.println("md testPassed for counter " + j + " : " + testPassed); if (testPassed) { nrTestPassed[nrOfTest]++; } else { nrTestFailed[nrOfTest]++; } } |
Nachfolgend seht Ihr die KAT-Lesedatei (KAT_SHA1.java) sowie die Auswertungs-/Steuerdatei (Run_Sha1_Kat.java) – alle Dateien sowie die Ergebnis-/Result-Dateien findet Ihr im GitHub-Archiv https://github.com/java-crypto/Known_Answer_Tests.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
package SHA_1; /* * Herkunft/Origin: http://java-crypto.bplaced.net/ * Programmierer/Programmer: Michael Fehr * Copyright/Copyright: frei verwendbares Programm (Public Domain) * Copyright: This is free and unencumbered software released into the public domain. * Lizenttext/Licence: <http://unlicense.org> * getestet mit/tested with: Java Runtime Environment 11.0.6 x64 * verwendete IDE/used IDE: intelliJ IDEA 2019.3.4 * Datum/Date (dd.mm.jjjj): 17.05.2020 * Funktion: fuehrt den Known Answer Test (KAT) für SHA-1 durch * Function: performs the Known Answer Test (KAT) for SHA-1 * Beschreibung in / Description in * http://java-crypto.bplaced.net/sha-1-kat/ * * Sicherheitshinweis/Security notice * Die Programmroutinen dienen nur der Darstellung und haben keinen Anspruch auf eine korrekte Funktion, * insbesondere mit Blick auf die Sicherheit ! * Pruefen Sie die Sicherheit bevor das Programm in der echten Welt eingesetzt wird. * The program routines just show the function but please be aware of the security part - * check yourself before using in the real world ! * */ import java.io.BufferedReader; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; public class KAT_SHA1 { static List<String> header = new ArrayList<String>(); static List<String> len = new ArrayList<String>(); static List<String> msg = new ArrayList<String>(); static List<String> md = new ArrayList<String>(); static String lenFixed = ""; private static String filename; private static int readLines = 0; private static boolean headerPhase = false; static void init(String fn) { filename = fn; // clear variables readLines = 0; header.clear(); len.clear(); msg.clear(); md.clear(); headerPhase = true; } static String getFilename() { return filename; } static int getReadlines() { return readLines; } static boolean parse() { int zeilennummer = 0; try (BufferedReader br = Files.newBufferedReader(Paths.get(filename))) { // read line by line String line; while ((line = br.readLine()) != null) { zeilennummer++; analyzeLine(line); } } catch (IOException e) { System.err.format("IOException: %s%n", e); } // auto closure return true; } static void analyzeLine(String line) { readLines++; // headerPhase ? if (headerPhase) { if (line.startsWith("#")) { header.add(line); } else { // headerPhase ends headerPhase = false; } } if (!headerPhase) { // analyzing data if (line.startsWith("Len")) { // Len = 0 if (line.contentEquals("Len = 0")) { lenFixed = "0"; } else lenFixed = "1"; len.add(line.replace("Len = ", "")); } if (line.startsWith("Msg")) { // Msg = 00 if (lenFixed == "0") { msg.add(""); } else { msg.add(line.replace("Msg = ", "")); } } if (line.startsWith("MD")) { // MD = da39a3ee5e6b4b0d3255bfef95601890afd80709 md.add(line.replace("MD = ", "")); } } } } |
Hauptprogramm:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
package SHA_1; /* * Herkunft/Origin: http://java-crypto.bplaced.net/ * Programmierer/Programmer: Michael Fehr * Copyright/Copyright: frei verwendbares Programm (Public Domain) * Copyright: This is free and unencumbered software released into the public domain. * Lizenttext/Licence: <http://unlicense.org> * getestet mit/tested with: Java Runtime Environment 11.0.6 x64 * verwendete IDE/used IDE: intelliJ IDEA 2019.3.4 * Datum/Date (dd.mm.jjjj): 17.05.2020 * Funktion: fuehrt den Known Answer Test (KAT) für SHA-1 durch * Function: performs the Known Answer Test (KAT) for SHA-1 * Beschreibung in / Description in * http://java-crypto.bplaced.net/sha-1-kat/ * * Sicherheitshinweis/Security notice * Die Programmroutinen dienen nur der Darstellung und haben keinen Anspruch auf eine korrekte Funktion, * insbesondere mit Blick auf die Sicherheit ! * Pruefen Sie die Sicherheit bevor das Programm in der echten Welt eingesetzt wird. * The program routines just show the function but please be aware of the security part - * check yourself before using in the real world ! * */ import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; public class Run_Sha1_Kat { public static void main(String[] args) throws IOException { TeePrintStream ts = new TeePrintStream(System.out, "SHA1_KAT_Results.txt", true); System.setOut(ts); System.out.println("SHA-1 Known Answer Test (KAT)"); System.out.println("for information see: https://csrc.nist.gov/Projects/cryptographic-algorithm-validation-program/Secure-Hashing"); System.out.println("for tests see: https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/shs/SHAVS.pdf"); System.out.println("get the testfiles: https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Algorithm-Validation-Program/documents/shs/shabytetestvectors.zip"); System.out.println("\nTest on " + getActualDate() + " with Java version: " + Runtime.version()); String filenameTest = ""; boolean verbose = false; // true = print all data, false = print just results // statistics int nrOfTestfiles = 2; int nrOfTest = 0; String[] filename = new String[nrOfTestfiles]; int[] nrTestdata = new int[nrOfTestfiles]; int[] nrTestPassed = new int[nrOfTestfiles]; int[] nrTestFailed = new int[nrOfTestfiles]; String[] filenames = {"kat/sha_1/SHA1ShortMsg.rsp", "kat/sha_1/SHA1LongMsg.rsp"}; for (int algs = 0; algs < filenames.length; algs++) { filenameTest = filenames[algs]; KAT_SHA1.init(filenameTest); filename[nrOfTest] = filenameTest; System.out.println("\ntesting filename: " + KAT_SHA1.getFilename()); KAT_SHA1.parse(); System.out.println("readLines: " + KAT_SHA1.getReadlines()); System.out.println("header lines: " + KAT_SHA1.header.size()); // output data int counterSize = KAT_SHA1.len.size(); System.out.println("nr of test vectors: " + counterSize); for (int i = 0; i < counterSize; i++) { if (verbose) System.out.println("len: " + KAT_SHA1.len.get(i) + " msg: " + KAT_SHA1.msg.get(i) + " md: " + KAT_SHA1.md.get(i) ); } // now we are testing the data System.out.println("testing the data"); for (int i = 0; i < counterSize; i++) { String mdCalculated = bytesToHex(sha_1(hexStringToByteArray(KAT_SHA1.msg.get(i)))); boolean testPassed = mdCalculated.contentEquals(KAT_SHA1.md.get(i)); if (verbose) System.out.println("md testPassed for counter " + i + " : " + testPassed); if (testPassed) { nrTestPassed[nrOfTest]++; } else { nrTestFailed[nrOfTest]++; } } nrTestdata[nrOfTest] = counterSize; nrOfTest++; } System.out.println("\nTest results"); System.out.println("filename tests passed failed"); for (int i = 0; i < nrOfTestfiles; i++) { System.out.format("%-28s%5d%8d%8d%n", filename[i], nrTestdata[i], nrTestPassed[i], nrTestFailed[i]); } ts.close(); } public static byte[] sha_1(byte[] input) { MessageDigest md = null; try { md = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } byte[] result = md.digest(input); return result; } private static String bytesToHex(byte[] bytes) { StringBuffer result = new StringBuffer(); for (byte b : bytes) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1)); return result.toString(); } public static byte[] hexStringToByteArray(String s) { int len = s.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); } return data; } private static String getActualDate() { // provides the actual date and time in this format dd-MM-yyyy_HH-mm-ss e.g. 16-03-2020_10-27-15 DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm:ss"); LocalDateTime today = LocalDateTime.now(); return formatter.format(today); } } |
Alle Quellcodes und die dazu gehörenden Testdateien zu den Known Answer Tests findet Ihr zum Download in meinem GitHub-Repository Known_Answer_Tests, welches Ihr über diesen Link erreicht: https://github.com/java-crypto/Known_Answer_Tests. Alle Programme sind unter Java 11 lauffähig (vermutlich auch unter Java 8) und wurden mit intelliJ IDEA entwickelt, welches für dieses Programm aber nicht notwendig ist.
Noch ein Wort zum Thema „Lizenz“: Die von mir erstellten Beispiele stehen unter der „Unlicense“-Lizenz.
Letzte Bearbeitung: 17.05.2020