Fix CTR decryption.
Off by one error in respect to the y blocks.
This commit is contained in:
parent
e04a341fc6
commit
e1c2d7df7d
@ -3,6 +3,11 @@ package ch.fhnw.kry;
|
|||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
import static ch.fhnw.kry.Main.BLOCK_LENGTH;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of CTR mode for decryption.
|
||||||
|
*/
|
||||||
public class CTR {
|
public class CTR {
|
||||||
private final SPN spn = new SPN();
|
private final SPN spn = new SPN();
|
||||||
private final int iv;
|
private final int iv;
|
||||||
@ -13,10 +18,11 @@ public class CTR {
|
|||||||
this.key = key;
|
this.key = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getIV() {
|
/**
|
||||||
return iv;
|
* Generate a 16 bit long initialisation vector.
|
||||||
}
|
*
|
||||||
|
* @return Generated initialisation vector.
|
||||||
|
*/
|
||||||
public static int generateIV() {
|
public static int generateIV() {
|
||||||
Random random = ThreadLocalRandom.current();
|
Random random = ThreadLocalRandom.current();
|
||||||
byte[] r = new byte[2];
|
byte[] r = new byte[2];
|
||||||
@ -25,10 +31,22 @@ public class CTR {
|
|||||||
return r[0] << 8 & r[1];
|
return r[0] << 8 & r[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
public int decrypt(int block, int idx) {
|
public int getIV() {
|
||||||
int e = (iv + idx) % (1 << 16);
|
return iv;
|
||||||
e = spn.decryptBlock(key, e);
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,31 +4,68 @@ import java.nio.charset.StandardCharsets;
|
|||||||
|
|
||||||
import static ch.fhnw.kry.Main.BLOCK_LENGTH;
|
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 {
|
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.
|
||||||
|
* <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(chiffre);
|
int[] data = strToArray(cipher);
|
||||||
byte[] decryptedData = new byte[data.length * 2];
|
byte[] decryptedData =
|
||||||
int iv = data[0];
|
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);
|
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 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] = (byte) (decryptedBlock >>> 8); // get the upper half of the decrypted block
|
||||||
decryptedData[i * 2 + 1] = (byte)(decryptedBlock & 0x0000_00FF);
|
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.
|
* Convert a bit string into an integer array.
|
||||||
*
|
* <p>
|
||||||
* Because later we only ever look at the lower 16 bits,
|
* 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
|
* 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).
|
* (meaning the igh 16 bits of the ints in the array are always 0).
|
||||||
|
@ -6,12 +6,20 @@ package ch.fhnw.kry;
|
|||||||
public class Main {
|
public class Main {
|
||||||
|
|
||||||
static final String KEY = "00111010100101001101011000111111";
|
static final String KEY = "00111010100101001101011000111111";
|
||||||
static final String CHIFFRE =
|
static final String CIPHER =
|
||||||
"00000100110100100000101110111000000000101000111110001110011111110110000001010001010000111010000000010011011001110010101110110000";
|
"00000100110100100000101110111000000000101000111110001110011111110110000001010001010000111010000000010011011001110010101110110000";
|
||||||
|
|
||||||
static final int BLOCK_LENGTH = 16;
|
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) {
|
public static void main(String[] args) {
|
||||||
|
var decrypt = new Decrypt();
|
||||||
|
System.out.printf("decrypted message: %s%n", decrypt.decrypt(KEY, CIPHER));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,7 @@ public class SPN {
|
|||||||
entry(7, 0xF)
|
entry(7, 0xF)
|
||||||
);
|
);
|
||||||
private final static int ROUND_KEY_LENGTH = 16;
|
private final static int ROUND_KEY_LENGTH = 16;
|
||||||
|
private static final int ROUNDS = 4;
|
||||||
private final Map<Integer, Integer> PERMUTATION = Map.ofEntries(
|
private final Map<Integer, Integer> PERMUTATION = Map.ofEntries(
|
||||||
entry(0, 0),
|
entry(0, 0),
|
||||||
entry(1, 4),
|
entry(1, 4),
|
||||||
@ -63,9 +64,6 @@ public class SPN {
|
|||||||
entry(15, 15)
|
entry(15, 15)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
private static final int ROUNDS = 4;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypt a block with the defined SPN.
|
* Encrypt a block with the defined SPN.
|
||||||
*
|
*
|
||||||
@ -128,7 +126,7 @@ public class SPN {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialer Weisschritt.
|
* Initialer Weisschritt.
|
||||||
*
|
* <p>
|
||||||
* XOR an input with the key of a specific round.
|
* XOR an input with the key of a specific round.
|
||||||
*
|
*
|
||||||
* @param key Key to derive the round key.
|
* @param key Key to derive the round key.
|
||||||
@ -183,7 +181,7 @@ public class SPN {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Swap two bits.
|
* Swap two bits.
|
||||||
*
|
* <p>
|
||||||
* Because we work with blocks of 16 bits, the high bits of the int input x are ignored.
|
* 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 x Value where the bits get swapped (high 16 bits get ignored).
|
||||||
|
@ -2,14 +2,12 @@ package ch.fhnw.kry;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
|
||||||
|
|
||||||
class CTRTest {
|
class CTRTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void generateIV() {
|
void generateIV() {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
while(count < Integer.MAX_VALUE) {
|
while (count < Integer.MAX_VALUE) {
|
||||||
int iv = CTR.generateIV();
|
int iv = CTR.generateIV();
|
||||||
assert (iv < 1 << 16);
|
assert (iv < 1 << 16);
|
||||||
count++;
|
count++;
|
||||||
|
@ -2,9 +2,9 @@ package ch.fhnw.kry;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
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 ch.fhnw.kry.Main.KEY;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
class DecryptTest {
|
class DecryptTest {
|
||||||
|
|
||||||
@ -12,7 +12,8 @@ class DecryptTest {
|
|||||||
void strToArray() {
|
void strToArray() {
|
||||||
var decrypt = new Decrypt();
|
var decrypt = new Decrypt();
|
||||||
|
|
||||||
String input = "00000100110100100000101110111000000000101000111110001110011111110110000001010001010000111010000000010011011001110010101110110000";
|
String input =
|
||||||
|
"00000100110100100000101110111000000000101000111110001110011111110110000001010001010000111010000000010011011001110010101110110000";
|
||||||
int[] data = decrypt.strToArray(input);
|
int[] data = decrypt.strToArray(input);
|
||||||
|
|
||||||
assertEquals(8, data.length);
|
assertEquals(8, data.length);
|
||||||
@ -23,6 +24,6 @@ class DecryptTest {
|
|||||||
@Test
|
@Test
|
||||||
void decrypt() {
|
void decrypt() {
|
||||||
var decrypt = new Decrypt();
|
var decrypt = new Decrypt();
|
||||||
String decrypted = decrypt.decrypt(KEY, CHIFFRE);
|
assertEquals("Gut gemacht!", decrypt.decrypt(KEY, CIPHER));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package ch.fhnw.kry;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
class SPNTest {
|
class SPNTest {
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ class SPNTest {
|
|||||||
void blockEncryptionDecryption() {
|
void blockEncryptionDecryption() {
|
||||||
var spn = new SPN();
|
var spn = new SPN();
|
||||||
|
|
||||||
int x = 0x128F ;
|
int x = 0x128F;
|
||||||
int key = 0x11288C00;
|
int key = 0x11288C00;
|
||||||
int y = 0xAEB4;
|
int y = 0xAEB4;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user