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
* 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;