package freenet.crypt.ciphers;
import java.security.InvalidKeyException;
import java.security.GeneralSecurityException;
import java.security.Provider;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import freenet.crypt.BlockCipher;
import freenet.crypt.JceLoader;
import freenet.crypt.UnsupportedCipherException;
import freenet.support.Logger;
/*
This code is part of the Java Adaptive Network Client by Ian Clarke.
It is distributed under the GNU Public Licence (GPL) version 2. See
http://www.gnu.org/ for further details of the GPL.
*/
/**
* Interfaces with the Rijndael AES candidate to implement the Rijndael
* algorithm
*/
public class Rijndael implements BlockCipher {
private Object sessionKey;
private final int keysize, blocksize;
public static final Provider AesCtrProvider = getAesCtrProvider();
public static String getProviderName() {
return AesCtrProvider != null ? AesCtrProvider.getName() : null;
}
static private long benchmark(Cipher cipher, SecretKeySpec key, IvParameterSpec IV) throws GeneralSecurityException
{
long times = Long.MAX_VALUE;
byte[] input = new byte[1024];
byte[] output = new byte[input.length*32];
cipher.init(Cipher.ENCRYPT_MODE, key, IV);
// warm-up
for (int i = 0; i < 32; i++) {
cipher.doFinal(input, 0, input.length, output, 0);
System.arraycopy(output, 0, input, 0, input.length);
}
for (int i = 0; i < 128; i++) {
long startTime = System.nanoTime();
cipher.init(Cipher.ENCRYPT_MODE, key, IV);
for (int j = 0; j < 4; j++) {
int ofs = 0;
for (int k = 0; k < 32; k ++) {
ofs += cipher.update(input, 0, input.length, output, ofs);
}
cipher.doFinal(output, ofs);
}
long endTime = System.nanoTime();
times = Math.min(endTime - startTime, times);
System.arraycopy(output, 0, input, 0, input.length);
}
return times;
}
/** @return null if JCA is crippled (restricted to 128-bit) so we need
* to use this class. */
private static Provider getAesCtrProvider() {
try {
final String algo = "AES/CTR/NOPADDING";
final Provider bcastle = JceLoader.BouncyCastle;
final Class<?> clazz = Rijndael.class;
byte[] key = new byte[32]; // Test for whether 256-bit works.
byte[] iv = new byte[16];
byte[] plaintext = new byte[16];
SecretKeySpec k = new SecretKeySpec(key, "AES");
IvParameterSpec IV = new IvParameterSpec(iv);
Cipher c = Cipher.getInstance(algo);
c.init(Cipher.ENCRYPT_MODE, k, IV);
// ^^^ resolve provider
Provider provider = c.getProvider();
if (bcastle != null) {
// BouncyCastle provider is faster (in some configurations)
try {
Cipher bcastle_cipher = Cipher.getInstance(algo, bcastle);
bcastle_cipher.init(Cipher.ENCRYPT_MODE, k, IV);
Provider bcastle_provider = bcastle_cipher.getProvider();
if (provider != bcastle_provider) {
long time_def = benchmark(c, k, IV);
long time_bcastle = benchmark(bcastle_cipher, k, IV);
System.out.println(algo + " (" + provider + "): " + time_def + "ns");
System.out.println(algo + " (" + bcastle_provider + "): " + time_bcastle + "ns");
Logger.minor(clazz, algo + "/" + provider + ": " + time_def + "ns");
Logger.minor(clazz, algo + "/" + bcastle_provider + ": " + time_bcastle + "ns");
if (time_bcastle < time_def) {
provider = bcastle_provider;
c = bcastle_cipher;
}
}
} catch(GeneralSecurityException e) {
// ignore
Logger.warning(clazz, algo + "@" + bcastle + " benchmark failed", e);
} catch(Throwable e) {
// ignore
Logger.error(clazz, algo + "@" + bcastle + " benchmark failed", e);
}
}
c = Cipher.getInstance(algo, provider);
c.init(Cipher.ENCRYPT_MODE, k, IV);
c.doFinal(plaintext);
Logger.normal(Rijndael.class, "Using JCA: provider "+provider);
System.out.println("Using JCA cipher provider: "+provider);
return provider;
} catch (GeneralSecurityException e) {
Logger.warning(Rijndael.class, "Not using JCA as it is crippled (can't use 256-bit keys). Will use built-in encryption. ", e);
return null;
}
}
/**
* Create a Rijndael instance.
* @param keysize The key size.
* @param blocksize The block size.
* @throws UnsupportedCipherException
*/
public Rijndael(int keysize, int blocksize) throws UnsupportedCipherException {
if (! ((keysize == 128) ||
(keysize == 192) ||
(keysize == 256)))
throw new UnsupportedCipherException("Invalid keysize");
if (! ((blocksize == 128) ||
(blocksize == 256)))
throw new UnsupportedCipherException("Invalid blocksize");
this.keysize=keysize;
this.blocksize=blocksize;
}
// for Util.getCipherByName.. and yes, screw you too, java
public Rijndael() {
this.keysize = 128;
this.blocksize = 128;
}
@Override
public final int getBlockSize() {
return blocksize;
}
@Override
public final int getKeySize() {
return keysize;
}
@Override
public final void initialize(byte[] key) {
try {
byte[] nkey=new byte[keysize>>3];
System.arraycopy(key, 0, nkey, 0, nkey.length);
sessionKey=Rijndael_Algorithm.makeKey(nkey, blocksize/8);
} catch (InvalidKeyException e) {
e.printStackTrace();
Logger.error(this,"Invalid key");
}
}
@Override
public synchronized final void encipher(byte[] block, byte[] result) {
if(block.length != blocksize/8)
throw new IllegalArgumentException();
Rijndael_Algorithm.blockEncrypt(block, result, 0, sessionKey, blocksize/8);
}
@Override
public synchronized final void decipher(byte[] block, byte[] result) {
if(block.length != blocksize/8)
throw new IllegalArgumentException();
Rijndael_Algorithm.blockDecrypt(block, result, 0, sessionKey, blocksize/8);
}
}