/* This code is part of Freenet. It is distributed under the GNU General * Public License, version 2 (or at your option any later version). See * http://www.gnu.org/ for further details of the GPL. */ package freenet.crypt; import java.io.Serializable; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.BitSet; import javax.crypto.Cipher; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.ShortBufferException; import javax.crypto.spec.IvParameterSpec; import freenet.crypt.ciphers.Rijndael; import freenet.support.Fields; /** * CryptByteBuffer will encrypt and decrypt both byte[]s and BitSets with a specified * algorithm, key, and also an iv if the algorithm requires one. Note that the input and the * output are the same length, i.e. these are either stream ciphers or non-padded block ciphers * (where it must be called with whole blocks). For a stream cipher, once a CryptByteBuffer is * initialised, processed bytes will be treated as a single stream, i.e. repeatedly encrypting the * same data will give different results. * @author unixninja92 * * Suggested CryptByteBufferType to use: ChaCha128 */ @SuppressWarnings("deprecation") // Suppresses warnings about RijndaelPCFB being deprecated public final class CryptByteBuffer implements Serializable{ private static final long serialVersionUID = 6143338995971755362L; private final CryptByteBufferType type; private final SecretKey key; private IvParameterSpec iv; //Used for AES and ChaCha ciphers private Cipher encryptCipher; private Cipher decryptCipher; //These variables are used with Rijndael ciphers private BlockCipher blockCipher; private PCFBMode encryptPCFB; private PCFBMode decryptPCFB; /** * Creates an instance of CryptByteBuffer that will be able to encrypt and decrypt * sets of bytes using the specified algorithm type with the given key. If the * algorithm requires an iv, it will either use the one passed in, or if that is * null, it will generate a random one. * @param type The symmetric algorithm, mode, and key and block size to use * @param key The key that will be used for encryption * @param iv The iv that will be used for encryption. * @throws InvalidAlgorithmParameterException * @throws InvalidKeyException */ public CryptByteBuffer(CryptByteBufferType type, SecretKey key, IvParameterSpec iv) throws InvalidKeyException, InvalidAlgorithmParameterException{ if(iv != null && !type.hasIV()){ throw new UnsupportedTypeException(type, "This type does not take an IV."); } else if(iv != null){ this.iv = iv; } else if(type.hasIV()){ genIV(); } this.type = type; this.key = key; try{ if(type.cipherName.equals("RIJNDAEL")){ blockCipher = new Rijndael(type.keyType.keySize, type.blockSize); blockCipher.initialize(key.getEncoded()); if(type == CryptByteBufferType.RijndaelPCFB){ encryptPCFB = PCFBMode.create(blockCipher, this.iv.getIV()); decryptPCFB = PCFBMode.create(blockCipher, this.iv.getIV()); } } else{ encryptCipher = Cipher.getInstance(type.algName); decryptCipher = Cipher.getInstance(type.algName); encryptCipher.init(Cipher.ENCRYPT_MODE, this.key, this.iv); decryptCipher.init(Cipher.DECRYPT_MODE, this.key, this.iv); } } catch (UnsupportedCipherException e) { throw new Error(e); // Should be impossible as we bundle BC } catch (NoSuchAlgorithmException e) { throw new Error(e); // Should be impossible as we bundle BC } catch (NoSuchPaddingException e) { throw new Error(e); // Should be impossible as we don't use padded modes } } /** * Creates an instance of CryptByteBuffer that will be able to encrypt and decrypt * sets of bytes using the specified algorithm type with the given key. If the * algorithm requires an iv, it will generate a random one. * @param type The symmetric algorithm, mode, and key and block size to use * @param key The key that will be used for encryption * @throws InvalidAlgorithmParameterException * @throws InvalidKeyException */ public CryptByteBuffer(CryptByteBufferType type, SecretKey key) throws GeneralSecurityException{ this(type, key, (IvParameterSpec)null); } /** * Creates an instance of CryptByteBuffer that will be able to encrypt and decrypt * sets of bytes using the specified algorithm type with the given key. If the * algorithm requires an iv, it will generate a random one. * @param type The symmetric algorithm, mode, and key and block size to use * @param key The key that will be used for encryption * @throws InvalidAlgorithmParameterException * @throws InvalidKeyException */ public CryptByteBuffer(CryptByteBufferType type, byte[] key) throws GeneralSecurityException{ this(type, KeyGenUtils.getSecretKey(type.keyType, key)); } /** * Creates an instance of CryptByteBuffer that will be able to encrypt and decrypt * sets of bytes using the specified algorithm type with the given key. If the * algorithm requires an iv, it will generate a random one. * @param type The symmetric algorithm, mode, and key and block size to use * @param key The key that will be used for encryption * @throws InvalidAlgorithmParameterException * @throws InvalidKeyException */ public CryptByteBuffer(CryptByteBufferType type, ByteBuffer key) throws GeneralSecurityException{ this(type, Fields.copyToArray(key)); } /** * Creates an instance of CryptByteBuffer that will be able to encrypt and decrypt * sets of bytes using the specified algorithm type with the given key and iv. * The iv will be extracted from the passed in byte[] starting at the offset * using the length provided by type.ivSize * @param type The symmetric algorithm, mode, and key and block size to use * @param key The key that will be used for encryption * @param iv The byte[] containing the iv * @param offset Where in the byte[] the iv starts * @throws InvalidKeyException * @throws InvalidAlgorithmParameterException */ public CryptByteBuffer(CryptByteBufferType type, SecretKey key, byte[] iv, int offset) throws InvalidKeyException, InvalidAlgorithmParameterException{ this(type, key, new IvParameterSpec(iv, offset, type.ivSize)); } /** * Creates an instance of CryptByteBuffer that will be able to encrypt and decrypt * sets of bytes using the specified algorithm type with the given key and iv. * @param type The symmetric algorithm, mode, and key and block size to use * @param key The key that will be used for encryption * @param iv The iv that will be used for encryption. * @throws InvalidAlgorithmParameterException * @throws InvalidKeyException */ public CryptByteBuffer(CryptByteBufferType type, SecretKey key, byte[] iv) throws InvalidKeyException, InvalidAlgorithmParameterException{ this(type, key, iv, 0); } /** * Creates an instance of CryptByteBuffer that will be able to encrypt and decrypt * sets of bytes using the specified algorithm type with the given key and iv. * @param type The symmetric algorithm, mode, and key and block size to use * @param key The key that will be used for encryption * @param iv The iv that will be used for encryption. * @throws InvalidAlgorithmParameterException * @throws InvalidKeyException */ public CryptByteBuffer(CryptByteBufferType type, SecretKey key, ByteBuffer iv) throws InvalidKeyException, InvalidAlgorithmParameterException{ this(type, key, Fields.copyToArray(iv), 0); } /** * Creates an instance of CryptByteBuffer that will be able to encrypt and decrypt * sets of bytes using the specified algorithm type with the given key and iv. * The iv will be extracted from the passed in byte[] starting at the offset * using the length provided by type.ivSize * @param type The symmetric algorithm, mode, and key and block size to use * @param key The key that will be used for encryption * @param iv The byte[] containing the iv * @param offset Where in the byte[] the iv starts * @throws InvalidKeyException * @throws InvalidAlgorithmParameterException */ public CryptByteBuffer(CryptByteBufferType type, byte[] key, byte[] iv, int offset) throws InvalidKeyException, InvalidAlgorithmParameterException{ this(type, KeyGenUtils.getSecretKey(type.keyType, key), iv, offset); } /** * Creates an instance of CryptByteBuffer that will be able to encrypt and decrypt * sets of bytes using the specified algorithm type with the given key and iv. * @param type The symmetric algorithm, mode, and key and block size to use * @param key The key that will be used for encryption * @param iv The iv that will be used for encryption. * @throws InvalidAlgorithmParameterException * @throws InvalidKeyException */ public CryptByteBuffer(CryptByteBufferType type, byte[] key, byte[] iv) throws InvalidKeyException, InvalidAlgorithmParameterException{ this(type, key, iv, 0); } /** * Creates an instance of CryptByteBuffer that will be able to encrypt and decrypt * sets of bytes using the specified algorithm type with the given key and iv. * @param type The symmetric algorithm, mode, and key and block size to use * @param key The key that will be used for encryption * @param iv The iv that will be used for encryption. * @throws InvalidAlgorithmParameterException * @throws InvalidKeyException */ public CryptByteBuffer(CryptByteBufferType type, ByteBuffer key, ByteBuffer iv) throws InvalidKeyException, InvalidAlgorithmParameterException{ this(type, Fields.copyToArray(key), Fields.copyToArray(iv), 0); } /** Encrypt the specified section of the provided byte[] to the output byte[], without * modifying the input. */ public void encrypt(byte[] input, int offset, int len, byte[] output, int outputOffset) { if(offset+len > input.length) throw new IllegalArgumentException(); if(input == output && offset != outputOffset) { // FIXME only copy if it actually overlaps... byte[] temp = Arrays.copyOfRange(input, offset, offset+len); encrypt(temp, 0, temp.length); System.arraycopy(temp, 0, output, outputOffset, len); return; } if(type == CryptByteBufferType.RijndaelPCFB){ System.arraycopy(input, offset, output, outputOffset, len); encryptPCFB.blockEncipher(output, outputOffset, len); } else if(type.cipherName.equals("RIJNDAEL")){ if(offset == 0 && len == input.length && outputOffset == 0 && len == output.length) blockCipher.encipher(input, output); else { byte[] result = new byte[len]; blockCipher.encipher(Arrays.copyOfRange(input, offset, offset+len), result); System.arraycopy(result, 0, output, outputOffset, len); } } else { try { int copied = encryptCipher.update(input, offset, len, output, outputOffset); if(copied != len) throw new IllegalStateException("Not a stream cipher???"); } catch (ShortBufferException e) { throw new IllegalArgumentException(e); } } } /** Encrypt the specified section of the provided byte[] in-place */ public void encrypt(byte[] input, int offset, int len) { encrypt(input, offset, len, input, offset); } /** * Encrypts the specified section of provided byte[] into a new array returned as a ByteBuffer. * Does not modify the original array. If you are using a RijndaelECB alg then len must equal * the block size. * @param input The bytes to be encrypted. Contents will not be modified. * @param offset The position of input to start encrypting at * @param len The number of bytes after offset to encrypt * @return Returns a new array containing the ciphertext encoding the specified range. */ public byte[] encryptCopy(byte[] input, int offset, int len){ byte[] output = Arrays.copyOfRange(input, offset, offset+len); encrypt(input, offset, len, output, 0); return output; } /** * Encrypts the provided byte[]. If you are using a RijndaelECB * alg then the length of input must equal the block size. * @param input The byte[] to be encrypted * @return The encrypted data. The original data will be unchanged. */ public byte[] encryptCopy(byte[] input){ return encryptCopy(input, 0, input.length); } /** * Encrypts the provided ByteBuffer, returning a new ByteBuffer. Only reads the bytes that are * actually readable, i.e. from position to limit, so equivalent to get()ing into a buffer, * encrypting that and returning. If you are using a RijndaelECB alg then the length of input * must equal the block size. * @param input The byte[] to be encrypted * @return A new ByteBuffer containing the ciphertext. It will have a backing array and its * arrayOffset() will be 0, its position will be 0 and its capacity will be the length of the * input data. */ public ByteBuffer encryptCopy(ByteBuffer input){ if(input.hasArray()) return ByteBuffer.wrap(encryptCopy(input.array(), input.arrayOffset() + input.position(), input.remaining())); else { return ByteBuffer.wrap(encryptCopy(Fields.copyToArray(input))); } } /** Get bytes from one ByteBuffer and encrypt them and put them into the other ByteBuffer. */ public void encrypt(ByteBuffer input, ByteBuffer output) { if(input.hasArray() && output.hasArray()) { int moved = Math.min(input.remaining(), output.remaining()); encrypt(input.array(), input.arrayOffset()+input.position(), moved, output.array(), output.arrayOffset()+output.position()); input.position(input.position()+moved); output.position(output.position()+moved); } else if(!(type == CryptByteBufferType.RijndaelPCFB || type.cipherName.equals("RIJNDAEL"))) { // Use ByteBuffer to ByteBuffer operations. try { int copy = Math.min(input.remaining(), output.remaining()); int copied = encryptCipher.update(input, output); if(copied != copy) throw new IllegalStateException("Not a stream cipher???"); } catch (ShortBufferException e) { throw new Error("Impossible: "+e, e); } } else { // FIXME use a smaller temporary buffer int moved = Math.min(input.remaining(), output.remaining()); byte[] buf = new byte[moved]; input.get(buf); encrypt(buf, 0, buf.length); output.put(buf); } } // FIXME /* BitSet based operations commented out. If you need them, wait until we are using java 7, * or implement your own toByteArray(). Please don't steal it from OpenJDK because we are GPL2+ * and link ASL2 code, therefore it is illegal for us to use GPL2-only code such as OpenJDK. * Please test very, VERY carefully, as it's essential that the representation not change when * we do switch to using java 7's BitSet.toByteArray(). The javadocs give a precise definition * so you can test it with unit tests. */ // /** // * Encrypts the provided BitSet. If you are using a RijndaelECB // * alg then the length of input must equal the block size. // * @param input The BitSet to encrypt // * @return The encrypted BitSet // */ // public BitSet encrypt(BitSet input){ // return BitSet.valueOf(encrypt(input.toByteArray())); // } /** Decrypt the specified section of the provided byte[] to the output byte[], without * modifying the input. */ public void decrypt(byte[] input, int offset, int len, byte[] output, int outputOffset) { if(offset+len > input.length) throw new IllegalArgumentException(); if(input == output && offset != outputOffset) { // FIXME only copy if it actually overlaps... byte[] temp = Arrays.copyOfRange(input, offset, offset+len); decrypt(temp, 0, temp.length); System.arraycopy(temp, 0, output, outputOffset, len); return; } if(type == CryptByteBufferType.RijndaelPCFB){ System.arraycopy(input, offset, output, outputOffset, len); decryptPCFB.blockDecipher(output, outputOffset, len); } else if(type.cipherName.equals("RIJNDAEL")){ if(offset == 0 && len == input.length && outputOffset == 0 && len == output.length) blockCipher.decipher(input, output); else { byte[] result = new byte[len]; blockCipher.decipher(Arrays.copyOfRange(input, offset, offset+len), result); System.arraycopy(result, 0, output, outputOffset, len); } } else { try { int copied = decryptCipher.update(input, offset, len, output, outputOffset); if(copied != len) throw new IllegalStateException("Not a stream cipher???"); } catch (ShortBufferException e) { throw new IllegalArgumentException(e); } } } /** Decrypt the specified section of the provided byte[] in-place */ public void decrypt(byte[] input, int offset, int len) { decrypt(input, offset, len, input, offset); } /** * Decrypts the specified section of provided byte[] into an array which is returned as a * ByteBuffer. Does not modify the original array. If you are using a RijndaelECB alg then len * must equal the block size. * @param input The bytes to be decrypted. Contents will not be modified. * @param offset The position of input to start decrypting at * @param len The number of bytes after offset to decrypt * @return Returns the decrypted plaintext, a newly allocated byte array of the same length as * the input data. */ public byte[] decryptCopy(byte[] input, int offset, int len){ byte[] output = Arrays.copyOfRange(input, offset, offset+len); decrypt(input, offset, len, output, 0); return output; } /** * Decrypts the provided byte[]. If you are using a RijndaelECB * alg then the length of input must equal the block size. * @param input The byte[] to be decrypted * @return The decrypted plaintext bytes. */ public byte[] decryptCopy(byte[] input){ return decryptCopy(input, 0, input.length); } /** * Decrypts the provided ByteBuffer, returning a new ByteBuffer. Only reads the bytes that are * actually readable, i.e. from position to limit, so equivalent to get()ing into a buffer, * decrypting that and returning. If you are using a RijndaelECB alg then the length of input * must equal the block size. * @param input The buffer to be decrypted * @return A new ByteBuffer containing the plaintext. It will have a backing array and its * arrayOffset() will be 0, its position will be 0 and its capacity will be the length of the * input data. */ public ByteBuffer decryptCopy(ByteBuffer input){ if(input.hasArray()) return ByteBuffer.wrap(decryptCopy(input.array(), input.arrayOffset() + input.position(), input.remaining())); else return ByteBuffer.wrap(decryptCopy(Fields.copyToArray(input))); } /** Get bytes from one ByteBuffer and encrypt them and put them into the other ByteBuffer. */ public void decrypt(ByteBuffer input, ByteBuffer output) { if(input.hasArray() && output.hasArray()) { int moved = Math.min(input.remaining(), output.remaining()); decrypt(input.array(), input.arrayOffset()+input.position(), moved, output.array(), output.arrayOffset()+output.position()); input.position(input.position()+moved); output.position(output.position()+moved); } else if(!(type == CryptByteBufferType.RijndaelPCFB || type.cipherName.equals("RIJNDAEL"))) { // Use ByteBuffer to ByteBuffer operations. try { int copy = Math.min(input.remaining(), output.remaining()); int copied = decryptCipher.update(input, output); if(copied != copy) throw new IllegalStateException("Not a stream cipher???"); } catch (ShortBufferException e) { throw new Error("Impossible: "+e, e); } } else { // FIXME use a smaller temporary buffer int moved = Math.min(input.remaining(), output.remaining()); byte[] buf = new byte[moved]; input.get(buf); decrypt(buf, 0, buf.length); output.put(buf); } } // FIXME // /** // * Decrypts the provided BitSet. If you are using a RijndaelECB // * alg then the length of input must equal the block size. // * @param input The BitSet to decrypt // * @return The decrypted BitSet // */ // public BitSet decrypt(BitSet input){ // return BitSet.valueOf(decrypt(input.toByteArray())); // } /** * Changes the current iv to the provided iv and initializes the cipher instances with * the new iv. Only works with algorithms that support IVs, not RijndaelPCFB. * @param iv The new iv to use as IvParameterSpec * @throws InvalidAlgorithmParameterException */ public void setIV(IvParameterSpec iv) throws InvalidAlgorithmParameterException{ if(!type.hasIV()){ throw new UnsupportedTypeException(type); } this.iv = iv; try { encryptCipher.init(Cipher.ENCRYPT_MODE, this.key, this.iv); decryptCipher.init(Cipher.DECRYPT_MODE, this.key, this.iv); } catch (InvalidKeyException e) { throw new IllegalArgumentException(e); } } /** * Generates a new IV to be used and initializes the cipher instances with * the new iv. Only works with algorithms that support IVs, not RijndaelPCFB. * @return The generated IV */ public IvParameterSpec genIV(){ if(!type.hasIV()){ throw new UnsupportedTypeException(type); } this.iv = KeyGenUtils.genIV(type.ivSize); try { encryptCipher.init(Cipher.ENCRYPT_MODE, this.key, this.iv); decryptCipher.init(Cipher.DECRYPT_MODE, this.key, this.iv); } catch (InvalidKeyException e) { throw new IllegalArgumentException(e); // Definitely a bug ... } catch (InvalidAlgorithmParameterException e) { throw new IllegalArgumentException(e); // Definitely a bug ... } return iv; } /** * Gets the IV being used. Only works with algorithms that support IVs. * @return Returns the iv as a IvParameterSpec */ public IvParameterSpec getIV(){ if(!type.hasIV()){ throw new UnsupportedTypeException(type); } return iv; } }