簡體   English   中英

我如何歸零java中的密鑰?

[英]How do I zero-ise a secret key in 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.Destroyable com.sun.crypto.provider.DESedeKey也沒有用於AES的javax.crypto.spec.SecretKeySpec 這兩個關鍵實現都克隆了getEncoded方法中的鍵。 因此,對於這些非常常見的算法3DES和AES,我們似乎沒有辦法擦除密鑰的內存?

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+並且非常有效(它在一個鍵上進行試驗銷毀以決定再次使用它)。

我很確定清除rawKey不會影響key的數據。

我不認為有一種方法可以清除SecretKey中的數據。 具體的實現類可以提供,但我不知道這樣做。 在Android中,保持數據不被清除的風險非常低。 每個應用程序都在自己的進程中運行,其內存在外部不可見。

我想有一個攻擊場景,一個root權限的進程可以獲取內存快照並將它們發送到某個地方的某個超級計算機進行分析,希望能夠發現某人的密鑰。 但是我從來沒有聽說過這樣的攻擊,而且我覺得它與其他獲取系統訪問權限的方式沒有競爭力。 您是否有理由擔心這個特殊的假設漏洞?

根據為垃圾收集器提供動力的技術,任何單個對象都可以隨時移動(即復制)在物理內存中,因此您無法確定是否真的會通過清零數組來破壞密鑰 - 假設您可以訪問“ “持有密鑰的數組,而不是其副本。

簡而言之:如果您的安全模型和上下文調用了歸零鍵,那么您根本不應該使用Java(或者除了C和匯編之外的任何東西)。

換句話說,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類可能仍然保存生成該字節數組所需的數據,最終包括給定字節數組的另一個副本,因此您應該研究如何清除該數據。

采用稍微不同的方法,一旦確定要覆蓋的正確內存區域,您可能需要多次執行此操作:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM