Как обнулить секретный ключ в Java? - PullRequest
6 голосов
/ 25 августа 2011

Достаточно ли следующего java-кода для очистки секретного ключа в памяти (установка всех его байтов в значение 0)?

zerorize(SecretKey key)
{
    byte[] rawKey = key.getEncoded();
    Arrays.fill(rawKey, (byte) 0);
}

Другими словами, возвращает ли метод getEncoded копию или ссылку на фактический ключ? Если копия возвращается, то как я могу очистить секретный ключ в качестве меры безопасности?

Ответы [ 8 ]

6 голосов
/ 25 августа 2011

Прежде чем пытаться очистить ключ, вы должны сначала проверить, реализует ли реализация интерфейса SecretKey также интерфейс javax.security.auth.Destroyable. Если да, то, конечно, предпочитаю.

3 голосов
/ 08 декабря 2012

getEncoded(), по-видимому, в основном возвращает клон ключа (из источника Oracle 1.6, например, javax.security.auth.kerberos):

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 у нас нет способа стереть память для секретного ключа?

1 голос
/ 25 марта 2017

GetEncoded возвращает копию секретного ключа (такая очистка, которая не влияет на данные секретного ключа), и уничтожение по умолчанию создает исключение DestroyFailedException, которое хуже бесполезного. Он также доступен только в версии 1.8+, поэтому Android не повезло. Вот хак, который использует самоанализ для (1) вызова уничтожения, если оно доступно, и не выдает исключение, в противном случае (2) обнуляет данные ключа и устанавливает ссылку на ноль.

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+ и довольно эффективна (она пробно уничтожает один ключ, чтобы решить использовать его снова).

0 голосов
/ 26 августа 2011

В зависимости от технологии, приводящей в действие сборщик мусора, любой отдельный объект может быть перемещен (т.е. скопирован) в физическую память в любое время, поэтому вы не можете быть уверены, что действительно уничтожите ключ, обнулив массив - при условии, что вы может получить доступ к массиву "the", который содержит ключ, а не его копию.

Короче говоря: если ваша модель безопасности и контекст требуют обнуления ключей, то вам вообще не следует использовать Java (или только что-либо, кроме C и сборки).

0 голосов
/ 25 августа 2011

Принимая несколько иной такт, после того как вы определили правильную область памяти для перезаписи, вы можете сделать это несколько раз:

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);
}
0 голосов
/ 25 августа 2011

Я почти уверен, что очистка rawKey не повлияет на данные в key.

Я не думаю, что вообще есть способ очистить данные в SecretKey.Определенные классы реализации могут обеспечивать это, но я не знаю ни одного, что делает.В Android риск оставить данные не очищенными очень низок.Каждое приложение работает в своем собственном процессе, и его память не видна извне.

Я полагаю, что существует сценарий атаки, в котором привилегированный процесс root может сделать снимки памяти и отправить их на какой-нибудь суперкомпьютер для анализа,в надежде обнаружить чьи-то секретные ключи.Но я никогда не слышал о такой атаке, и мне кажется, что она не конкурентоспособна с другими способами получить доступ к системе.Есть ли причина, по которой вы беспокоитесь об этой конкретной гипотетической уязвимости?

0 голосов
/ 25 августа 2011

За исключением примитивных значений, все остальное в Java всегда передается по ссылке, включая массивы, так что да, вы правильно очищаете данный байтовый массив.

Однако класс SecretKey, вероятно, по-прежнему содержит данные, необходимые для генерации этого байтового массива, включая, в конечном счете, еще одну копию данного байтового массива, поэтому вам следует изучить, как очистить эти данные.

0 голосов
/ 25 августа 2011

Другими словами, возвращает ли метод 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.

...