2011-08-25 102 views
6

以下java代码是否足以清除内存中的密钥(将其所有字节值设置为0)?如何在java中清零密钥?

zerorize(SecretKey key) 
{ 
    byte[] rawKey = key.getEncoded(); 
    Arrays.fill(rawKey, (byte) 0); 
} 

换句话说,它的getEncoded方法返回一个副本或参考实际的钥匙?如果返回了副本,那么我怎样才能清除密钥作为安全措施?

回答

-1

换句话说,getEncoded方法是否返回实际密钥的副本或引用?

key.getEncoded()将返回参考到阵列。

如果当你做Array.fill取决于密钥是否是由返回数组支持密钥的内容被丢弃。鉴于文档,在我看来,假设关键的编码是关键,即的另一种表示,这关键是用返回数组支持。

虽然很容易找到。请尝试以下操作:

byte[] rawKey = key.getEncoded(); 
Arrays.fill(rawKey, (byte) 0); 

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

如果输出为false,你知道,关键仍存储在SecretKey

-1

除了原始值,一切都在其他的Java总是通过引用传递,包括数组,所以是的,你是正确清除字节数组。

然而,SecretKey的类可能仍然持有生成的字节数组,还有包括给定的字节数组的最终另一个副本所需的数据,所以你应该研究如何清除这些数据也是如此。

+4

-1:* Java中的所有其他东西总是通过引用传递* - Nooo,Java总是*通过值传递!你无法按值传递*对象*的原因是因为没有变量可以首先包含对象! – aioobe

+0

@aioobe ..你确定我们正在谈论相同的Java? int通过值传递,boolean通过值传递,Integer是一个引用,以及任何对象,数组等等... Java传递一个“值”,它实际上是一个对象的“引用”,所以它通过参考。 –

+0

@Joachim ..我指的是“给定的字节数组”,但是进一步澄清说可能有另一个副本。 –

0

我敢肯定,清理rawKey不会影响key数据。

我不认为有一种方法可以清除SecretKey中的数据。具体的实施类可能规定,但我不知道有任何这样做。在Android中,数据丢失的风险非常低。每个应用程序都在其自己的进程中运行,并且其内存从外部不可见。

我想有一个攻击场景,其中一个根priviledged过程可能需要的内存快照,并送他们过一些超级计算机的地方进行分析,希望能发现别人的秘密密钥。但我从来没有听说过这样的攻击,它让我觉得没有其他方式获得系统的权利。您是否有理由担心这种特定的假设漏洞?

6

在尝试清除密钥之前,应首先检查接口SecretKey的实现是否也实现javax.security.auth.Destroyable接口。如果是这样,那当然更喜欢。

+0

只能工作在1.8+以上,通常只会抛出DestroyFailedException –

-2

以一个略有不同的策略,一旦你已经确定的内存覆盖正确的区域,你可能想要做一次以上:

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

取决于技术的垃圾收集器供电,任何单一对象可以随时移动(即复制)在物理内存中,所以你不能确定你真的会通过置零数组来销毁该键 - 假设你可以访问持有该键的“数组”,而不是其副本。简而言之:如果您的安全模型和上下文要求调零键,那么您根本不应该使用Java(或者几乎不使用Java,除C和程序集之外的任何其他语言)。

+0

但是,如果您必须使用Java,请快速将其清零,然后GC可能会重新打包数据或操作系统将其移至交换位置等。 –

3

getEncoded()似乎大多返回键的克隆(从例如javax.security.auth.kerberos的甲骨文1.6源):

public final byte[] getEncoded() { 
    if (destroyed) 
    throw new IllegalStateException("This key is no longer valid"); 
    return (byte[])keyBytes.clone(); 
} 

因此擦拭返回数据不从内存中删除密钥的所有副本。

擦拭从SecretKey密钥的唯一方法是,如果它实现了接口,将它转换为javax.security.auth.Destroyable并调用destroy()方法:

public void destroy() throws DestroyFailedException { 
    if (!destroyed) { 
    destroyed = true; 
    Arrays.fill(keyBytes, (byte) 0); 
    } 
} 

奇怪的是,这一切似乎主要执行不执行javax.security.auth.Destroyablecom.sun.crypto.provider.DESedeKey没有也没有javax.crypto.spec.SecretKeySpec用于AES。这两个关键实现也都克隆了getEncoded方法中的密钥。因此,对于这些非常常见的算法3DES和AES,我们似乎没有办法擦除秘密密钥的内存?

1

GetEncoded返回密钥的副本(因此清除对秘密密钥数据没有影响),并且默认情况下销毁抛出DestroyFailedException,这比无用的更糟糕。它也只在1.8+以上,所以Android运气不好。这里有一个使用自省的黑客(1)调用destroy(如果可用)并且不抛出异常,否则(2)清零关键数据并将引用设置为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); 
    } 
} 

使用的方法就是像

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

我用Closeable接口,因为销毁的只是一个1.8+功能。这个版本工作在1.7+以上,效率很高(它试图破坏一个密钥以决定再次使用它)。

+0

这是一个破解,并且GC可以重新打包内存或操作系统将数据移动到交换位置,这可能会泄漏关键数据。尽快关闭钥匙,以尽量减少由于GC或OS副作用而泄漏的可能性。 –