如何在java中设置密钥?

如何在java中设置密钥?,java,android,security,cryptography,secret-key,Java,Android,Security,Cryptography,Secret Key,以下java代码是否足以清除内存中的密钥(将其所有字节值设置为0) 换句话说,getEncoded方法是否返回实际密钥的副本或引用?如果返回了副本,那么如何清除密钥作为安全措施 换句话说,getEncoded方法是否返回对实际键的副本或引用? key.getEncoded()将返回对数组的引用 如果在执行数组时丢弃了密钥的内容。填充取决于返回的数组是否支持密钥。给定文档,在我看来,密钥的编码似乎是密钥的另一种表示形式,即,该密钥不受返回数组的支持 不过很容易找到答案。请尝试以下操作: byte[

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

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

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

key.getEncoded()
将返回对数组的引用

如果在执行数组时丢弃了密钥的内容。填充取决于返回的数组是否支持密钥。给定文档,在我看来,密钥的编码似乎是密钥的另一种表示形式,即,该密钥不受返回数组的支持

不过很容易找到答案。请尝试以下操作:

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中的数据。具体的实现类可能会提供这种功能,但我不知道有哪种实现类可以提供这种功能。在安卓系统中,不清理数据的风险非常低。每个应用程序都在自己的进程中运行,其内存从外部看不到


我假设有一种攻击场景,根特权进程可以获取内存快照,并将其发送到某个超级计算机进行分析,希望发现某人的密钥。但我从未听说过这样的攻击,我觉得它与其他访问系统的方式没有竞争力。您担心这个特定的假设漏洞有什么原因吗?

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

采取稍微不同的策略,一旦您确定了要覆盖的正确内存区域,您可能会想多次这样做:

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(或者除了C和汇编以外的任何东西)。

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

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

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
不用于AES,也不用于AES。这两个密钥实现还克隆了
getEncoded
方法中的密钥。因此,对于这些非常常见的算法3DES和AES,我们似乎没有办法清除内存中的密钥?

GetEncoded返回密钥副本(因此清除对密钥数据没有影响),默认情况下销毁会抛出DestroyeFailedException,这比无用更糟糕。它也只在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接口,因为Destroyable只是1.8+的特性。这个版本在1.7+上运行,非常有效(它对一个键进行了一次尝试销毁,以决定是否再次使用它)。

-1:Java中的所有其他内容都是通过引用传递的——不,Java总是通过值传递的!不能按值传递对象的原因是,没有变量可以首先包含对象@艾奥贝。。你确定我们谈论的是同一个Java?int是按值传递的,boolean是按值传递的,Integer是一个引用,以及任何对象、数组等。。。Java传递“一个值”,实际上是对一个对象的“引用”,所以它是通过引用来传递的。@SimoneGianni:请忽略我之前的评论,我已经死了。但aioobe是对的:按值传递引用与按引用传递某物不同。@aioobe:您的评论有点误导。事实上,在Java中,所有内容都是按值传递的,包括原语类型和对象引用。实际上,变量只能包含对对象的引用,而不能包含对象本身。但是如果没有它,这是相当混乱的。@Simone-知道它是一个参考并没有帮助OP。问题是,提到什么?特别是,它是对密钥内部数据的引用还是对数据副本的引用?OP想知道清除数组是否会清除密钥中的敏感数据。仅在1.8+中有效,通常只抛出DestroyFailedException,但如果必须使用Java,请在GC可能重新打包数据或操作系统将其移动到交换之前快速将其归零,等等
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);
}