以下java代码是否足以清除内存中的密钥(将其所有字节值设置为0)?如何在java中清零密钥?
zerorize(SecretKey key)
{
byte[] rawKey = key.getEncoded();
Arrays.fill(rawKey, (byte) 0);
}
换句话说,它的getEncoded
方法返回一个副本或参考实际的钥匙?如果返回了副本,那么我怎样才能清除密钥作为安全措施?
以下java代码是否足以清除内存中的密钥(将其所有字节值设置为0)?如何在java中清零密钥?
zerorize(SecretKey key)
{
byte[] rawKey = key.getEncoded();
Arrays.fill(rawKey, (byte) 0);
}
换句话说,它的getEncoded
方法返回一个副本或参考实际的钥匙?如果返回了副本,那么我怎样才能清除密钥作为安全措施?
换句话说,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
。
除了原始值,一切都在其他的Java总是通过引用传递,包括数组,所以是的,你是正确清除字节数组。
然而,SecretKey的类可能仍然持有生成的字节数组,还有包括给定的字节数组的最终另一个副本所需的数据,所以你应该研究如何清除这些数据也是如此。
我敢肯定,清理rawKey
不会影响key
数据。
我不认为有一种方法可以清除SecretKey中的数据。具体的实施类可能规定,但我不知道有任何这样做。在Android中,数据丢失的风险非常低。每个应用程序都在其自己的进程中运行,并且其内存从外部不可见。
我想有一个攻击场景,其中一个根priviledged过程可能需要的内存快照,并送他们过一些超级计算机的地方进行分析,希望能发现别人的秘密密钥。但我从来没有听说过这样的攻击,它让我觉得没有其他方式获得系统的权利。您是否有理由担心这种特定的假设漏洞?
在尝试清除密钥之前,应首先检查接口SecretKey
的实现是否也实现javax.security.auth.Destroyable
接口。如果是这样,那当然更喜欢。
只能工作在1.8+以上,通常只会抛出DestroyFailedException –
以一个略有不同的策略,一旦你已经确定的内存覆盖正确的区域,你可能想要做一次以上:
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);
}
取决于技术的垃圾收集器供电,任何单一对象可以随时移动(即复制)在物理内存中,所以你不能确定你真的会通过置零数组来销毁该键 - 假设你可以访问持有该键的“数组”,而不是其副本。简而言之:如果您的安全模型和上下文要求调零键,那么您根本不应该使用Java(或者几乎不使用Java,除C和程序集之外的任何其他语言)。
但是,如果您必须使用Java,请快速将其清零,然后GC可能会重新打包数据或操作系统将其移至交换位置等。 –
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.Destroyable
。 com.sun.crypto.provider.DESedeKey
没有也没有javax.crypto.spec.SecretKeySpec
用于AES。这两个关键实现也都克隆了getEncoded
方法中的密钥。因此,对于这些非常常见的算法3DES和AES,我们似乎没有办法擦除秘密密钥的内存?
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+以上,效率很高(它试图破坏一个密钥以决定再次使用它)。
这是一个破解,并且GC可以重新打包内存或操作系统将数据移动到交换位置,这可能会泄漏关键数据。尽快关闭钥匙,以尽量减少由于GC或OS副作用而泄漏的可能性。 –
-1:* Java中的所有其他东西总是通过引用传递* - Nooo,Java总是*通过值传递!你无法按值传递*对象*的原因是因为没有变量可以首先包含对象! – aioobe
@aioobe ..你确定我们正在谈论相同的Java? int通过值传递,boolean通过值传递,Integer是一个引用,以及任何对象,数组等等... Java传递一个“值”,它实际上是一个对象的“引用”,所以它通过参考。 –
@Joachim ..我指的是“给定的字节数组”,但是进一步澄清说可能有另一个副本。 –