Pages

Thursday, November 6, 2008

Securing Keystores I

The application I am working on is a secure document management system. Internal users and external users can share documents instead of passing them around by email. Security requirements have made encryption of these documents a certainty. Documents are uploaded via https so there is not problem in transport, but they have to reside encrypted on the disk as well. The IT Security group was looking for the following features.
  1. Key rotation - allowing a security manager to generate a new keystore and rekey all documents as needed.
  2. Keystore password stored on remotely.
  3. Keystore password > 25 chars.
Knowing that PBE of the keystore is potentially breakable I decided to wrap the keys. My goal is that an attacker could have both the keystore and the encrypted documents and still have significant difficulty in reading the documents. My basis for this technique is in rfc3394. It discusses using AES 256bit to wrap. I will be wrapping the keys with 2048 bit RSA.

If an attacker was to mount a brute force attack then they would have to first break the PBE on the keystore, then they would have to break the 2048 bit RSA wrapping the keys.

The keystore generation program will generate a keystore with a specified number of AES 256 bit keys. This number of keys will need to be substantially large to accommodate a unique key for each document. We use will use 50000. This should provide a set of keys randomly distributed within the keyspace. 
Given that there is a possibility of an attack against two or more encrypted documents we will ensure that no two documents are encrypted with the same key.

Each document will be encrypted with a randomly selected key from this pool. Allowing that the password based encryption of the keystore is breakable, I decided to use the keywrap classes to encrypt each secret key.


If you make the assumption that a keystore's PBE can be broken, then it would be relatively trivial to write a program to attempt to decrypt the files with the secret keys. We need a way to lock this keystore specifically to our code. To do this we will end up storing the RSA private key used to wrap the AES keys in our program as a byte array. This will lock this keystore to the code used to decrypt the files. Thus the attacker will have to grab the classes as well as the encrypted files and the keystore.
Below are the relevant parts of our keystore generator.

...snip...
String keyStoreEntryAlias = null;
 
// Change the output file location to where ever you want to store the new KeyStore
File keyStoreOutputFile = new File(keyStoreName);

KeyStore keyStore = null;    
 
  try
  {
     // NOTE: This creates numKeys of random SecretKey. If you lose the keystore and you have used it
     //       to encrypt some file you are HOSED. DO NOT lose the KeyStore file that is
     //       generated from this program. Keep a second copy on a CD and put it in a safe.
     //
     // We are using the AES encryption algorithm to represent our SecretKey
     
     // Create an empty keyStore
     keyStore = KeyStore.getInstance("JCEKS");
     keyStore.load(null, password);
     
     //create RsaEncrypter with nulls to use the embedded keys.
RsaEncrypter rsaEncrypter = new RsaEncrypter(null, null);

PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(rsaEncrypter.getPrivateKey().getEncoded());
String privateKeyStr = Base64.encodeToString(privateKeySpec.getEncoded(), true); 
System.out.println( "PrivateKey: \n\r" +  privateKeyStr  );

// to output embedded private key with some massaging.  add quotes take off []
System.out.println( "Embedded PrivateKey: \n\r" +  Arrays.toString(privateKeyStr.toCharArray())) ;

X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(rsaEncrypter.getPublicKey().getEncoded());
String publicKeyStr = Base64.encodeToString(publicKeySpec.getEncoded(), true);
System.out.println( "PublicKey: \n\r" + publicKeyStr );

// to output embedded public key with some massaging.  add quotes take off []
System.out.println( "Embedded PublicKey: \n\r" + Arrays.toString(publicKeyStr.toCharArray()) );

     for (int i=0; i<>
     {                            
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(256);
        SecretKey key = keyGen.generateKey();
        byte[] wrappedKey = rsaEncrypter.wrapKey(key);
     
        //System.out.println( "NakedKey: " + key.getEncoded());
        //System.out.println("WrappedKey: " + wrappedKey.toString());
        SecretKeySpec skWrapSpec = new SecretKeySpec(wrappedKey, "AES");

        //create a alias for the key
        keyStoreEntryAlias = (String)RandomStringUtils.randomAlphanumeric(16);
    
        // test to see if key alias already exists           
        if (! keyStore.containsAlias(keyStoreEntryAlias) )           
        {                  
          
           // Add our secret key as an entry in the empty keyStore
           KeyStore.SecretKeyEntry skEntry = new KeyStore.SecretKeyEntry(skWrapSpec);
           keyStore.setEntry(keyStoreEntryAlias, skEntry, new KeyStore.PasswordProtection(password));
        }
     }

     // Write the keyStore to disk.      
     FileOutputStream fos = new FileOutputStream(keyStoreOutputFile);
     keyStore.store(fos, password);          
     System.out.println("AES 256 Key store created with " + keyStore.size() + " random RSA wrapped SecretKeys. \r\n");
     System.out.println("Alg    --- Aliases --- \r\n");
    
     Enumeration aliases = keyStore.aliases();
     StringBuffer buf = new StringBuffer("");
     while (aliases.hasMoreElements())                
     {         
        String keyName = (String)aliases.nextElement();
        SecretKey theKey = (SecretKey)keyStore.getKey(keyName,password);
        String keyStr = "";
        String algorithm = theKey.getAlgorithm();
        if (verbose)
        {            
           keyStr = Base64.encodeToString(theKey.getEncoded());
        }         
        buf.append( algorithm ).append("   ").append(keyName).append("|     ")
buf.append( keyStr ).append("\r\n-----------------------\r\n");
     }
    
     System.out.println( buf.toString() );
     System.out.println("File is: \"" + keyStoreOutputFile.getCanonicalPath() + "\"");
  }   
  catch (Exception e)   
  {                
     System.out.println("Error: " + e);
     e.printStackTrace();         
  }  
...snip...

Stay tuned ... I will reveal the RSAEncrypter and AESEcrypter source to show you how the wrapping and unwrapping occurs.


If you are looking for more information on the key wrapping technique these references may help.

http://www.faqs.org/rfcs/rfc3394.html

http://www.java2s.com/Code/Java/Security/WrapAndUnwrapKey.htm

http://books.google.com/books?id=WLLAD2FKH3IC&pg=PA50&lpg=PA50&dq=key+Wrapping+java&source=web&ots=O9EnrPo8xB&sig=8UuFnc04iXEhBqeduJLKZU46MFk&hl=en&sa=X&oi=book_result&resnum=3&ct=result

These two don't use the KeyWrap technique but they are similar.

http://www-users.york.ac.uk/~mal503/lore/pkencryption.htm

http://www.junkheap.net/content/public_key_encryption_java

No comments:

Post a Comment