我如何归零java中的密钥?

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

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

换句话说, getEncoded方法是否返回实际键的副本或引用? 如果返回副本,那么如何清除密钥作为安全措施?

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

getEncoded()似乎主要返回密钥的克隆(来自例如javax.security.auth.kerberos的Oracle 1.6源代码):

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

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

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

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

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

GetEncoded返回密钥的副本(因此清除对密钥数据没有影响),默认情况下destroy会抛出DestroyFailedException,这比无用更糟糕。 它也仅适用于1.8+,因此Android运气不佳。 这是一个使用内省的hack(1)如果可用则调用destroy并且不抛出exception,否则(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接口,因为Destroyable只是一个1.8+function。 这个版本适用于1.7+并且非常有效(它在一个键上进行试验销毁以决定再次使用它)。

我很确定清除rawKey不会影响key的数据。

我不认为有一种方法可以清除SecretKey中的数据。 具体的实现类可以提供,但我不知道这样做。 在Android中,保持数据不被清除的风险非常低。 每个应用程序都在自己的进程中运行,其内存在外部不可见。

我想有一个攻击场景,一个root权限的进程可以获取内存快照并将它们发送到某个地方的某个超级计算机进行分析,希望能够发现某人的密钥。 但是我从来没有听说过这样的攻击,并且它让我觉得与其他获取系统访问权限无法竞争。 您是否有理由担心这个特殊的假设漏洞?

根据为垃圾收集器提供动力的技术,任何单个对象都可以随时在物理内存中移动(即复制),因此您无法确定是否真的会通过将数组清零来破坏密钥 – 假设您可以访问“ “持有密钥的数组,而不是其副本。

简而言之:如果您的安全模型和上下文调用了归零键,那么您根本不应该使用Java(或者除了C和汇编之外的任何东西)。

除了原始值之外,Java中的其他所有内容总是通过引用传递,包括数组,所以是的,您正在正确地清除给定的字节数组。

但是,SecretKey类可能仍然保存生成该字节数组所需的数据,最终包括给定字节数组的另一个副本,因此您应该研究如何清除该数据。

换句话说,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

稍微改变一下,一旦确定要覆盖的正确内存区域,您可能需要多次执行此操作:

 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); }