Handling crypto exceptions

2019-01-07 22:26发布

问题:

This, pretty basic, piece of code is quite common when handling encryption\decryption in Java.

final Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
cipher.doFinal(*something*);

These three lines alone, potentially throw six exceptions and I'm not sure what's the cleanest (in terms of code readability) way to handle them. A try with six catch clauses really looks like a smell to me.

Are there micropatterns or best practices, I am obviously missing, when working with such objects?

EDIT

Sorry, I think I didn't explain myself very well. My question is not really about avoiding a try\catch clause, but if there is a common way to handle similar situations.

The exceptions are

NoSuchPaddingException, NoSuchAlgorithmException
InvalidAlgorithmParameterException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException

回答1:

You indicated the following exceptions:

NoSuchPaddingException, NoSuchAlgorithmException
InvalidAlgorithmParameterException, InvalidKeyException,
BadPaddingException, IllegalBlockSizeException

Now all of these are GeneralSecurityException's, so it would be easy to catch them all. But looking at the use case, you probably don't want to do that.

If you look at the cause of the exceptions then you will find that any of these exceptions - except for the last two - are only thrown when generating an implementation of an algorithm or a key. I think it is reasonable that once you have tested your application that these values remain more or less static. Hence it would be logical to throw - for instance - an IllegalStateException. IllegalStateException is a runtime exception which you are not required to throw or catch. Of course, you should indicate the security exception as being the cause of the exception.

Now the last two exceptions, BadPaddingException and IllegalBlockSizeException are different. They depend on the actual ciphertext, so they are dependent on the input of the algorithm. Now normally you should always verify the integrity of the input before you feed it into your Cipher instance, initiated for decryption, for instance by first validating a HMAC checksum). So in that sense you could still get away with a runtime exception.

If you don't check for integrity you should do something different with the exception, such as re-throwing it as a (different?) checked exception. If you take that route you should understand about padding oracle attacks; if an adversary can try and decrypt ciphertext multiple times and can find out if the padding is correct or not then the confidentiality of the message is lost.

It is probably best to use separate try/catch blocks for the construction and initialization of the Cipher and the decryption itself. You could also catch the exceptions BadPaddingException and IllegalBlockSizeException before handling the GeneralSecurityException. Starting with Java 7 you may use multi-catch statements as well (e.g. catch(final BadPaddingException | IllegalBlockSizeException e)).


Finally some notes:

  • Beware that an exception may be thrown for AES key sizes 192 bit and 256 bit if the unlimited crypto files are not being installed (check the Oracle JavaSE site for more info); you should check if the key size is permitted when the application is started;
  • Both BadPaddingException and IllegalBlockSizeException may be created because of attacks or because the data was not completely present;
  • BadPaddingException may also be thrown if the key is incorrect.


回答2:

If you are willing to lose some specificty, all of the Crypto exceptions extend GeneralSecurityException, you can just catch that instead.



回答3:

The best way to handle that is to create a bussines exception (MyModuleException or something) and then rethrow that exception adding Crypto exceptions to cause part. In that way your method would throw only one exception, not six, what would be much easier to manage in other layers of your application.

public void myMethod(...) throws MyModuleException {
  try {
    final Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
    cipher.init(Cipher.ENCRYPT_MODE, key, iv);
    cipher.doFinal(*something*);
  } catch(Crypto1Ex ex){
    throw new MyModuleException("something is wrong", ex); //ex added, so it is not lost and visible in stacktraceses
  } catch(Crypto1Ex ex){
    throw new MyModuleException("something is wrong", ex);
  } //etc.
}

In Java 7 you might handle it even easier (see: http://docs.oracle.com/javase/7/docs/technotes/guides/language/catch-multiple.html)