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 }