Search code examples
javaencryptiondestroysecret-keyaes-gcm

How to destroy SecretKey in Java 14?


I am trying to clear my Secretkey after decrypting. From what I've read, SecretKeys can be destroyed via the destroy method since Java 8. I am using Java 14 so it should be possible.

However, whenever I use the destroy method on a key, a DestroyFailedException is thrown. I've also seen that people ignore that Exception in their code, however, if I were to do that, I am able to print the Key after calling the destroy method on it.

Here my Decryption method:

private byte[] decrypt(byte[] encryptedText, char[] password) throws InvalidKeyException,
        InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException,
        InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException, DestroyFailedException {

    ByteBuffer bb = ByteBuffer.wrap(encryptedText);

    byte[] iv = new byte[ivLengthByte];
    bb.get(iv);

    byte[] salt = new byte[saltLengthByte];
    bb.get(salt);

    byte[] cipherText = new byte[bb.remaining()];
    bb.get(cipherText);

    SecretKey key;
    key = crypto.getAESKeyFromPassword(password, salt);

    Cipher cipher;
    cipher = Cipher.getInstance(algorithm);

    cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(tagLengthBit, iv));

    byte[] plainText = cipher.doFinal(cipherText);

    Main.clearArray(password, null);
    Main.clearArray(null, iv);
    Main.clearArray(null, salt);
    Main.clearArray(null, cipherText);

    key.destroy();

    cipher = null;

    return plainText;

}

After calling the destroy method, I am, as said, (assuming I ignore the Exception) able to print the key via String encodedKey = Base64.getEncoder().encodeToString(key.getEncoded());

EDIT: After using my Clear method on the array, I can still print it:

byte[] temp = key.getEncoded();
        Main.clearArray(null, temp);

Clear Array:

protected static void clearArray(char[] chars, byte[] bytes) {
    if (chars != null) {
        for (int i = 0; i < chars.length; i++) {
            chars[i] = '\0';
        }

    }
    if (bytes != null) {
        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = 0;
        }

    }

}

getAESKey:

protected SecretKey getAESKeyFromPassword(char[] password, byte[] salt)
        throws NoSuchAlgorithmException, InvalidKeySpecException {

    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");

    KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
    SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");

    return secret;

}

Final Edit:

The best solution was to switch frim PBKDF2 to argon2. https://github.com/kosprov/jargon2-api Argon2 allows to use raw Hashes, then you may store that byte array in a SecureKeySpec as mentioned above, since it allows destroying of the Spec, and clear the raw Hash Array.


Solution

  • Actually, there is no easy solution to this. The problem is that the destroy method is an "optional" method. Not all implementations of SecretKey implement it. If you are using a SecretKey type that doesn't implement the method, you get this exception and there is no simple solution.

    Unfortunately, you can't just implement the method yourself because (typically) the class it belongs on is provided by the Java SE libraries.

    And even if you do figure out how to destroy the key, there is also the problem of the String containing the password1. (And this problem is more of a security risk, since trawling for a String containing a password is likely to be easier than trawling for an unknown byte sequence.)

    Options:

    1. Forget the problem. Don't destroy they key / password in memory. (See below for an explanation as to why this is not as bad as it sounds.)

    2. Look for alternative JSSE crypto libraries where the SecretKey implementation for AES secret keys does implement destroy. I am guessing that the Bouncy Castle libraries might. (And if they don't, you always have the option of downloading the source code and patching them.)

    3. Nasty reflection. You could figure out which actual class implements the secret key, and look at its code to work out how it represents the key internally. Then you could use reflection to break the abstraction and access its private state and ... write zeros over the key.


    Why is not destroying the key not a disaster?

    So some security experts may disagree with this, but I still think it is a valid viewpoint.

    When you zero a key or password in memory, you are (ostensibly) protecting against the following kinds of attack:

    • Attaching a Java debugger to the JVM process and using that to locate and read the key.
    • Reading the JVM processes memory.
    • Reading memory pages that have been written to disk.

    How easy are these attacks? Well the first two require that the hacker has already gotten into the host and escalated to (probably) root privilege. In the third case you could do it that way, but the hacker could also just steal the hard drive where the swap pages are written.

    In all cases, the hacker then has to find the secret key. Unlike (say) a C / C++ program, the key won't be stored at a fixed location. Instead the hacker has to find it by pattern matching, or by finding reference chains. (A Java debugger would make it easier, provided that the key object is still reachable.) And the flip-side is once the key has been garbage collected, the copy in memory will be gone, and the copy in swap will go the next time that the OS writes out the (now) dirty page where the key object once lived. After that ... it is "gone" for all practical purposes.

    So rewind a bit. I said that in order to carry off this kind of attack, the hacker already needs root access. (Or the hard drives, which most likely amounts to the same thing.) Now if they have that, there are other ways they can steal the secret key. For example:

    • Use the debugger to set a breakpoint on (say) the destroy method, and grab the key before it is destroyed.

    • Use the debugger to capture the password before you create the key.

    • Steal the private key for the server's SSL cert (or whatever) so that they can pick up the password from network traffic.

    • Install a software keystroke logger.

    • Replace your application code with a version that leaks the key or password via some side channel.

    And of course they can install backdoors, etc. In short, if the hacker has compromised the system to the degree needed to pull off a "steal stuff out of memory" attack against a JVM, that is probably the least of your worries.

    Now a security expert may say that it is "best practice" to have a layered defense against hackers. There is some truth to that. However, if security is that important to you, you should do a proper security analysis (not just a "tick the boxes" audit) and figure out what the real risks are. This will (probably2) tell you that it is better to focus on making the system secure than worrying if someone (with root privilege) can steal keys out of memory.


    1 - Though not in your case, because I see that you are using a char[] ... which can be cleared. Except that this still vulnerable to all of the other attacks that I talk about.
    2 - Or maybe it won't. But you need to do the analysis!