package com.mindprod.example;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import static java.lang.System.*;
/**
* Demonstrate use of CipherOutputStream and CipherInputStream to encipher and decipher a message.
* <p/>
* This particular version uses RSA/ECB/PKCS1Padding
* but it fairly easy to convert it to use other algorithms.
* RSA requires a digital certificate in your .keystore.
* Does not require a shared secret key.
* Works with a public/private key pair stored in certificates.
*
* @author Roedy Green, Canadian Mind Products
* @version 1.0 2008-07-07
* @since 2008-07-07
*/
public class TestCipherRSA
{
/**
* configure with encryption algorithm to use. Changes to algorithm may require additional ivParms.
*/
private static final String ALGORITHM = "RSA";
/**
* configure with block mode to use. We have to use insecure ECB since Sun support nothing else.
*/
private static final String BLOCK_MODE = "ECB";
/**
* where to find .keystore. There should be a way to automatically find the default .keystore.
* perhaps via a System property.
*/
private static final String KEYSTORE_FILENAME = "C:/users/roedy/.keystore";
/**
* configure with padding method to use
*/
private static final String PADDING = "PKCS1Padding";
/**
* alias of the RSA certificate in .keystore in JKS format. Contains private key of recipient.
*/
private static final String RECEIVERS_PRIVATE_CERTIFICATE_ALIAS = "mindprodcert2010rsa";
/**
* receiver's public key in standalone certificate, in x.509 format
*/
private static final String RECEIVERS_PUBLIC_CERTIFICATE = "E:/mindprod/contact/mindprodcert2010rsa.cer";
/**
* the encoding to use when converting bytes <--> String
*/
private static final Charset CHARSET = Charset.forName( "UTF-8" );
/**
* configure .keystore password
*/
private static final char[] PASSWORD = "sesame".toCharArray();
/**
* get the receiver's private cert from .keystore.
*
* @return containing private key
* @throws java.security.KeyStoreException if .keystore corrupt.
* @throws java.io.IOException if trouble loading cert.
* @throws java.security.NoSuchAlgorithmException if no JCE support.
* @throws java.security.cert.CertificateException if certificate corrupt.
* @throws java.security.UnrecoverableKeyException if wrong password.
*/
private static PrivateKey getPrivateKey()
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
UnrecoverableKeyException
{
final KeyStore keystore = KeyStore.getInstance( "JKS" );
keystore.load( new FileInputStream( KEYSTORE_FILENAME ), null );
return ( PrivateKey ) keystore.getKey( RECEIVERS_PRIVATE_CERTIFICATE_ALIAS, PASSWORD );
}
/**
* get the receiver's public key from standalone cert file.
*
* @return public key
* @throws java.io.FileNotFoundException if missing certificate file.
* @throws java.security.cert.CertificateException if certificate corrupt.
*/
private static PublicKey getPublicKey()
throws FileNotFoundException, CertificateException
{
final FileInputStream fis = new FileInputStream( RECEIVERS_PUBLIC_CERTIFICATE );
final CertificateFactory cf = CertificateFactory.getInstance( "X.509" );
return cf.generateCertificate( fis ).getPublicKey();
}
/**
* read an enciphered file and retrieve its plaintext message.
*
* @param cipher method used to encrypt the file
* @param privateKey private key of recipient.
* @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, PrivateKey privateKey, File file )
throws InvalidKeyException, IOException
{
cipher.init( Cipher.DECRYPT_MODE, privateKey );
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 publicKey public key of recipient.
* @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 the key
* @throws java.io.IOException if there are problems writing the file.
* .
*/
private static void writeCiphered( Cipher cipher, PublicKey publicKey, File file, String plainText )
throws InvalidKeyException, IOException
{
cipher.init( Cipher.ENCRYPT_MODE, publicKey );
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 RSA 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 programming error
* @throws java.security.NoSuchProviderException if no JCE support
* @throws java.security.KeyStoreException if .keystore corrupt.
* @throws java.security.cert.CertificateException if either certificate corrupt.
* @throws java.security.UnrecoverableKeyException if wrong password
*/
public static void main( String[] args ) throws
CertificateException,
InvalidAlgorithmParameterException,
InvalidKeyException,
IOException,
KeyStoreException,
NoSuchAlgorithmException,
NoSuchPaddingException,
NoSuchProviderException,
UnrecoverableKeyException
{
final String plainText = "W. to visit Abu Ghraib for a hands on, wink wink, tomorrow at 19:05.";
final Cipher cipher = Cipher.getInstance( ALGORITHM + "/" + BLOCK_MODE + "/" + PADDING );
writeCiphered( cipher, getPublicKey(), new File( "transport.bin" ), plainText );
final String reconstitutedText = readCiphered( cipher, getPrivateKey(), new File( "transport.bin" ) );
out.println( "original: " + plainText );
out.println( "reconstituted: " + reconstitutedText );
}
}