From 18515ea4cdda90949de78eef610c3e2459c772b0 Mon Sep 17 00:00:00 2001 From: Simon Freiermuth Date: Fri, 18 Mar 2022 11:25:54 +0100 Subject: [PATCH 01/10] add bin to gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3de6611..d9ea881 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .DS_Store build .gradle -.idea \ No newline at end of file +.idea +bin \ No newline at end of file From 2d53d984373532d329398c05f5111b0650008545 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Sat, 19 Mar 2022 16:58:22 +0100 Subject: [PATCH 02/10] =?UTF-8?q?keys=20that=20are=20not=200=20or=20r=20ne?= =?UTF-8?q?ed=20to=20be=20permutated=20during=20encryption=20=F0=9F=A4=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/ch/fhnw/kry/SPN.java | 2 +- src/test/java/ch/fhnw/kry/SPNTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/fhnw/kry/SPN.java b/src/main/java/ch/fhnw/kry/SPN.java index 3b9ef91..a0f83b3 100644 --- a/src/main/java/ch/fhnw/kry/SPN.java +++ b/src/main/java/ch/fhnw/kry/SPN.java @@ -88,7 +88,7 @@ public class SPN { for (int i = 3; i > 0; i--) { x = substitution(x, SBOX_REVERSE); x = permutation(x); - x ^= k(key, i); + x ^= permutation(k(key, i)); } x = substitution(x, SBOX_REVERSE); diff --git a/src/test/java/ch/fhnw/kry/SPNTest.java b/src/test/java/ch/fhnw/kry/SPNTest.java index 782408b..4c18b3f 100644 --- a/src/test/java/ch/fhnw/kry/SPNTest.java +++ b/src/test/java/ch/fhnw/kry/SPNTest.java @@ -63,7 +63,7 @@ class SPNTest { } @Test - void sp() { + void blockEncryptionDecryption() { var spn = new SPN(); int x = 0x128F ; From 1fd74c6ec388b863857c9a5e4379470a87033138 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Sat, 19 Mar 2022 18:14:16 +0100 Subject: [PATCH 03/10] add documentation to spn functions --- src/main/java/ch/fhnw/kry/SPN.java | 86 +++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 14 deletions(-) diff --git a/src/main/java/ch/fhnw/kry/SPN.java b/src/main/java/ch/fhnw/kry/SPN.java index a0f83b3..33a14a4 100644 --- a/src/main/java/ch/fhnw/kry/SPN.java +++ b/src/main/java/ch/fhnw/kry/SPN.java @@ -67,36 +67,59 @@ public class SPN { return null; } + /** + * Encrypt a block with the defined SPN. + * + * @param key Key for encryption (round keys get derived from this). + * @param x Input block (high 16 bits get ignored). + * @return Encrypted block. + */ public int encryptBlock(int key, int x) { - x = init(key, x, 0); + x = init(key, x, 0); // initialer Weisschritt - for (int i = 1; i < 4; i++) { - x = substitution(x, SBOX); - x = permutation(x); - x ^= k(key, i); + for (int i = 1; i < 4; i++) { // for rounds > 0 && < r + x = substitution(x, SBOX); // run through s-box + x = permutation(x); // permutate + x ^= k(key, i); // xor with derived round key } - x = substitution(x, SBOX); - x ^= k(key, 4); + x = substitution(x, SBOX); // run through s-box + x ^= k(key, 4); // xor with last round key return x; } + /** + * Decrypt a block with the defined SPN. + * + * @param key Key for decryption (round keys get derived from this). + * @param x Encrypted block (high 16 bits get ignored). + * @return Decrypted block. + */ public int decryptBlock(int key, int x) { - x = init(key, x, 4); + x = init(key, x, 4); // initialer Weisschritt - for (int i = 3; i > 0; i--) { - x = substitution(x, SBOX_REVERSE); - x = permutation(x); - x ^= permutation(k(key, i)); + for (int i = 3; i > 0; i--) { // for rounds > 0 && < r, going in reverse + x = substitution(x, SBOX_REVERSE); // run through reverse s-box + x = permutation(x); // permutate + x ^= permutation(k(key, i)); // xor with permutated derived round key } - x = substitution(x, SBOX_REVERSE); - x ^= k(key, 0); + x = substitution(x, SBOX_REVERSE); // run through reverse s-box + x ^= k(key, 0); // xor with round key 0 return x; } + /** + * Derive a round key from a key by multiplying + * the round number with four and taking the next 16 + * bits from that position in the key. + * + * @param key Key as input. + * @param i Round number (starts at 0). + * @return Derived key. + */ public int k(int key, int i) { i *= 4; int mask = 0xFFFF_0000 >>> i; @@ -104,10 +127,27 @@ public class SPN { return (key & mask) >>> ROUND_KEY_LENGTH - i; } + /** + * Initialer Weisschritt. + * + * XOR an input with the key of a specific round. + * + * @param key Key to derive the round key. + * @param x Input. + * @param r Round number (starts at 0). + * @return Input xored with the derived round key. + */ public int init(int key, int x, int r) { return x ^ k(key, r); } + /** + * Permutate an input by swapping all its lower 16 bits as defined + * by a map. + * + * @param x Initial state (the high 16 bits of the integer are ignored). + * @return End state, after running all permutations. + */ public int permutation(int x) { Map blocked = new HashMap<>(); @@ -121,6 +161,13 @@ public class SPN { return x; } + /** + * Substitute every four bits of an input by running them through an S-Box. + * + * @param x Initial state (the high 16 bits of the integer are ignored). + * @param sbox A mapping consisting of hex keys and values. + * @return End state, after running all substitutions. + */ public int substitution(int x, Map sbox) { int mask = 0xF000; for (int i = 0; i < 4; i++) { @@ -135,7 +182,18 @@ public class SPN { return x; } + /** + * Swap two bits. + * + * Because we work with blocks of 16 bits, the high bits of the int input x are ignored. + * + * @param x Value where the bits get swapped (high 16 bits get ignored). + * @param a Position of the first bit to swap. + * @param b Position of the second bit to swap. + * @return Input value with bits in position a and b swapped. + */ public int swapBits(int x, int a, int b) { + // calculate the position respective to the 32 bits, hence ignoring the high 16 bits of x a = 15 - a; b = 15 - b; From 55876a7d3348f5d6008896a7e6fb602fbd8ab95f Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Mon, 21 Mar 2022 09:38:13 +0100 Subject: [PATCH 04/10] add bit string to int array conversion --- src/main/java/ch/fhnw/kry/SPN.java | 23 +++++++++++++++++++++++ src/test/java/ch/fhnw/kry/SPNTest.java | 12 ++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/main/java/ch/fhnw/kry/SPN.java b/src/main/java/ch/fhnw/kry/SPN.java index 33a14a4..7d2e3c6 100644 --- a/src/main/java/ch/fhnw/kry/SPN.java +++ b/src/main/java/ch/fhnw/kry/SPN.java @@ -67,6 +67,29 @@ public class SPN { return null; } + /** + * Convert a bit string into an integer array. + * + * Because later we only ever look at the lower 16 bits, + * we stuff every block of 16 bits into the lower half of an int + * (meaning the igh 16 bits of the ints in the array are always 0). + * + * @param bits Bit string. Its length must be a multiple of 16. + * @return int array, with every int's lower 16 bits set to 16 bits of the input string. + */ + public int[] strToArray(String bits) { + int[] data = new int[bits.length() / 16]; + + for (int i = 0; i < data.length; i++) { + int startIdx = i * 16; + String wordBits = bits.substring(startIdx, startIdx + 16); + int word = Integer.parseInt(wordBits, 2); + data[i] = word; + } + + return data; + } + /** * Encrypt a block with the defined SPN. * diff --git a/src/test/java/ch/fhnw/kry/SPNTest.java b/src/test/java/ch/fhnw/kry/SPNTest.java index 4c18b3f..dfbccd3 100644 --- a/src/test/java/ch/fhnw/kry/SPNTest.java +++ b/src/test/java/ch/fhnw/kry/SPNTest.java @@ -73,4 +73,16 @@ class SPNTest { assertEquals(y, spn.encryptBlock(key, x)); assertEquals(x, spn.decryptBlock(key, y)); } + + @Test + void strToArray() { + var spn = new SPN(); + + String input = "00000100110100100000101110111000000000101000111110001110011111110110000001010001010000111010000000010011011001110010101110110000"; + int[] data = spn.strToArray(input); + + assertEquals(8, data.length); + assertEquals(0x04D2, data[0]); + assertEquals(0x2BB0, data[data.length - 1]); + } } From 18cdd1ed8119d1cec9b5f5ea6d81ec3f2a74a287 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Mon, 21 Mar 2022 09:42:50 +0100 Subject: [PATCH 05/10] factor out some constants --- src/main/java/ch/fhnw/kry/SPN.java | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/java/ch/fhnw/kry/SPN.java b/src/main/java/ch/fhnw/kry/SPN.java index 7d2e3c6..8c39bee 100644 --- a/src/main/java/ch/fhnw/kry/SPN.java +++ b/src/main/java/ch/fhnw/kry/SPN.java @@ -62,6 +62,9 @@ public class SPN { entry(15, 15) ); + private static final int BLOCK_LENGTH = 16; + private static final int ROUNDS = 4; + public String decrypt(int key, String chiffre) { return null; @@ -78,11 +81,11 @@ public class SPN { * @return int array, with every int's lower 16 bits set to 16 bits of the input string. */ public int[] strToArray(String bits) { - int[] data = new int[bits.length() / 16]; + int[] data = new int[bits.length() / BLOCK_LENGTH]; for (int i = 0; i < data.length; i++) { - int startIdx = i * 16; - String wordBits = bits.substring(startIdx, startIdx + 16); + int startIdx = i * BLOCK_LENGTH; + String wordBits = bits.substring(startIdx, startIdx + BLOCK_LENGTH); int word = Integer.parseInt(wordBits, 2); data[i] = word; } @@ -100,14 +103,14 @@ public class SPN { public int encryptBlock(int key, int x) { x = init(key, x, 0); // initialer Weisschritt - for (int i = 1; i < 4; i++) { // for rounds > 0 && < r + for (int i = 1; i < ROUNDS; i++) { // for rounds > 0 && < r x = substitution(x, SBOX); // run through s-box x = permutation(x); // permutate x ^= k(key, i); // xor with derived round key } x = substitution(x, SBOX); // run through s-box - x ^= k(key, 4); // xor with last round key + x ^= k(key, ROUNDS); // xor with last round key return x; } @@ -120,9 +123,9 @@ public class SPN { * @return Decrypted block. */ public int decryptBlock(int key, int x) { - x = init(key, x, 4); // initialer Weisschritt + x = init(key, x, ROUNDS); // initialer Weisschritt - for (int i = 3; i > 0; i--) { // for rounds > 0 && < r, going in reverse + for (int i = ROUNDS - 1; i > 0; i--) { // for rounds > 0 && < r, going in reverse x = substitution(x, SBOX_REVERSE); // run through reverse s-box x = permutation(x); // permutate x ^= permutation(k(key, i)); // xor with permutated derived round key @@ -144,7 +147,7 @@ public class SPN { * @return Derived key. */ public int k(int key, int i) { - i *= 4; + i *= ROUNDS; int mask = 0xFFFF_0000 >>> i; return (key & mask) >>> ROUND_KEY_LENGTH - i; @@ -174,7 +177,7 @@ public class SPN { public int permutation(int x) { Map blocked = new HashMap<>(); - for (int i = 0; i < 16; i++) { + for (int i = 0; i < BLOCK_LENGTH; i++) { int target = PERMUTATION.get(i); if (!blocked.containsKey(target)) { x = swapBits(x, i, target); From 3bc74649ad163424ec4fa362a53fb82ff2501df7 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Tue, 22 Mar 2022 17:21:33 +0100 Subject: [PATCH 06/10] push garbage --- src/main/java/ch/fhnw/kry/CTR.java | 34 +++++++++++++++ src/main/java/ch/fhnw/kry/Decrypt.java | 49 ++++++++++++++++++++++ src/main/java/ch/fhnw/kry/Main.java | 6 ++- src/main/java/ch/fhnw/kry/SPN.java | 31 +------------- src/test/java/ch/fhnw/kry/CTRTest.java | 20 +++++++++ src/test/java/ch/fhnw/kry/DecryptTest.java | 28 +++++++++++++ src/test/java/ch/fhnw/kry/SPNTest.java | 12 ------ 7 files changed, 137 insertions(+), 43 deletions(-) create mode 100644 src/main/java/ch/fhnw/kry/CTR.java create mode 100644 src/main/java/ch/fhnw/kry/Decrypt.java create mode 100644 src/test/java/ch/fhnw/kry/CTRTest.java create mode 100644 src/test/java/ch/fhnw/kry/DecryptTest.java diff --git a/src/main/java/ch/fhnw/kry/CTR.java b/src/main/java/ch/fhnw/kry/CTR.java new file mode 100644 index 0000000..7987cc9 --- /dev/null +++ b/src/main/java/ch/fhnw/kry/CTR.java @@ -0,0 +1,34 @@ +package ch.fhnw.kry; + +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +public class CTR { + private final SPN spn = new SPN(); + private final int iv; + private final int key; + + public CTR(int key) { + this.iv = generateIV(); + this.key = key; + } + + public int getIV() { + return iv; + } + + public int generateIV() { + Random random = ThreadLocalRandom.current(); + byte[] r = new byte[2]; + random.nextBytes(r); + + return r[0] << 8 & r[1]; + } + + public int decrypt(int block, int idx) { + int e = (iv + idx) % (1 << 16); + e = spn.decryptBlock(key, e); + + return block ^ e; + } +} diff --git a/src/main/java/ch/fhnw/kry/Decrypt.java b/src/main/java/ch/fhnw/kry/Decrypt.java new file mode 100644 index 0000000..c4c85e8 --- /dev/null +++ b/src/main/java/ch/fhnw/kry/Decrypt.java @@ -0,0 +1,49 @@ +package ch.fhnw.kry; + +import java.nio.charset.StandardCharsets; + +import static ch.fhnw.kry.Main.BLOCK_LENGTH; + +public class Decrypt { + public String decrypt(String keyString, String chiffre) { + int key = Integer.parseInt(keyString, 2); + var ctr = new CTR(key); + + int[] data = strToArray(chiffre); + byte[] decryptedData = new byte[data.length * 2]; + for (int i = 0; i < data.length; i++) { + int block = data[i]; + int decryptedBlock = ctr.decrypt(block, i); + + decryptedData[i * 2] = (byte)(decryptedBlock >>> 8); + decryptedData[i * 2 + 1] = (byte)(decryptedBlock & 0x0000_00FF); + } + + String decryptedString = new String(decryptedData, StandardCharsets.US_ASCII); + + return decryptedString; + } + + /** + * Convert a bit string into an integer array. + * + * Because later we only ever look at the lower 16 bits, + * we stuff every block of 16 bits into the lower half of an int + * (meaning the igh 16 bits of the ints in the array are always 0). + * + * @param bits Bit string. Its length must be a multiple of 16. + * @return int array, with every int's lower 16 bits set to 16 bits of the input string. + */ + public int[] strToArray(String bits) { + int[] data = new int[bits.length() / BLOCK_LENGTH]; + + for (int i = 0; i < data.length; i++) { + int startIdx = i * BLOCK_LENGTH; + String wordBits = bits.substring(startIdx, startIdx + BLOCK_LENGTH); + int word = Integer.parseInt(wordBits, 2); + data[i] = word; + } + + return data; + } +} diff --git a/src/main/java/ch/fhnw/kry/Main.java b/src/main/java/ch/fhnw/kry/Main.java index e260c3d..20ad38f 100644 --- a/src/main/java/ch/fhnw/kry/Main.java +++ b/src/main/java/ch/fhnw/kry/Main.java @@ -5,10 +5,12 @@ package ch.fhnw.kry; */ public class Main { - private static final String KEY = "00111010100101001101011000111111"; - private static final String CHIFFRE = + static final String KEY = "00111010100101001101011000111111"; + static final String CHIFFRE = "00000100110100100000101110111000000000101000111110001110011111110110000001010001010000111010000000010011011001110010101110110000"; + static final int BLOCK_LENGTH = 16; + public static void main(String[] args) { } diff --git a/src/main/java/ch/fhnw/kry/SPN.java b/src/main/java/ch/fhnw/kry/SPN.java index 8c39bee..da2da63 100644 --- a/src/main/java/ch/fhnw/kry/SPN.java +++ b/src/main/java/ch/fhnw/kry/SPN.java @@ -3,6 +3,7 @@ package ch.fhnw.kry; import java.util.HashMap; import java.util.Map; +import static ch.fhnw.kry.Main.BLOCK_LENGTH; import static java.util.Map.entry; public class SPN { @@ -62,37 +63,9 @@ public class SPN { entry(15, 15) ); - private static final int BLOCK_LENGTH = 16; + private static final int ROUNDS = 4; - public String decrypt(int key, String chiffre) { - - return null; - } - - /** - * Convert a bit string into an integer array. - * - * Because later we only ever look at the lower 16 bits, - * we stuff every block of 16 bits into the lower half of an int - * (meaning the igh 16 bits of the ints in the array are always 0). - * - * @param bits Bit string. Its length must be a multiple of 16. - * @return int array, with every int's lower 16 bits set to 16 bits of the input string. - */ - public int[] strToArray(String bits) { - int[] data = new int[bits.length() / BLOCK_LENGTH]; - - for (int i = 0; i < data.length; i++) { - int startIdx = i * BLOCK_LENGTH; - String wordBits = bits.substring(startIdx, startIdx + BLOCK_LENGTH); - int word = Integer.parseInt(wordBits, 2); - data[i] = word; - } - - return data; - } - /** * Encrypt a block with the defined SPN. * diff --git a/src/test/java/ch/fhnw/kry/CTRTest.java b/src/test/java/ch/fhnw/kry/CTRTest.java new file mode 100644 index 0000000..2aa54f2 --- /dev/null +++ b/src/test/java/ch/fhnw/kry/CTRTest.java @@ -0,0 +1,20 @@ +package ch.fhnw.kry; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class CTRTest { + + @Test + void generateIV() { + var ctr = new CTR(0); + + int count = 0; + while(count < Integer.MAX_VALUE) { + int iv = ctr.generateIV(); + assert (iv < 1 << 16); + count++; + } + } +} diff --git a/src/test/java/ch/fhnw/kry/DecryptTest.java b/src/test/java/ch/fhnw/kry/DecryptTest.java new file mode 100644 index 0000000..139b406 --- /dev/null +++ b/src/test/java/ch/fhnw/kry/DecryptTest.java @@ -0,0 +1,28 @@ +package ch.fhnw.kry; + +import org.junit.jupiter.api.Test; + +import static ch.fhnw.kry.Main.CHIFFRE; +import static ch.fhnw.kry.Main.KEY; +import static org.junit.jupiter.api.Assertions.*; + +class DecryptTest { + + @Test + void strToArray() { + var decrypt = new Decrypt(); + + String input = "00000100110100100000101110111000000000101000111110001110011111110110000001010001010000111010000000010011011001110010101110110000"; + int[] data = decrypt.strToArray(input); + + assertEquals(8, data.length); + assertEquals(0x04D2, data[0]); + assertEquals(0x2BB0, data[data.length - 1]); + } + + @Test + void decrypt() { + var decrypt = new Decrypt(); + String decrypted = decrypt.decrypt(KEY, CHIFFRE); + } +} diff --git a/src/test/java/ch/fhnw/kry/SPNTest.java b/src/test/java/ch/fhnw/kry/SPNTest.java index dfbccd3..4c18b3f 100644 --- a/src/test/java/ch/fhnw/kry/SPNTest.java +++ b/src/test/java/ch/fhnw/kry/SPNTest.java @@ -73,16 +73,4 @@ class SPNTest { assertEquals(y, spn.encryptBlock(key, x)); assertEquals(x, spn.decryptBlock(key, y)); } - - @Test - void strToArray() { - var spn = new SPN(); - - String input = "00000100110100100000101110111000000000101000111110001110011111110110000001010001010000111010000000010011011001110010101110110000"; - int[] data = spn.strToArray(input); - - assertEquals(8, data.length); - assertEquals(0x04D2, data[0]); - assertEquals(0x2BB0, data[data.length - 1]); - } } From e04a341fc6914a5cffba50bbede63f439ff87031 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Tue, 22 Mar 2022 19:38:52 +0100 Subject: [PATCH 07/10] read iv from chiffre (still not correct but at least less wrong now) --- src/main/java/ch/fhnw/kry/CTR.java | 6 +++--- src/main/java/ch/fhnw/kry/Decrypt.java | 6 ++++-- src/test/java/ch/fhnw/kry/CTRTest.java | 4 +--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/ch/fhnw/kry/CTR.java b/src/main/java/ch/fhnw/kry/CTR.java index 7987cc9..6971620 100644 --- a/src/main/java/ch/fhnw/kry/CTR.java +++ b/src/main/java/ch/fhnw/kry/CTR.java @@ -8,8 +8,8 @@ public class CTR { private final int iv; private final int key; - public CTR(int key) { - this.iv = generateIV(); + public CTR(int iv, int key) { + this.iv = iv; this.key = key; } @@ -17,7 +17,7 @@ public class CTR { return iv; } - public int generateIV() { + public static int generateIV() { Random random = ThreadLocalRandom.current(); byte[] r = new byte[2]; random.nextBytes(r); diff --git a/src/main/java/ch/fhnw/kry/Decrypt.java b/src/main/java/ch/fhnw/kry/Decrypt.java index c4c85e8..2868fd1 100644 --- a/src/main/java/ch/fhnw/kry/Decrypt.java +++ b/src/main/java/ch/fhnw/kry/Decrypt.java @@ -7,11 +7,13 @@ import static ch.fhnw.kry.Main.BLOCK_LENGTH; public class Decrypt { public String decrypt(String keyString, String chiffre) { int key = Integer.parseInt(keyString, 2); - var ctr = new CTR(key); int[] data = strToArray(chiffre); byte[] decryptedData = new byte[data.length * 2]; - for (int i = 0; i < data.length; i++) { + int iv = data[0]; + var ctr = new CTR(iv, key); + + for (int i = 1; i < data.length; i++) { int block = data[i]; int decryptedBlock = ctr.decrypt(block, i); diff --git a/src/test/java/ch/fhnw/kry/CTRTest.java b/src/test/java/ch/fhnw/kry/CTRTest.java index 2aa54f2..01efc92 100644 --- a/src/test/java/ch/fhnw/kry/CTRTest.java +++ b/src/test/java/ch/fhnw/kry/CTRTest.java @@ -8,11 +8,9 @@ class CTRTest { @Test void generateIV() { - var ctr = new CTR(0); - int count = 0; while(count < Integer.MAX_VALUE) { - int iv = ctr.generateIV(); + int iv = CTR.generateIV(); assert (iv < 1 << 16); count++; } From e1c2d7df7d0ef6591e252ab2e92dfed962aea070 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Tue, 22 Mar 2022 21:07:04 +0100 Subject: [PATCH 08/10] Fix CTR decryption. Off by one error in respect to the y blocks. --- src/main/java/ch/fhnw/kry/CTR.java | 34 +++++++++--- src/main/java/ch/fhnw/kry/Decrypt.java | 61 +++++++++++++++++----- src/main/java/ch/fhnw/kry/Main.java | 12 ++++- src/main/java/ch/fhnw/kry/SPN.java | 20 ++++--- src/test/java/ch/fhnw/kry/CTRTest.java | 4 +- src/test/java/ch/fhnw/kry/DecryptTest.java | 9 ++-- src/test/java/ch/fhnw/kry/SPNTest.java | 10 ++-- 7 files changed, 105 insertions(+), 45 deletions(-) diff --git a/src/main/java/ch/fhnw/kry/CTR.java b/src/main/java/ch/fhnw/kry/CTR.java index 6971620..7c484ce 100644 --- a/src/main/java/ch/fhnw/kry/CTR.java +++ b/src/main/java/ch/fhnw/kry/CTR.java @@ -3,6 +3,11 @@ package ch.fhnw.kry; import java.util.Random; import java.util.concurrent.ThreadLocalRandom; +import static ch.fhnw.kry.Main.BLOCK_LENGTH; + +/** + * Implementation of CTR mode for decryption. + */ public class CTR { private final SPN spn = new SPN(); private final int iv; @@ -13,10 +18,11 @@ public class CTR { this.key = key; } - public int getIV() { - return iv; - } - + /** + * Generate a 16 bit long initialisation vector. + * + * @return Generated initialisation vector. + */ public static int generateIV() { Random random = ThreadLocalRandom.current(); byte[] r = new byte[2]; @@ -25,10 +31,22 @@ public class CTR { return r[0] << 8 & r[1]; } - public int decrypt(int block, int idx) { - int e = (iv + idx) % (1 << 16); - e = spn.decryptBlock(key, e); + public int getIV() { + return iv; + } - return block ^ e; + /** + * Decrypt a block in CTR mode. + * + * @param block Encrypted block (only lower 16 bits get looked at). + * @param idx Block index. + * @param y Y at index idx. + * @return Decrypted block (in the lower 16 bits of the int). + */ + public int decrypt(int block, int idx, int y) { + int e = (iv + idx) % (1 << BLOCK_LENGTH); // iv + i mod 2^16 + e = spn.encryptBlock(key, e); // yes, we need the encryption function, as this is CTR + + return y ^ e; } } diff --git a/src/main/java/ch/fhnw/kry/Decrypt.java b/src/main/java/ch/fhnw/kry/Decrypt.java index 2868fd1..7086e54 100644 --- a/src/main/java/ch/fhnw/kry/Decrypt.java +++ b/src/main/java/ch/fhnw/kry/Decrypt.java @@ -4,31 +4,68 @@ import java.nio.charset.StandardCharsets; import static ch.fhnw.kry.Main.BLOCK_LENGTH; +/** + * Decrypt cipher text with CTR and SPN. + *

+ * Some implemented functions are not as flexible as could be, + * as everything basically assumes a block length of 16 bits. + * For this learning exercise, this is satisfactory though. + */ public class Decrypt { - public String decrypt(String keyString, String chiffre) { - int key = Integer.parseInt(keyString, 2); + /** + * Decrypt a cipher text with a key, using CTR and an SPN. + *

+ * The decrypted data is interpreted as ASCII code. + * + * @param keyString Key as a bit string. + * @param cipher Cipher text as a bit string. + * @return Decrypted data, interpreted as an ASCII string. + */ + public String decrypt(String keyString, String cipher) { + int key = Integer.parseInt(keyString, 2); // get the key - int[] data = strToArray(chiffre); - byte[] decryptedData = new byte[data.length * 2]; - int iv = data[0]; + int[] data = strToArray(cipher); + byte[] decryptedData = + new byte[data.length * 2]; // as the block size is 16 bits, the byte array needs double the capacity + int iv = data[0]; // first block is the iv var ctr = new CTR(iv, key); - for (int i = 1; i < data.length; i++) { + for (int i = 0; i < data.length - 1; i++) { int block = data[i]; - int decryptedBlock = ctr.decrypt(block, i); + int decryptedBlock = ctr.decrypt(block, i, data[i + 1]); // y is i + 1 because i = 0 is the iv - decryptedData[i * 2] = (byte)(decryptedBlock >>> 8); - decryptedData[i * 2 + 1] = (byte)(decryptedBlock & 0x0000_00FF); + decryptedData[i * 2] = (byte) (decryptedBlock >>> 8); // get the upper half of the decrypted block + decryptedData[i * 2 + 1] = (byte) (decryptedBlock & 0xFF); // and the lower half } - String decryptedString = new String(decryptedData, StandardCharsets.US_ASCII); + decryptedData = removePadding(decryptedData); - return decryptedString; + return new String(decryptedData, StandardCharsets.US_ASCII); + } + + /** + * Remove the padding of data by traversing it + * backwards until a byte != 0 s found and lop it off behind + * the found position. + * + * @param data Data with padding. + * @return Input data without the padding. + */ + public byte[] removePadding(byte[] data) { + int idx = data.length - 1; + + while (idx > 0 && data[idx] == 0) { + idx--; + } + + byte[] unpaddedData = new byte[idx]; + System.arraycopy(data, 0, unpaddedData, 0, idx); + return unpaddedData; } /** * Convert a bit string into an integer array. - * + *

* Because later we only ever look at the lower 16 bits, * we stuff every block of 16 bits into the lower half of an int * (meaning the igh 16 bits of the ints in the array are always 0). diff --git a/src/main/java/ch/fhnw/kry/Main.java b/src/main/java/ch/fhnw/kry/Main.java index 20ad38f..dbcec11 100644 --- a/src/main/java/ch/fhnw/kry/Main.java +++ b/src/main/java/ch/fhnw/kry/Main.java @@ -6,12 +6,20 @@ package ch.fhnw.kry; public class Main { static final String KEY = "00111010100101001101011000111111"; - static final String CHIFFRE = + static final String CIPHER = "00000100110100100000101110111000000000101000111110001110011111110110000001010001010000111010000000010011011001110010101110110000"; static final int BLOCK_LENGTH = 16; + /** + * Decrypt {@link Main#CIPHER} with {@link Main#KEY}. + *

+ * In a real program we would read those from files or command line arguments. + * + * @param args Command line arguments. + */ public static void main(String[] args) { - + var decrypt = new Decrypt(); + System.out.printf("decrypted message: %s%n", decrypt.decrypt(KEY, CIPHER)); } } diff --git a/src/main/java/ch/fhnw/kry/SPN.java b/src/main/java/ch/fhnw/kry/SPN.java index da2da63..565ac7d 100644 --- a/src/main/java/ch/fhnw/kry/SPN.java +++ b/src/main/java/ch/fhnw/kry/SPN.java @@ -44,6 +44,7 @@ public class SPN { entry(7, 0xF) ); private final static int ROUND_KEY_LENGTH = 16; + private static final int ROUNDS = 4; private final Map PERMUTATION = Map.ofEntries( entry(0, 0), entry(1, 4), @@ -63,14 +64,11 @@ public class SPN { entry(15, 15) ); - - private static final int ROUNDS = 4; - /** * Encrypt a block with the defined SPN. * * @param key Key for encryption (round keys get derived from this). - * @param x Input block (high 16 bits get ignored). + * @param x Input block (high 16 bits get ignored). * @return Encrypted block. */ public int encryptBlock(int key, int x) { @@ -92,7 +90,7 @@ public class SPN { * Decrypt a block with the defined SPN. * * @param key Key for decryption (round keys get derived from this). - * @param x Encrypted block (high 16 bits get ignored). + * @param x Encrypted block (high 16 bits get ignored). * @return Decrypted block. */ public int decryptBlock(int key, int x) { @@ -116,7 +114,7 @@ public class SPN { * bits from that position in the key. * * @param key Key as input. - * @param i Round number (starts at 0). + * @param i Round number (starts at 0). * @return Derived key. */ public int k(int key, int i) { @@ -128,12 +126,12 @@ public class SPN { /** * Initialer Weisschritt. - * + *

* XOR an input with the key of a specific round. * * @param key Key to derive the round key. - * @param x Input. - * @param r Round number (starts at 0). + * @param x Input. + * @param r Round number (starts at 0). * @return Input xored with the derived round key. */ public int init(int key, int x, int r) { @@ -163,7 +161,7 @@ public class SPN { /** * Substitute every four bits of an input by running them through an S-Box. * - * @param x Initial state (the high 16 bits of the integer are ignored). + * @param x Initial state (the high 16 bits of the integer are ignored). * @param sbox A mapping consisting of hex keys and values. * @return End state, after running all substitutions. */ @@ -183,7 +181,7 @@ public class SPN { /** * Swap two bits. - * + *

* Because we work with blocks of 16 bits, the high bits of the int input x are ignored. * * @param x Value where the bits get swapped (high 16 bits get ignored). diff --git a/src/test/java/ch/fhnw/kry/CTRTest.java b/src/test/java/ch/fhnw/kry/CTRTest.java index 01efc92..ec0588f 100644 --- a/src/test/java/ch/fhnw/kry/CTRTest.java +++ b/src/test/java/ch/fhnw/kry/CTRTest.java @@ -2,14 +2,12 @@ package ch.fhnw.kry; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - class CTRTest { @Test void generateIV() { int count = 0; - while(count < Integer.MAX_VALUE) { + while (count < Integer.MAX_VALUE) { int iv = CTR.generateIV(); assert (iv < 1 << 16); count++; diff --git a/src/test/java/ch/fhnw/kry/DecryptTest.java b/src/test/java/ch/fhnw/kry/DecryptTest.java index 139b406..b858daf 100644 --- a/src/test/java/ch/fhnw/kry/DecryptTest.java +++ b/src/test/java/ch/fhnw/kry/DecryptTest.java @@ -2,9 +2,9 @@ package ch.fhnw.kry; import org.junit.jupiter.api.Test; -import static ch.fhnw.kry.Main.CHIFFRE; +import static ch.fhnw.kry.Main.CIPHER; import static ch.fhnw.kry.Main.KEY; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; class DecryptTest { @@ -12,7 +12,8 @@ class DecryptTest { void strToArray() { var decrypt = new Decrypt(); - String input = "00000100110100100000101110111000000000101000111110001110011111110110000001010001010000111010000000010011011001110010101110110000"; + String input = + "00000100110100100000101110111000000000101000111110001110011111110110000001010001010000111010000000010011011001110010101110110000"; int[] data = decrypt.strToArray(input); assertEquals(8, data.length); @@ -23,6 +24,6 @@ class DecryptTest { @Test void decrypt() { var decrypt = new Decrypt(); - String decrypted = decrypt.decrypt(KEY, CHIFFRE); + assertEquals("Gut gemacht!", decrypt.decrypt(KEY, CIPHER)); } } diff --git a/src/test/java/ch/fhnw/kry/SPNTest.java b/src/test/java/ch/fhnw/kry/SPNTest.java index 4c18b3f..d6e3127 100644 --- a/src/test/java/ch/fhnw/kry/SPNTest.java +++ b/src/test/java/ch/fhnw/kry/SPNTest.java @@ -2,7 +2,7 @@ package ch.fhnw.kry; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; class SPNTest { @@ -11,9 +11,9 @@ class SPNTest { var spn = new SPN(); final int key = 0xFFFFFFFF; - assertEquals(0xFFFF, spn.k(key, 0)); - assertEquals(0xFFFF, spn.k(key, 1)); - assertEquals(0xFFFF, spn.k(key, 2)); + assertEquals(0xFFFF, spn.k(key, 0)); + assertEquals(0xFFFF, spn.k(key, 1)); + assertEquals(0xFFFF, spn.k(key, 2)); } @Test @@ -66,7 +66,7 @@ class SPNTest { void blockEncryptionDecryption() { var spn = new SPN(); - int x = 0x128F ; + int x = 0x128F; int key = 0x11288C00; int y = 0xAEB4; From 22fcf324e4d519b53a889a741de1f62cf1a4b887 Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Tue, 22 Mar 2022 21:10:14 +0100 Subject: [PATCH 09/10] use cipher text from constant in test --- src/test/java/ch/fhnw/kry/DecryptTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/java/ch/fhnw/kry/DecryptTest.java b/src/test/java/ch/fhnw/kry/DecryptTest.java index b858daf..e5e5d87 100644 --- a/src/test/java/ch/fhnw/kry/DecryptTest.java +++ b/src/test/java/ch/fhnw/kry/DecryptTest.java @@ -12,9 +12,7 @@ class DecryptTest { void strToArray() { var decrypt = new Decrypt(); - String input = - "00000100110100100000101110111000000000101000111110001110011111110110000001010001010000111010000000010011011001110010101110110000"; - int[] data = decrypt.strToArray(input); + int[] data = decrypt.strToArray(CIPHER); assertEquals(8, data.length); assertEquals(0x04D2, data[0]); From f78d2e812447685f30d4d80d57a769d70c99ff9a Mon Sep 17 00:00:00 2001 From: Sebastian Hugentobler Date: Tue, 22 Mar 2022 21:23:49 +0100 Subject: [PATCH 10/10] no need to send the encrypted block twice --- src/main/java/ch/fhnw/kry/CTR.java | 5 ++--- src/main/java/ch/fhnw/kry/Decrypt.java | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/ch/fhnw/kry/CTR.java b/src/main/java/ch/fhnw/kry/CTR.java index 7c484ce..16c8c73 100644 --- a/src/main/java/ch/fhnw/kry/CTR.java +++ b/src/main/java/ch/fhnw/kry/CTR.java @@ -40,13 +40,12 @@ public class CTR { * * @param block Encrypted block (only lower 16 bits get looked at). * @param idx Block index. - * @param y Y at index idx. * @return Decrypted block (in the lower 16 bits of the int). */ - public int decrypt(int block, int idx, int y) { + public int decrypt(int block, int idx) { int e = (iv + idx) % (1 << BLOCK_LENGTH); // iv + i mod 2^16 e = spn.encryptBlock(key, e); // yes, we need the encryption function, as this is CTR - return y ^ e; + return block ^ e; } } diff --git a/src/main/java/ch/fhnw/kry/Decrypt.java b/src/main/java/ch/fhnw/kry/Decrypt.java index 7086e54..69aeab8 100644 --- a/src/main/java/ch/fhnw/kry/Decrypt.java +++ b/src/main/java/ch/fhnw/kry/Decrypt.java @@ -31,8 +31,8 @@ public class Decrypt { var ctr = new CTR(iv, key); for (int i = 0; i < data.length - 1; i++) { - int block = data[i]; - int decryptedBlock = ctr.decrypt(block, i, data[i + 1]); // y is i + 1 because i = 0 is the iv + int block = data[i + 1]; // index is i + 1 because i = 0 is the iv, hence the encrypted block is offset by 1 + int decryptedBlock = ctr.decrypt(block, i); decryptedData[i * 2] = (byte) (decryptedBlock >>> 8); // get the upper half of the decrypted block decryptedData[i * 2 + 1] = (byte) (decryptedBlock & 0xFF); // and the lower half