How do I zero-ise a secret key in java?

后端 未结 8 2323
南方客
南方客 2020-12-16 05:22

Is the following java code sufficient for clearing the secret key in memory (setting all of its byte value to 0)?

zerorize(SecretKey key)
{
    byte[] rawKey         


        
相关标签:
8条回答
  • 2020-12-16 05:33

    In other words, does the getEncoded method return a copy or reference to the actual key?

    key.getEncoded() will return a reference to an array.

    If the content of key is discarded when you do the Array.fill depends on whether or not the key is backed by the returned array. Given the documentation, it seems to me as if the encoding of the key is another representation of the key, i.e., that the key is not backed by the returned array.

    It's easy to find out though. Try the following:

    byte[] rawKey = key.getEncoded();
    Arrays.fill(rawKey, (byte) 0);
    
    byte[] again = key.getEncoded();
    Log.d(Arrays.equals(rawKey, again));
    

    If the output is false, you know that the key is still stored in SecretKey.

    0 讨论(0)
  • 2020-12-16 05:36

    Before trying to clear the key, you should check first if the implementation of the SecretKey interface also implements the javax.security.auth.Destroyable interface. If so, prefer that of course.

    0 讨论(0)
  • 2020-12-16 05:37

    Except primitive values, everything else in Java is always passed by reference, including arrays, so yes, you are clearing the given byte array correctly.

    However, SecretKey class probably still holds data needed to generate that byte array, there including eventually another copy of the given byte array, so you should investigate how to clear that data as well.

    0 讨论(0)
  • 2020-12-16 05:44

    Taking a slightly different tack, once you have identified the correct area of memory to overwrite, you might want to do it more than once:

    zerorize(SecretKey key)
    {
        byte[] rawKey = key.getEncoded();
        Arrays.fill(rawKey, (byte) 0xFF);
        Arrays.fill(rawKey, (byte) 0xAA);
        Arrays.fill(rawKey, (byte) 0x55);
        Arrays.fill(rawKey, (byte) 0x00);
    }
    
    0 讨论(0)
  • 2020-12-16 05:45

    I'm pretty sure that clearing rawKey will not affect the data in key.

    I don't think there's a way in general to clear the data in a SecretKey. Specific implementation classes may provide for that, but I'm not aware of any that do. In Android, the risk of leaving the data uncleared is very low. Each app runs in its own process and its memory is not visible from outside.

    I suppose there's an attack scenario where a root-priviledged process can take snapshots of memory and send them off to some supercomputer somewhere for analysis, hoping to discover someone's secret keys. But I've never heard of such an attack, and it strikes me as not competitive with other ways to gain access to a system. Is there a reason you are worried about this particular hypothetical vulnerability?

    0 讨论(0)
  • 2020-12-16 05:45

    GetEncoded returns a copy of the secret key (so clearing that has no effect on the secret key data), and destroy by default throws DestroyFailedException which is worse than useless. It is also only available in 1.8+ so Android is out of luck. Here's a hack that uses introspection to (1) invoke destroy if available and does not throw an exception, otherwise (2) zero the key data and set the reference to null.

    package kiss.cipher;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.Arrays;
    
    import javax.crypto.spec.SecretKeySpec;
    
    /**
     * Created by wmacevoy on 10/12/16.
     */
    public class CloseableKey implements AutoCloseable {
    
        // forward portable to JDK 1.8 to destroy keys
        // but usable in older JDK's
        static final Method DESTROY;
        static final Field KEY;
    
        static {
            Method _destroy = null;
    
            Field _key = null;
            try {
                Method destroy = SecretKeySpec.class.getMethod("destroy");
                SecretKeySpec key = new SecretKeySpec(new byte[16], "AES");
                destroy.invoke(key);
                _destroy = destroy;
            } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
            }
    
            try {
                _key = SecretKeySpec.class.getDeclaredField("key");
                _key.setAccessible(true);
            } catch (NoSuchFieldException | SecurityException ex) {
            }
    
            DESTROY = _destroy;
            KEY = _key;
        }
    
        static void close(SecretKeySpec secretKeySpec) {
            if (secretKeySpec != null) {
                if (DESTROY != null) {
                    try {
                        DESTROY.invoke(secretKeySpec);
                    } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
                        throw new IllegalStateException("inconceivable: " + ex);
                    }
                } else if (KEY != null) {
                    try {
                        byte[] key = (byte[]) KEY.get(secretKeySpec);
                        Arrays.fill(key, (byte) 0);
                        KEY.set(secretKeySpec, null);
                    } catch (IllegalAccessException | IllegalArgumentException ex) {
                        throw new IllegalStateException("inconceivable: " + ex);
                    }
                }
            }
        }
    
        public final SecretKeySpec secretKeySpec;
    
        CloseableKey(SecretKeySpec _secretKeySpec) {
    
            secretKeySpec = _secretKeySpec;
        }
    
        @Override
        public void close() {
            close(secretKeySpec);
        }
    }
    

    The way to use this is like

    try (CloseableKey key = 
           new CloseableKey(new SecretKeySpec(data, 0, 16, "AES"))) {
      aesecb.init(Cipher.ENCRYPT_MODE, key.secretKeySpec);
    }
    

    I use the Closeable interface because Destroyable is a 1.8+ feature only. This version works on 1.7+ and is pretty efficient (it does a trial destroy on one key to decide to ever use it again).

    0 讨论(0)
提交回复
热议问题