Go to the documentation of this file.00001 
00002 package crypto;
00003 
00004 import java.io.BufferedReader;
00005 import java.io.BufferedWriter;
00006 import java.io.ByteArrayInputStream;
00007 import java.io.ByteArrayOutputStream;
00008 import java.io.FileNotFoundException;
00009 import java.io.FileReader;
00010 import java.io.FileWriter;
00011 import java.io.IOException;
00012 import java.io.ObjectInputStream;
00013 import java.io.Serializable;
00014 import java.security.InvalidKeyException;
00015 import java.security.KeyPair;
00016 import java.security.KeyPairGenerator;
00017 import java.security.NoSuchAlgorithmException;
00018 import java.security.PrivateKey;
00019 import java.security.PublicKey;
00020 import java.security.Security;
00021 import java.security.Signature;
00022 import java.security.SignatureException;
00023 import java.security.SignedObject;
00024 import java.text.SimpleDateFormat;
00025 import java.util.Calendar;
00026 
00027 import javax.crypto.Cipher;
00028 import javax.crypto.NoSuchPaddingException;
00029 import javax.crypto.SecretKey;
00030 
00031 import utils.Log;
00032 import utils.Base64;
00033 
00034 
00035 
00036 
00037 
00038 
00039 
00040 
00041 
00042 
00043 
00044 
00045 
00046 
00047 
00048 
00049 
00050 public class AsymmetricCipher
00051 {
00052 
00053 
00054 
00055     private final static String algorithm = "RSA";
00056     
00057 
00058 
00059 
00060     private final static int keySize = 1024;
00061 
00062 
00063 
00064 
00065 
00066 
00067     private final static String padding = "/ECB/PKCS1PADDING";
00068 
00069 
00070 
00071 
00072     private final static String digest  = "SHA1";
00073     
00074 
00075 
00076 
00077     private final static String privateKeyFile = "mykf-private-key.txt";
00078     
00079 
00080 
00081 
00082     private final static String publicKeyFile = "mykf-public-key.txt";
00083 
00084 
00085 
00086 
00087     private PrivateKey privateKey = null;
00088     
00089 
00090 
00091 
00092     private PublicKey publicKey = null;
00093     
00094 
00095 
00096 
00097     private String keyPairComment = null;
00098 
00099 
00100 
00101 
00102     private Cipher cipher = null;
00103     
00104 
00105 
00106 
00107     private String serializedPublicKey = null;
00108 
00109 
00110 
00111 
00112     public AsymmetricCipher ()
00113     {
00114         boolean loadedKeys = loadSavedKeyPair ();
00115         boolean sanity = false;
00116         
00117         while( true )
00118         {
00119             if ( ! loadedKeys  ) {
00120                 generateKeyPair ();
00121             }
00122     
00123             instantiateCipher ();
00124             
00125             serializePublicKey ();
00126             
00127             if ( ! isActive () ) {
00128                 destruct (); 
00129                 if ( loadedKeys ) {
00130                     Log.warn( "Load key pair inactive; Generating new key pair..." );
00131                     continue;
00132                 } else {
00133                     Log.error( "AsymmetricCipher: Generated key pair inactive." );
00134                     return;
00135                 }
00136             }
00137     
00138             sanity = sanityCheck ();
00139             if ( sanity ) {
00140                 break; 
00141             }
00142             
00143             if ( loadedKeys )  {
00144                 Log.warn( "Sanity check failed on loaded key pair: Retrying..." );
00145                 loadedKeys = false;
00146             } else {
00147                 Log.error( "AsymmetricCipher: Sanity check failed on generated key pair." );
00148                 destruct ();
00149                 return;
00150             }
00151         }
00152 
00153         
00154 
00155         if ( isActive () && sanity && ! loadedKeys ) {
00156             saveKeyPair ();
00157             exportPublicKey( null );
00158         }
00159     }
00160     
00161 
00162 
00163 
00164     private void destruct ()
00165     {
00166         this.privateKey = null;
00167         this.publicKey = null;
00168         this.cipher = null;
00169         this.serializedPublicKey = null;
00170     }
00171     
00172 
00173 
00174 
00175     private boolean loadSavedKeyPair ()
00176     {
00177         this.privateKey = null;
00178         this.publicKey = null;
00179         
00180         String keyFilePath = CipherEngine.getPrivateKeyDirectory() 
00181                            + AsymmetricCipher.privateKeyFile;
00182 
00183         Object oPair = loadObject( keyFilePath );
00184         
00185         if ( oPair != null && ( oPair instanceof NamedKeyPair ) ) 
00186         {
00187             NamedKeyPair keyPair = (NamedKeyPair) oPair;
00188             this.privateKey = keyPair.privateKey;
00189             this.publicKey = keyPair.publicKey;
00190             this.keyPairComment = keyPair.comment;
00191             
00192             Log.attn( "Loaded private key '" + this.keyPairComment 
00193                     + "' from file '" + AsymmetricCipher.privateKeyFile + "'" );
00194         }
00195 
00196         return ( this.privateKey != null && this.publicKey != null ); 
00197     }
00198 
00199 
00200 
00201 
00202     private void saveKeyPair ()
00203     {
00204         if ( ! isActive () ) {
00205             return;
00206         }
00207 
00208         String keyFilePath = CipherEngine.getPrivateKeyDirectory() 
00209                            + AsymmetricCipher.privateKeyFile;
00210         
00211         if ( saveObject( 
00212                 new NamedKeyPair( this.publicKey, this.privateKey, this.keyPairComment ),
00213                 keyFilePath, null ) )
00214         {
00215             Log.attn( "Private key saved as '" + keyFilePath + "'" );
00216             
00217             
00218 
00219 
00220             String osName = System.getProperty( "os.name" ).toLowerCase();
00221             if ( ! osName.matches( "^.*windows.*$" ) ) 
00222             {
00223                 try 
00224                 {
00225                     Runtime.getRuntime().exec( new String[] { "chmod", "400", keyFilePath } );
00226                 }
00227                 catch( IOException e ) 
00228                 {
00229                     Log.trace( "Failed to do chmod; OS = " + osName );
00230                     Log.exception( Log.TRACE, e );
00231                 }
00232             }
00233         }
00234     }
00235 
00236 
00237 
00238 
00239     private void generateKeyPair ()
00240     {
00241         this.privateKey = null;
00242         this.publicKey = null;
00243         this.keyPairComment = null;
00244         
00245         try
00246         {
00247             KeyPairGenerator keyGen = KeyPairGenerator.getInstance( algorithm );
00248             keyGen.initialize( keySize );
00249             
00250             KeyPair keyPair = keyGen.generateKeyPair ();
00251             this.privateKey = keyPair.getPrivate ();
00252             this.publicKey  = keyPair.getPublic ();
00253             
00254             
00255 
00256             Calendar cal = Calendar.getInstance ();
00257             SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd-HHmmssSSS" );
00258             this.keyPairComment = algorithm.toLowerCase () + "-key-" 
00259                                 + sdf.format( cal.getTime() ); 
00260 
00261             Log.attn( "Generated a new " + algorithm + "/" 
00262                     + keySize + " key pair: '" + this.keyPairComment + "'" );
00263         }
00264         catch( NoSuchAlgorithmException e )
00265         {
00266             StringBuffer algos = new StringBuffer( "Available algorithms:" );
00267             
00268             for( String s : Security.getAlgorithms( "Cipher" ) ) {
00269                 algos.append( " " ).append( s );
00270             }
00271             
00272             Log.exception( Log.ERROR, e );
00273             Log.warn( algos.toString () );
00274         }
00275     }
00276 
00277 
00278 
00279 
00280     private void instantiateCipher ()
00281     {
00282         this.cipher = null;
00283 
00284         if ( this.privateKey == null || this.publicKey == null ) {
00285             return; 
00286         }
00287 
00288         try
00289         {
00290             this.cipher = Cipher.getInstance( algorithm + padding );
00291 
00292             Log.trace( "Instantiated asymmetric cipher: " + this.cipher.getAlgorithm () );
00293         }
00294         catch( NoSuchAlgorithmException e )
00295         {
00296             StringBuffer algos = new StringBuffer( "Available algorithms:" );
00297             
00298             for( String s : Security.getAlgorithms( "Cipher" ) ) {
00299                 algos.append( " " ).append( s );
00300             }
00301             
00302             Log.exception( Log.ERROR, e );
00303             Log.warn( algos.toString () );
00304         }
00305         catch( NoSuchPaddingException e )
00306         {
00307             Log.exception( Log.ERROR, e );
00308         }
00309     }
00310 
00311 
00312 
00313 
00314     private void serializePublicKey ()
00315     {
00316         if ( this.privateKey == null || this.publicKey == null || this.cipher == null ) {
00317             return; 
00318         }
00319 
00320         try
00321         {
00322             SignedObject signedPublicKey = this.signObject( this.publicKey );
00323   
00324             this.serializedPublicKey = Base64.encodeObject( signedPublicKey, Base64.GZIP );
00325 
00326             Log.trace( "Serialized Public Key in Base64; length = " 
00327                     + this.serializedPublicKey.length () );
00328         }
00329         catch( IOException e )
00330         {
00331             Log.exception( Log.ERROR, e );
00332         }
00333     }
00334 
00335 
00336 
00337 
00338     public boolean isActive ()
00339     {
00340         return this.cipher != null && this.serializedPublicKey != null;
00341     }
00342     
00343 
00344 
00345 
00346 
00347     public String getSerializedAndSignedPublicKey ()
00348     {
00349         return this.serializedPublicKey;
00350     }
00351 
00352 
00353 
00354 
00355 
00356 
00357 
00358     public void exportPublicKey( String fileName )
00359     {
00360         if ( ! isActive () ) {
00361             return;
00362         }
00363 
00364         if ( fileName == null ) {
00365             fileName = CipherEngine.getPrivateKeyDirectory()
00366                      + AsymmetricCipher.publicKeyFile;
00367         }
00368 
00369         if ( saveObject( 
00370                 new NamedPublicKey( this.publicKey, this.keyPairComment ), 
00371                 fileName,
00372                 "  " + this.keyPairComment + "\n" ) )
00373         {
00374             Log.attn( "Public key exported to '" + fileName + "'" );
00375         }
00376     }
00377 
00378 
00379 
00380 
00381     public String getNamedPublicKey ()
00382     {
00383         StringBuffer sb = new StringBuffer ();
00384 
00385         try {
00386             String encodedKey = Base64.encodeObject( new NamedPublicKey( 
00387                     this.publicKey, this.keyPairComment ), Base64.GZIP );
00388             
00389             sb.append( encodedKey );
00390             sb.append( " " );
00391             sb.append( this.keyPairComment );
00392         }
00393         catch( IOException e )
00394         {
00395             Log.exception( Log.ERROR, e );
00396         }
00397 
00398         return sb.toString ();
00399     }
00400     
00401 
00402 
00403 
00404     public static boolean saveObject( Serializable object, String fileName, String comment )
00405     {
00406         boolean result = false;
00407 
00408         try {
00409             String text = Base64.encodeObject( object, Base64.GZIP );
00410             
00411             BufferedWriter out = new BufferedWriter( new FileWriter( fileName ) );
00412             
00413             out.write( text );
00414             
00415             if ( comment != null ) {
00416                 out.write( comment );
00417             }
00418 
00419             out.flush ();
00420             out.close ();
00421             
00422             Log.trace( "Saved " + object.getClass().toString () + " into " + fileName );
00423             
00424             result = true;
00425         }
00426         catch( IOException e )
00427         {
00428             Log.exception( Log.ERROR, e );
00429         }
00430 
00431         return result;
00432     }
00433     
00434 
00435 
00436 
00437     public static Object loadObject( String fileName )
00438     {
00439         Object object = null;
00440 
00441         try {
00442             StringBuffer sb = new StringBuffer ();
00443             
00444             BufferedReader in = new BufferedReader( new FileReader( fileName ) );
00445             String line;
00446             while( ( line = in.readLine () ) != null ) {
00447                 sb.append( line );
00448             }
00449             in.close ();
00450 
00451             object = Base64.decodeToObject( sb.toString () );
00452         }
00453         catch( FileNotFoundException e )
00454         {
00455             Log.exception( Log.TRACE, e );
00456         }
00457         catch( IOException e )
00458         {
00459             Log.exception( Log.WARN, e );
00460         }
00461         catch( ClassNotFoundException e )
00462         {
00463             Log.exception( Log.ERROR, e );
00464         }
00465 
00466         return object;
00467     }
00468 
00469 
00470 
00471 
00472     private boolean sanityCheck ()
00473     {
00474         
00475 
00476         byte[] plainText = new byte[ 2048 ];
00477         for ( int i = 0; i < plainText.length; ++i ) {
00478             plainText[i] = (byte)( Math.random() * 256 );
00479         }
00480         
00481         
00482 
00483         PublicEncryptor testPublic = new PublicEncryptor( this.serializedPublicKey, null );
00484         byte[] cipherText = testPublic.encrypt( plainText );
00485         byte[] output =  this.decrypt( cipherText );
00486         
00487         if ( ! java.util.Arrays.equals( plainText, output ) )
00488         {
00489             Log.error( "Public encryption / private decryption sanity check failed." );
00490 
00491             Log.trace( Log.toHex( plainText ) );
00492             Log.trace( Log.toHex( cipherText ) );
00493             Log.trace( Log.toHex( output ) );
00494             
00495             return false;
00496         }
00497         
00498         return true;
00499     }
00500     
00501 
00502 
00503 
00504 
00505 
00506 
00507 
00508 
00509 
00510 
00511 
00512     public byte[] decrypt( byte[] cipherText )
00513     {
00514         if ( this.cipher == null )  {
00515             return null;
00516         }
00517         
00518         byte[] output = null;
00519         
00520         synchronized( this.cipher )
00521         {
00522             try 
00523             {
00524                 this.cipher.init( Cipher.DECRYPT_MODE, privateKey );
00525 
00526                 int blockSize = cipher.getOutputSize( 1 );
00527                 
00528                 byte[] xorBlock = new byte[ blockSize ];
00529                 
00530                 ByteArrayOutputStream bOut = new ByteArrayOutputStream ();
00531 
00532                 for ( int pos = 0; pos < cipherText.length; pos += blockSize ) 
00533                 {
00534                     int len = Math.min( cipherText.length - pos, blockSize );
00535                     
00536                     byte[] plainBlock = this.cipher.doFinal( cipherText, pos, len );
00537                     
00538                     for ( int i = 0; i < plainBlock.length; ++i ) {
00539                         plainBlock[i] = (byte)( plainBlock[i] ^ xorBlock[i] );
00540                     }
00541                     
00542                     bOut.write( plainBlock );
00543 
00544                     System.arraycopy( cipherText, pos, xorBlock, 0, blockSize );
00545                 }
00546                 
00547                 output = bOut.toByteArray ();
00548             }
00549             catch( Exception e )
00550             {
00551                 Log.exception( Log.ERROR, e );
00552             }
00553         }
00554 
00555         return output;
00556     }
00557     
00558 
00559 
00560 
00561     public SignedObject signObject( Serializable object )
00562     {
00563         String signatureAlgorithm = digest + "with" + this.privateKey.getAlgorithm ();
00564         
00565         Signature signature = null;
00566         SignedObject result = null;
00567 
00568         try 
00569         {
00570             signature = Signature.getInstance( signatureAlgorithm );
00571             signature.initSign( this.privateKey );
00572             result = new SignedObject( object, this.privateKey, signature ); 
00573         }
00574         catch( NoSuchAlgorithmException e )
00575         {
00576             Log.exception( Log.ERROR, e );
00577         }
00578         catch( InvalidKeyException e )
00579         {
00580             Log.exception( Log.ERROR, e );
00581         }
00582         catch( SignatureException e )
00583         {
00584             Log.exception( Log.ERROR, e );
00585         }
00586         catch( IOException e )
00587         {
00588             Log.exception( Log.ERROR, e );
00589         }
00590         
00591         return result;
00592     }
00593     
00594 
00595 
00596 
00597 
00598     public SymmetricCipher deserializeEncryptedSecretKey( String serializedSecretKey )
00599     {
00600         SymmetricCipher result = null;
00601         
00602         ByteArrayInputStream bIn = null; 
00603         ObjectInputStream oIn = null;
00604         
00605         try
00606         {
00607             byte[] cipherText = Base64.decode( serializedSecretKey );
00608             byte[] plainText = decrypt( cipherText );
00609             
00610             bIn = new ByteArrayInputStream( plainText );
00611             
00612             oIn = new ObjectInputStream( bIn );
00613             
00614             Object object = oIn.readObject ();
00615             
00616             oIn.close ();
00617             bIn.close ();
00618 
00619             String verificator = null;
00620             SignedObject signedObject = null;
00621 
00622             if ( object instanceof SignedObject ) 
00623             {
00624                 signedObject = (SignedObject) object;
00625                 verificator = PublicEncryptor.verifyObject( signedObject );
00626                 object = signedObject.getObject ();
00627             }
00628             
00629             if ( object instanceof SecretKey ) 
00630             {
00631                 result = new SymmetricCipher( (SecretKey)object, verificator );
00632             }
00633             else 
00634             {
00635                 Log.error( "Invalid object when trying to deserialize encrypted secret key" );
00636             }
00637         }
00638         catch( Exception e ) 
00639         {
00640             Log.exception( Log.ERROR, e );
00641         }
00642         
00643         return result;
00644     }
00645 }