package com.mindprod.example;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import static java.lang.System.*;
/**
* Demonstrate use of CipherOutputStream and CipherInputStream to encipher and decipher a message.
* <p/>
* This particular version uses AES/CBC/PKCS5Padding
* but it fairly easy to convert it to use other algorithms.
* Requires a shared secret key.
*
* @author Roedy Green, Canadian Mind Products
* @version 1.0 2008-06-17
* @since 2008-06-17
*/
public class TestCipherAES
{
/**
* configure with encryption algorithm to use. Avoid insecure DES. Changes to algorithm may require additional
* ivParms.
*/
private static final String ALGORITHM = "AES";
/**
* configure with block mode to use. Avoid insecure ECB.
*/
private static final String BLOCK_MODE = "CBC";
/**
* configure with padding method to use
*/
private static final String PADDING = "PKCS5Padding";
/**
* the encoding to use when converting bytes <--> String
*/
private static final Charset CHARSET = Charset.forName( "UTF-8" );
/**
* 128 bits worth of some random, not particularly secret, but stable bytes to salt AES-CBC with
*/
private static final IvParameterSpec CBC_SALT = new IvParameterSpec(
new byte[] {
7, 34, 56, 78, 90, 87, 65, 43,
12, 34, 56, 78, -123, 87, 65, 43 }
);
/**
* generate a random AES style Key
*
* @return the AES key generated.
* @throws java.security.NoSuchAlgorithmException if AES is not supported.
*/
private static SecretKeySpec generateKey()
throws NoSuchAlgorithmException
{
final KeyGenerator kg = KeyGenerator.getInstance( ALGORITHM );
kg.init( 128 );// specify key size in bits
final SecretKey secretKey = kg.generateKey();
final byte[] keyAsBytes = secretKey.getEncoded();
return new SecretKeySpec( keyAsBytes, ALGORITHM );
}
/**
* read an enciphered file and retrieve its plaintext message.
*
* @param cipher method used to encrypt the file
* @param key secret key used to encrypt the file
* @param file file where the message was written.
*
* @return the reconstituted decrypted message.
* @throws java.security.InvalidKeyException if something wrong with the key.
* @throws java.io.IOException if problems reading the file.
*/
@SuppressWarnings( { "JavaDoc" } )
private static String readCiphered( Cipher cipher, SecretKeySpec key, File file )
throws InvalidKeyException, IOException, InvalidAlgorithmParameterException
{
cipher.init( Cipher.DECRYPT_MODE, key, CBC_SALT );
final CipherInputStream cin = new CipherInputStream( new FileInputStream( file ), cipher );
final int messageLengthInBytes = ( cin.read() << 8 ) | cin.read();
out.println( file.length() + " enciphered bytes in file" );
out.println( messageLengthInBytes + " reconstituted bytes" );
final byte[] reconstitutedBytes = new byte[ messageLengthInBytes ];
int bytesReadSoFar = 0;
int bytesRemaining = messageLengthInBytes;
while ( bytesRemaining > 0 )
{
final int bytesThisChunk = cin.read( reconstitutedBytes, bytesReadSoFar, bytesRemaining );
if ( bytesThisChunk == 0 )
{
throw new IOException( file.toString() + " corrupted." );
}
bytesReadSoFar += bytesThisChunk;
bytesRemaining -= bytesThisChunk;
}
cin.close();
return new String( reconstitutedBytes, CHARSET );
}
/**
* write a plaintext message to a file enciphered.
*
* @param cipher the method to use to encrypt the file.
* @param key the secret key to use to encrypt the file.
* @param file the file to write the encrypted message to.
* @param plainText the plaintext of the message to write.
*
* @throws java.security.InvalidKeyException if something is wrong with they key
* @throws java.io.IOException if there are problems writing the file.
* @throws java.security.InvalidAlgorithmParameterException if problems with CBC_SALT.
*/
private static void writeCiphered( Cipher cipher, SecretKeySpec key, File file, String plainText )
throws InvalidKeyException, IOException, InvalidAlgorithmParameterException
{
cipher.init( Cipher.ENCRYPT_MODE, key, CBC_SALT );
final CipherOutputStream cout = new CipherOutputStream( new FileOutputStream( file ), cipher );
final byte[] plainTextBytes = plainText.getBytes( CHARSET );
out.println( plainTextBytes.length + " plaintext bytes written" );
cout.write( plainTextBytes.length >>> 8 );// msb
cout.write( plainTextBytes.length & 0xff );// lsb
cout.write( plainTextBytes );
cout.close();
}
/**
* Demonstrate use of CipherOutputStream and CipherInputStream to encipher and decipher a message.
*
* @param args not used
*
* @throws java.security.NoSuchAlgorithmException if AES is not supported
* @throws javax.crypto.NoSuchPaddingException if PKCS5 padding is not supported.
* @throws java.security.InvalidKeyException if there is something wrong with the key.
* @throws java.io.IOException if there are problems reading or writing the file.
* @throws java.security.InvalidAlgorithmParameterException if problems with CBC_SALT.
*/
public static void main( String[] args ) throws InvalidAlgorithmParameterException,
InvalidKeyException,
IOException,
NoSuchAlgorithmException,
NoSuchPaddingException
{
final String plainText = "Q.E. to throw cream pies at Cheney and Bush tomorrow at 19:05.";
SecretKeySpec key = generateKey();
final Cipher cipher = Cipher.getInstance( ALGORITHM + "/" + BLOCK_MODE + "/" + PADDING );
writeCiphered( cipher, key, new File( "transport.bin" ), plainText );
final String reconstitutedText = readCiphered( cipher, key, new File( "transport.bin" ) );
out.println( "original: " + plainText );
out.println( "reconstituted: " + reconstitutedText );
}
}