package uk.ac.cam.tfmw2.stegdroid; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.ShortBufferException; import javax.crypto.spec.SecretKeySpec; import android.util.Log; public class BitStream{ // This class has two modes, for reading and writing bitstreams. // It can produces encrypted or unencrypted streams of bits private static final String TAG = "BitStream"; private static final int MODE_READ = 0; private static final int MODE_WRITE = 1; private byte[] bytes; private int cursor = 0; private int length; private int mode; private boolean encrypted = false; public BitStream(){ //constructor for decoding a bit stream mode = MODE_READ; bytes = new byte[1]; //initially this size until we know the length } public static byte[] padKey(String s){ //Key length needs to be (128,192 or) 256 bits, so hash with SHA-256 byte[] key = s.getBytes(); try { MessageDigest sha = MessageDigest.getInstance("SHA-256"); key = sha.digest(key); } catch (NoSuchAlgorithmException e) { //TODO e.printStackTrace(); } return key; } public BitStream(String s){ //constructor for encoding an unencrypted BitStream Log.v(TAG,"New BitStream: "+s); mode = MODE_WRITE; length = s.getBytes().length; byte lengthByte = (byte)length; //first byte contains the length, so that it can be decoded correctly bytes = new byte[length + 1]; bytes[0] = lengthByte; System.arraycopy(s.getBytes(), 0, bytes, 1, length); } public BitStream(String s, String keyString){ //constructor for encoding an encrypted BitStream Log.v(TAG,"Encrypted BitStream"); encrypted = true; mode = MODE_WRITE; byte[] input = s.getBytes(); //pad the key SecretKeySpec key = new SecretKeySpec(padKey(keyString), "AES"); Cipher cipher; try { cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC"); Log.v("Crypto",new String(input)); // encryption pass cipher.init(Cipher.ENCRYPT_MODE, key); byte[] cryptoBytes = new byte[cipher.getOutputSize(input.length)]; int cryptoBytesLength = cipher.update(input, 0, input.length, cryptoBytes, 0); cryptoBytesLength += cipher.doFinal(cryptoBytes, cryptoBytesLength); //get the length of the new byte[] and assign to first byte length = cryptoBytes.length; byte lengthByte = (byte)length; bytes = new byte[length + 1]; //also set the encryption bit (128) bytes[0] = (byte) (lengthByte + 128); //copy into bytes System.arraycopy(cryptoBytes, 0, bytes, 1, cryptoBytesLength); Log.v(TAG,"length: "+cryptoBytes.length+","+bytes.length+" first byte: "+(int)bytes[0]+" data: "+new String(bytes)); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (ShortBufferException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } } public int getNextBit() throws EndOfBitStreamException { try{ return (bytes[cursor / 8] >> (cursor++ % 8)) & 1; }catch(ArrayIndexOutOfBoundsException e){ throw new EndOfBitStreamException(); } } public boolean setNextBit(int bit) { //Log.v(TAG,"Got a bit - "+bit); // if the cursor == 8, the first byte has been decoded, so the length can be obtained if(cursor == 8){ //set length from first byte length = (int)bytes[0] & 0xFF; //check encryption bit if(length >= 128){ encrypted = true; length -= 128; } //set bytes to the right size bytes = new byte[length + 1]; bytes[0] = (byte)length; Log.d(TAG,"Length decoded: "+length); }else if(cursor % 8 == 0 && cursor > 8){ if(bytes[(cursor/8)-1] == 0) return true; //TODO remove this in final Log.d(TAG,"Extracted a byte ("+cursor/8+") - "+Byte.toString(bytes[(cursor/8)-1])+" - "+Integer.toBinaryString((int)bytes[(cursor/8)-1] & 0xFF)); } // return true at the end if(this.endOfBitStream()) return true; //assign the bit to the correct position in bytes[] byte b = bytes[cursor / 8]; if((bit & 1) == 1){ bytes[cursor / 8] = (byte) (b | (1 << (cursor % 8))); }else{ bytes[cursor / 8] = (byte) (b & ~(1 << (cursor % 8))); } cursor++; return false; } public boolean endOfBitStream() { // return true if it is the end of the BitStream if(mode == MODE_READ && cursor < 8) return false; //length not set yet! return cursor == length * 8 + 8; } public String getString(){ //return unencrypted String byte[] stringBytes = new byte[length]; System.arraycopy(bytes, 1, stringBytes, 0, length); return new String(stringBytes); } public String decryptString(String keyString){ // return String decrypted with the keyString byte[] stringBytes = new byte[length]; System.arraycopy(bytes, 1, stringBytes, 0, length); Cipher cipher; //pad the key SecretKeySpec key = new SecretKeySpec(padKey(keyString), "AES"); int ctLength = stringBytes.length; try { cipher = Cipher.getInstance("AES/ECB/PKCS7Padding", "BC"); // decryption pass cipher.init(Cipher.DECRYPT_MODE, key); byte[] decryptedBytes = new byte[cipher.getOutputSize(ctLength)]; int actualLength = cipher.update(stringBytes, 0, ctLength, decryptedBytes, 0); actualLength += cipher.doFinal(decryptedBytes, actualLength); Log.v("Crypto",new String(decryptedBytes)); Log.v("Crypto",actualLength+""); byte[] outputBytes = new byte[actualLength]; System.arraycopy(decryptedBytes, 0, outputBytes, 0, actualLength); return new String(outputBytes); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchProviderException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (ShortBufferException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } // if we get this far something has gone wrong // it's fairly safe to assume that this is due to an incorrect key return StegDroid.DECRYPTION_FAIL_MESSAGE; } public boolean isEncrypted(){ return encrypted; } }