package se.despotify.client.protocol.channel; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.IOException; import java.io.OutputStream; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; import java.security.NoSuchAlgorithmException; public class ChannelAudioHandler implements ChannelListener { private Cipher cipher; private Key key; private byte[] iv; private int offset; private OutputStream output; public ChannelAudioHandler(byte[] key, OutputStream output){ /* Get AES cipher instance. */ try { this.cipher = Cipher.getInstance("AES/CTR/NoPadding"); } catch (NoSuchAlgorithmException e){ System.err.println("AES not available! Aargh!"); } catch (NoSuchPaddingException e){ System.out.println("No padding not available... haha!"); } /* Create secret key from bytes. */ this.key = new SecretKeySpec(key, "AES"); /* Set IV. */ this.iv = new byte[]{ (byte)0x72, (byte)0xe0, (byte)0x67, (byte)0xfb, (byte)0xdd, (byte)0xcb, (byte)0xcf, (byte)0x77, (byte)0xeb, (byte)0xe8, (byte)0xbc, (byte)0x64, (byte)0x3f, (byte)0x63, (byte)0x0d, (byte)0x93 }; /* Initialize cipher with key and iv in encrypt mode. */ try { this.cipher.init(Cipher.ENCRYPT_MODE, this.key, new IvParameterSpec(this.iv)); } catch (InvalidKeyException e){ System.out.println("Invalid key!"); } catch (InvalidAlgorithmParameterException e){ System.out.println("Invalid IV!"); } /* Set output stream. */ this.output = output; } public void channelHeader(Channel channel, byte[] header){ /* Do nothing. */ } public void channelData(Channel channel, byte[] data){ /* Offsets needed for deinterleaving. */ int off, w, x, y, z; /* Allocate space for ciphertext. */ byte[] ciphertext = new byte[data.length + 1024]; byte[] keystream = new byte[16]; /* Decrypt each 1024 byte block. */ for(int block = 0; block < data.length / 1024; block++){ /* Deinterleave the 4x256 byte blocks. */ off = block * 1024; w = block * 1024 + 0 * 256; x = block * 1024 + 1 * 256; y = block * 1024 + 2 * 256; z = block * 1024 + 3 * 256; for(int i = 0; i < 1024 && (block * 1024 + i) < data.length; i += 4){ ciphertext[off++] = data[w++]; ciphertext[off++] = data[x++]; ciphertext[off++] = data[y++]; ciphertext[off++] = data[z++]; } /* Decrypt 1024 bytes block. This will fail for the last block. */ for(int i = 0; i < 1024 && (block * 1024 + i) < data.length; i += 16){ /* Produce 16 bytes of keystream from the IV. */ try{ keystream = this.cipher.doFinal(this.iv); } catch(IllegalBlockSizeException e){ e.printStackTrace(); } catch(BadPaddingException e){ e.printStackTrace(); } /* * Produce plaintext by XORing ciphertext with keystream. * And somehow I also need to XOR with the IV... Please * somebody tell me what I'm doing wrong, or is it the * Java implementation of AES? At least it works like this. */ for(int j = 0; j < 16; j++){ ciphertext[block * 1024 + i + j] ^= keystream[j] ^ this.iv[j]; } /* Update IV counter. */ for(int j = 15; j >= 0; j--){ this.iv[j] += 1; if((int)(this.iv[j] & 0xFF) != 0){ break; } } /* Set new IV. */ try{ this.cipher.init(Cipher.ENCRYPT_MODE, this.key, new IvParameterSpec(this.iv)); } catch(InvalidKeyException e){ e.printStackTrace(); } catch(InvalidAlgorithmParameterException e){ e.printStackTrace(); } } } /* Write data to output stream. */ try{ this.output.write(ciphertext, 0, ciphertext.length - 1024); } catch(IOException e){ /* Just don't care... */ } } public void channelEnd(Channel channel){ this.offset += channel.getDataLength(); Channel.unregister(channel.getId()); } public void channelError(Channel channel){ /* Do nothing. */ } }