import java.util.ArrayList;
import java.util.Arrays;
/**
* Data Encryption Standard with Cipher Text Stealing
* CPSC 428 project
* @author Joshua Wilkins
* @version 1.0 2008
*/
public class DESCTS {
private static ArrayList<byte[]> preCryptedBlocks; //inputs: organized in blocks of size 8.
private static ArrayList<byte[]> resultingBlocks; //outputs: organized in blocks of size 8.
//must be same size as the inputs
private static byte[] storage; //temporary byte array used in the encrypt & decrypt functions
private static int size; //variable used to store the size/length.
/**
* Empty Default Constructor
*/
public DESCTS() {
}
/**
* Decrypts the given byte array using the Cipher Text Stealing mode
* @param ctBytes ciphertext to be decrypted
* @param key key needed for decryption
* @param IV initial vector
* @return byte[] the same size as ctBytes
* @throws java.lang.Exception
*/
public static byte[] decrypt(byte[] ctBytes,
javax.crypto.SecretKey key,
byte[] IV) throws java.lang.Exception{
byte[] decrypted = null;
if(ctBytes.length < 8) {
throw new Exception();
}
if(ctBytes.length == 8) {
byte[] tempByteArray = DES.decrypt(ctBytes, key);
decrypted = exclusiveOr(tempByteArray, IV);
}
if(ctBytes.length > 8) {
preCryptedBlocks = splitByteArray(ctBytes);
boolean isLastArraySizeEight = checkLastArray(preCryptedBlocks);
//System.out.println(isLastArraySizeEight);
if(isLastArraySizeEight) {
resultingBlocks = easyDecryptCase(preCryptedBlocks, IV, key);
decrypted = buildByteArray(resultingBlocks);
}
else {
resultingBlocks = hardDecryptCase(preCryptedBlocks, IV, key);
decrypted = buildByteArray(resultingBlocks);
}
}
return decrypted;
}
/**
* Encrypts the given byte array using Cipher Text Stealing
* @param ptBytes plaintext to be encrypted
* @param key given key needed for encryption
* @param IV initial vector
* @return byte[] the same size as ptBytes
* @throws java.lang.Exception
*/
public static byte[] encrypt(byte[] ptBytes,
javax.crypto.SecretKey key,
byte[] IV) throws java.lang.Exception{
byte[] encrypted = null;
size = ptBytes.length;
if(ptBytes.length < 8) {
throw new Exception();
}
if(ptBytes.length == 8) {
byte[] tempByteArray = exclusiveOr(ptBytes, IV);
encrypted = DES.encrypt(tempByteArray, key);
}
if(ptBytes.length > 8) {
preCryptedBlocks = splitByteArray(ptBytes);
boolean isLastArraySizeEight = checkLastArray(preCryptedBlocks);
if(isLastArraySizeEight) {
resultingBlocks = easyEncryptCase(preCryptedBlocks, IV, key);
encrypted = buildByteArray(resultingBlocks);
}
else {
resultingBlocks = hardEncryptCase(preCryptedBlocks, IV, key);
encrypted = buildByteArray(resultingBlocks);
}
}
return encrypted;
}
/**
* Handles the decryption case with padding
* @param arraylist
* @param IV
* @param key
* @return
* @throws Exception
*/
private static ArrayList<byte[]> hardDecryptCase(ArrayList<byte[]> arraylist, byte[] IV,
javax.crypto.SecretKey key) throws Exception {
ArrayList<byte[]> result = new ArrayList<byte[]>();
for(int i = 0; i < arraylist.size(); i++) {
if(i == 0){
//base
result.add(exclusiveOr((DES.decrypt(arraylist.get(i), key)),IV));
}
else {
if(i == arraylist.size() - 1) {
storage = DES.decrypt(arraylist.get(i-1), key);
byte[] temp = arraylist.get(i);
byte[] temp8 = new byte[8];
for (int j = 0; j < temp.length; j++) {
temp8[j] = temp[j];
}
for (int k = temp.length -1; k < temp8.length; k++) {
temp8[k] = storage[k];
}
result.add(exclusiveOr((DES.decrypt(temp8, key)), arraylist.get(i-2)));
byte[] tempA = new byte[temp.length];
for (int l = 0; l < temp.length; l++) {
tempA[l] = (byte) (temp[l] ^ storage[l]);
}
result.add(tempA);
}
else {
result.add(exclusiveOr((DES.decrypt(arraylist.get(i), key)), arraylist.get(i-1)));
}
}
}
return result;
}
/**
* Handles the decryption case without padding
* @param arraylist
* @param IV
* @param key
* @return
* @throws Exception
*/
private static ArrayList<byte[]> easyDecryptCase(ArrayList<byte[]> arraylist, byte[] IV,
javax.crypto.SecretKey key) throws Exception {
ArrayList<byte[]> result = new ArrayList<byte[]>();
for(int i = 0; i < arraylist.size(); i++) {
if(i == 0) {
//base
result.add(exclusiveOr((DES.decrypt(arraylist.get(i), key)),IV));
}
else {
result.add(exclusiveOr((DES.decrypt(arraylist.get(i), key)), arraylist.get(i-1)));
}
}
return result;
}
/**
* Handles the encryption case with padding
* @param arraylist
* @param IV
* @param key
* @return ArrayList<byte[]>
* @throws Exception
*/
private static ArrayList<byte[]> hardEncryptCase(ArrayList<byte[]> arraylist, byte[] IV,
javax.crypto.SecretKey key) throws Exception {
ArrayList<byte[]> result = new ArrayList<byte[]>();
for(int i = 0; i < arraylist.size(); i++) {
if(i == 0) {
//base case, first block of 8 bytes
result.add(DES.encrypt(exclusiveOr(arraylist.get(i),IV),key));
}
else {
if(i == arraylist.size() - 2) {
//next to last case, save the output to storage dont add to final
storage = DES.encrypt(exclusiveOr(arraylist.get(i),result.get(i-1)), key);
}
else if(i == arraylist.size() - 1) {
byte[] temp = arraylist.get(i);
byte[] temp8 = new byte[8];
for(int j = 0; j < temp.length; j++){
temp8[j] = temp[j]; //builds the first part of the 8 byte array with whats available.
}
for(int k = temp.length; k < temp8.length; k++) {
temp8[k] = 0x00; //padding
}
result.add(DES.encrypt(exclusiveOr(storage, temp8),key));
byte[] tempFinal = new byte[temp.length];
for (int l = 0; l < tempFinal.length; l++) {
tempFinal[l] = storage[l];
}
result.add(tempFinal);
}
else {
result.add(DES.encrypt(exclusiveOr(arraylist.get(i),result.get(i-1)), key));
}
}
}
return result;
}
/**
* Handles the encryption case without padding
* @param arraylist
* @param IV
* @param key
* @return ArrayList<byte[]>
* @throws Exception
*/
private static ArrayList<byte[]> easyEncryptCase(ArrayList<byte[]> arraylist, byte[] IV,
javax.crypto.SecretKey key) throws Exception {
ArrayList<byte[]> result = new ArrayList<byte[]>();
for(int i = 0; i < arraylist.size(); i++) {
if(i == 0) {
//base case: blocks of 8 bytes
result.add(DES.encrypt(exclusiveOr(arraylist.get(i),IV),key));
}
else {
//adds the other blocks of 8 bytes to the final result.
result.add(DES.encrypt(exclusiveOr(arraylist.get(i),result.get(i-1)), key));
}
}
return result;
}
/**
* Checks to see if the last byte array's size is equal to 8
* @param arraylist
* @return boolean
*/
private static boolean checkLastArray(ArrayList<byte[]> arraylist) {
boolean checked = false;
if(arraylist.get(arraylist.size()-1).length == 8) {
checked = true;
}
return checked;
}
/**
* returns a single byte array from the blocks
* @param arraylist
* @return byte[]
*/
private static byte[] buildByteArray(ArrayList<byte[]> arraylist) {
byte[] temp = new byte[size];
int counter = 0;
for(int i = 0; i < arraylist.size(); i++) {
for(int j=0; j < arraylist.get(i).length; j++) {
if(counter < size) {
temp[counter] = arraylist.get(i)[j];
counter++;
}
}
}
return temp;
}
/**
* Returns the results of an exclusive Or Operation of two size 8 byte arrays
* @param byteArrayA
* @param byteArrayB
* @return byte[] of size 8
*/
private static byte[] exclusiveOr(byte[] byteArrayA, byte[] byteArrayB) {
byte[] temp = new byte[8];
for (int i = 0; i < 8; i++) {
temp[i] = (byte) (byteArrayA[i] ^ byteArrayB[i]);
}
return temp;
}
/**
* Splits the byte array into 8 byte blocks
* @return ArrayList<byte[]>
*/
private static ArrayList<byte[]> splitByteArray(byte[] byteArray) {
int counterA = 0;
ArrayList<byte[]> arraylist = new ArrayList<byte[]>();
while(counterA < byteArray.length) {
byte[] tempByteArray = new byte[8];
if ((byteArray.length - counterA) < 8) {
tempByteArray = new byte[byteArray.length - counterA];
for(int i = 0; i < tempByteArray.length; i++) {
tempByteArray[i] = byteArray[counterA];
counterA++;
}
}
else {
for(int i = 0; i < 8; i++) {
tempByteArray[i] = byteArray[counterA];
counterA++;
}
}
arraylist.add(tempByteArray);
}
return arraylist;
}
}