以下java代码是否足以清除内存中的密钥(将其所有字节值设置为0)?
zerorize(SecretKey key)
{
byte[] rawKey = key.getEncoded();
Arrays.fill(rawKey, (byte) 0);
}
换句话说,getEncoded
方法是否返回实际密钥的副本或引用?如果返回副本,那么如何清除密钥作为安全措施?
答案 0 :(得分:6)
在尝试清除密钥之前,应首先检查SecretKey
接口的实现是否也实现了javax.security.auth.Destroyable
接口。如果是这样,当然更喜欢。
答案 1 :(得分:3)
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
擦除密钥的唯一方法是,如果它实现了接口并调用javax.security.auth.Destroyable
方法,则将其强制转换为destroy()
:
public void destroy() throws DestroyFailedException {
if (!destroyed) {
destroyed = true;
Arrays.fill(keyBytes, (byte) 0);
}
}
奇怪的是,似乎所有Key实现都没有实现javax.security.auth.Destroyable
。 com.sun.crypto.provider.DESedeKey
没有javax.crypto.spec.SecretKeySpec
用于AES。这两个关键实现都克隆了getEncoded
方法中的密钥。因此,对于这些非常常见的算法3DES和AES,我们似乎没有办法擦除密钥的内存?
答案 2 :(得分:1)
GetEncoded返回密钥的副本(因此清除对密钥数据没有影响),默认情况下,destroy会抛出DestroyFailedException,这比无用的更糟糕。它也仅适用于1.8+,因此Android运气不佳。这是一个使用内省的hack(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接口,因为Destroyable只是一个1.8+功能。这个版本适用于1.7+并且非常有效(它会在一个键上进行试验销毁以决定再次使用它。)
答案 3 :(得分:0)
我很确定清除rawKey
不会影响key
中的数据。
我认为通常没有办法清除SecretKey中的数据。特定的实现类可能为此提供,但我不知道这样做。在Android中,保持数据不被清除的风险非常低。每个应用程序都在自己的进程中运行,其内存在外部不可见。
我想有一个攻击场景,一个root权限的进程可以获取内存快照并将它们发送到某个地方的超级计算机进行分析,希望能够发现某人的密钥。但是我从来没有听说过这样的攻击,而且我觉得它与其他获取系统访问权限的方式没有竞争力。您是否有理由担心这个特殊的假设漏洞?
答案 4 :(得分:0)
根据为垃圾收集器供电的技术,任何单个对象都可以随时在物理内存中移动(即复制),因此您无法确定是否真的会通过将数组清零来破坏密钥 - 假设您可以访问持有密钥的“数组”,而不是其副本。
简而言之:如果您的安全模型和上下文调用了归零键,那么您根本不应该使用Java(或者除了C和汇编之外的任何东西)。
答案 5 :(得分:-1)
换句话说,getEncoded方法是否返回实际密钥的副本或引用?
key.getEncoded()
会将引用返回给数组。
如果在执行Array.fill时丢弃了键的内容,则取决于返回的数组是否支持键。鉴于文档,在我看来,密钥的编码是密钥的另一种表示形式,即密钥不由返回的数组支持。< / p>
虽然很容易找到。请尝试以下方法:
byte[] rawKey = key.getEncoded();
Arrays.fill(rawKey, (byte) 0);
byte[] again = key.getEncoded();
Log.d(Arrays.equals(rawKey, again));
如果输出为false
,则表示该密钥仍存储在SecretKey
中。
答案 6 :(得分:-1)
除了原始值之外,Java中的其他所有内容总是通过引用传递,包括数组,所以是的,您正在正确地清除给定的字节数组。
但是,SecretKey类可能仍然保存生成该字节数组所需的数据,最终包括给定字节数组的另一个副本,因此您应该研究如何清除该数据。
答案 7 :(得分:-3)
稍微改变一下,一旦确定要覆盖的正确内存区域,您可能想要多次执行此操作:
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);
}