kry-ctr-spn/src/main/java/ch/fhnw/kry/Decrypt.java

89 lines
3.0 KiB
Java

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