以下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 ..我指的是「給定的字節數組」,但是進一步澄清說可能有另一個副本。 –