package com.subgraph.orchid.crypto;
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import com.subgraph.orchid.TorException;
public class TorStreamCipher {
public static final int KEY_LEN = 16;
public static TorStreamCipher createWithRandomKey() {
final SecretKey randomKey = generateRandomKey();
return new TorStreamCipher(randomKey.getEncoded());
}
public static TorStreamCipher createFromKeyBytes(byte[] keyBytes) {
return new TorStreamCipher(keyBytes);
}
public static TorStreamCipher createFromKeyBytesWithIV(byte[] keyBytes, byte[] iv) {
return new TorStreamCipher(keyBytes, iv);
}
private static final int BLOCK_SIZE = 16;
private final Cipher cipher;
private final byte[] counter;
private final byte[] counterOut;
/* Next byte of keystream in counterOut */
private int keystreamPointer = -1;
private final SecretKeySpec key;
private TorStreamCipher(byte[] keyBytes) {
this(keyBytes, null);
}
private TorStreamCipher(byte[] keyBytes, byte[] iv) {
key = keyBytesToSecretKey(keyBytes);
cipher = createCipher(key);
counter = new byte[BLOCK_SIZE];
counterOut = new byte[BLOCK_SIZE];
if(iv != null) {
applyIV(iv);
}
}
private void applyIV(byte[] iv) {
if(iv.length != BLOCK_SIZE) {
throw new IllegalArgumentException();
}
System.arraycopy(iv, 0, counter, 0, BLOCK_SIZE);
}
public void encrypt(byte[] data) {
encrypt(data, 0, data.length);
}
public synchronized void encrypt(byte[] data, int offset, int length) {
for(int i = 0; i < length; i++)
data[i + offset] ^= nextKeystreamByte();
}
public byte[] getKeyBytes() {
return key.getEncoded();
}
private static SecretKeySpec keyBytesToSecretKey(byte[] keyBytes) {
return new SecretKeySpec(keyBytes, "AES");
}
private static Cipher createCipher(SecretKeySpec keySpec) {
try {
final Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
return cipher;
} catch (GeneralSecurityException e) {
throw new TorException(e);
}
}
private static SecretKey generateRandomKey() {
try {
KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(128);
return generator.generateKey();
} catch (GeneralSecurityException e) {
throw new TorException(e);
}
}
private byte nextKeystreamByte() {
if(keystreamPointer == -1 || (keystreamPointer >= BLOCK_SIZE))
updateCounter();
return counterOut[keystreamPointer++];
}
private void updateCounter() {
encryptCounter();
incrementCounter();
keystreamPointer = 0;
}
private void encryptCounter() {
try {
cipher.doFinal(counter, 0, BLOCK_SIZE, counterOut, 0);
} catch (GeneralSecurityException e) {
throw new TorException(e);
}
}
private void incrementCounter() {
int carry = 1;
for(int i = counter.length - 1; i >= 0; i--) {
int x = (counter[i] & 0xff) + carry;
if(x > 0xff)
carry = 1;
else
carry = 0;
counter[i] = (byte)x;
}
}
}