Compare commits
10 Commits
5bd4e640f2
...
f78d2e8124
Author | SHA1 | Date | |
---|---|---|---|
f78d2e8124 | |||
22fcf324e4 | |||
e1c2d7df7d | |||
e04a341fc6 | |||
3bc74649ad | |||
18cdd1ed81 | |||
55876a7d33 | |||
1fd74c6ec3 | |||
2d53d98437 | |||
![]() |
18515ea4cd |
3
.gitignore
vendored
3
.gitignore
vendored
@ -2,4 +2,5 @@
|
||||
.DS_Store
|
||||
build
|
||||
.gradle
|
||||
.idea
|
||||
.idea
|
||||
bin
|
51
src/main/java/ch/fhnw/kry/CTR.java
Normal file
51
src/main/java/ch/fhnw/kry/CTR.java
Normal file
@ -0,0 +1,51 @@
|
||||
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;
|
||||
private final int key;
|
||||
|
||||
public CTR(int iv, int key) {
|
||||
this.iv = iv;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a 16 bit long initialisation vector.
|
||||
*
|
||||
* @return Generated initialisation vector.
|
||||
*/
|
||||
public static int generateIV() {
|
||||
Random random = ThreadLocalRandom.current();
|
||||
byte[] r = new byte[2];
|
||||
random.nextBytes(r);
|
||||
|
||||
return r[0] << 8 & r[1];
|
||||
}
|
||||
|
||||
public int getIV() {
|
||||
return iv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt a block in CTR mode.
|
||||
*
|
||||
* @param block Encrypted block (only lower 16 bits get looked at).
|
||||
* @param idx Block index.
|
||||
* @return Decrypted block (in the lower 16 bits of the int).
|
||||
*/
|
||||
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 block ^ e;
|
||||
}
|
||||
}
|
88
src/main/java/ch/fhnw/kry/Decrypt.java
Normal file
88
src/main/java/ch/fhnw/kry/Decrypt.java
Normal file
@ -0,0 +1,88 @@
|
||||
package ch.fhnw.kry;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static ch.fhnw.kry.Main.BLOCK_LENGTH;
|
||||
|
||||
/**
|
||||
* Decrypt cipher text with CTR and SPN.
|
||||
* <p>
|
||||
* 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 {
|
||||
/**
|
||||
* Decrypt a cipher text with a key, using CTR and an SPN.
|
||||
* <p>
|
||||
* 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(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 = 0; i < data.length - 1; i++) {
|
||||
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
|
||||
}
|
||||
|
||||
decryptedData = removePadding(decryptedData);
|
||||
|
||||
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.
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
}
|
@ -5,11 +5,21 @@ 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 CIPHER =
|
||||
"00000100110100100000101110111000000000101000111110001110011111110110000001010001010000111010000000010011011001110010101110110000";
|
||||
|
||||
public static void main(String[] args) {
|
||||
static final int BLOCK_LENGTH = 16;
|
||||
|
||||
/**
|
||||
* Decrypt {@link Main#CIPHER} with {@link Main#KEY}.
|
||||
* <p>
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
@ -43,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<Integer, Integer> PERMUTATION = Map.ofEntries(
|
||||
entry(0, 0),
|
||||
entry(1, 4),
|
||||
@ -62,56 +64,91 @@ public class SPN {
|
||||
entry(15, 15)
|
||||
);
|
||||
|
||||
public String decrypt(int key, String chiffre) {
|
||||
|
||||
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 < 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);
|
||||
x ^= k(key, 4);
|
||||
x = substitution(x, SBOX); // run through s-box
|
||||
x ^= k(key, ROUNDS); // 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, ROUNDS); // initialer Weisschritt
|
||||
|
||||
for (int i = 3; i > 0; i--) {
|
||||
x = substitution(x, SBOX_REVERSE);
|
||||
x = permutation(x);
|
||||
x ^= k(key, i);
|
||||
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
|
||||
}
|
||||
|
||||
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;
|
||||
i *= ROUNDS;
|
||||
int mask = 0xFFFF_0000 >>> i;
|
||||
|
||||
return (key & mask) >>> ROUND_KEY_LENGTH - i;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialer Weisschritt.
|
||||
* <p>
|
||||
* 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<Integer, Boolean> 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);
|
||||
@ -121,6 +158,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<Integer, Integer> sbox) {
|
||||
int mask = 0xF000;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
@ -135,7 +179,18 @@ public class SPN {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap two bits.
|
||||
* <p>
|
||||
* 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;
|
||||
|
||||
|
16
src/test/java/ch/fhnw/kry/CTRTest.java
Normal file
16
src/test/java/ch/fhnw/kry/CTRTest.java
Normal file
@ -0,0 +1,16 @@
|
||||
package ch.fhnw.kry;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class CTRTest {
|
||||
|
||||
@Test
|
||||
void generateIV() {
|
||||
int count = 0;
|
||||
while (count < Integer.MAX_VALUE) {
|
||||
int iv = CTR.generateIV();
|
||||
assert (iv < 1 << 16);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
27
src/test/java/ch/fhnw/kry/DecryptTest.java
Normal file
27
src/test/java/ch/fhnw/kry/DecryptTest.java
Normal file
@ -0,0 +1,27 @@
|
||||
package ch.fhnw.kry;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static ch.fhnw.kry.Main.CIPHER;
|
||||
import static ch.fhnw.kry.Main.KEY;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class DecryptTest {
|
||||
|
||||
@Test
|
||||
void strToArray() {
|
||||
var decrypt = new Decrypt();
|
||||
|
||||
int[] data = decrypt.strToArray(CIPHER);
|
||||
|
||||
assertEquals(8, data.length);
|
||||
assertEquals(0x04D2, data[0]);
|
||||
assertEquals(0x2BB0, data[data.length - 1]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void decrypt() {
|
||||
var decrypt = new Decrypt();
|
||||
assertEquals("Gut gemacht!", decrypt.decrypt(KEY, CIPHER));
|
||||
}
|
||||
}
|
@ -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
|
||||
@ -63,10 +63,10 @@ class SPNTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void sp() {
|
||||
void blockEncryptionDecryption() {
|
||||
var spn = new SPN();
|
||||
|
||||
int x = 0x128F ;
|
||||
int x = 0x128F;
|
||||
int key = 0x11288C00;
|
||||
int y = 0xAEB4;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user