Как я могу обеспечить уничтожение объекта String в Java? - PullRequest
38 голосов
/ 08 марта 2011

Сотрудник моей компании должен изменить данные из базы данных SQL Server через программу, которую я создал.Сначала программа использовала проверку подлинности Windows, и я попросил администраторов баз данных дать этому конкретному пользователю доступ на запись в указанную базу данных.

Они не хотели этого делать, а вместо этого предоставили доступ на запись к my * 1004.* Учетная запись пользователя Windows.

Поскольку я доверяю этому парню, но не могу позволить ему поработать 90 минут с открытым сеансом, я просто добавлю приглашение для входа в мою программу, запрашивая комбинацию имени пользователя и пароля,и войти в SQL Server с ним.Я войду в систему и доверю своему приложению, чтобы позволить ему делать только то, что ему нужно.

Это, однако, создает небольшую угрозу безопасности.Учебное пособие по полям паролей over Sun На сайте Oracle указано, что пароли должны храниться в памяти в течение минимального количества времени, и с этой целью метод getPassword возвращает char[]массив, который вы можете обнулить, как только закончите.

Тем не менее, класс DriverManager Java принимает в качестве паролей только объекты String, поэтому я не смогу избавиться от пароля, как толькоЯ закончил с этим.А поскольку у моего приложения, кстати, довольно мало ресурсов и требований к памяти, кто знает, как долго оно просуществует в памяти?Программа будет работать довольно долго, как указано выше.

Конечно, я не могу контролировать то, что классы JDBC SQL Server делают с моим паролем, но я надеялся, что смогу контролировать, что I сделать с моим паролем.

Есть ли верный способ уничтожить / обнулить String объект с помощью Java?Я знаю, что оба против языка (уничтожение объектов недетерминировано, и String объекты неизменны), а System.gc() тоже непредсказуемо, но все же;есть идеи?

Ответы [ 9 ]

19 голосов
/ 09 марта 2011

Итак, вот плохие новости. Я удивлен, что никто еще не упомянул это. с современными сборщиками мусора, даже вся концепция char [] не работает. независимо от того, используете ли вы String или char [], данные могут в конечном итоге жить в памяти в течение длительного времени. это почему? потому что современные jvms используют сборочные сборщики мусора, которые, вкратце, копируют объекты повсюду . таким образом, даже если вы используете char [], фактическая память, которую он использует, может быть скопирована в разные места в куче, оставляя копии пароля везде, где бы он ни находился (и никакой производительный gc не обнулит старую память). поэтому, когда вы обнуляете экземпляр, который у вас есть в конце, вы обнуляете только последнюю версию в памяти.

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

11 голосов
/ 09 марта 2011

Я могу думать только о решении с использованием отражения.Вы можете использовать рефлексию для вызова частного конструктора, который использует общий массив символов:

  char[] chars = {'a', 'b', 'c'};
  Constructor<String> con = String.class.getDeclaredConstructor(int.class, int.class, char[].class);
  con.setAccessible(true);
  String password = con.newInstance(0, chars.length, chars);
  System.out.println(password);

  //erase it
  Arrays.fill(chars, '\0');
  System.out.println(password);

Редактировать

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

3 голосов
/ 09 марта 2011

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

как-то, я должен добавить LOL к этому, даже если мой ответ выше, очень серьезно:)

1 голос
/ 09 марта 2011

Я не уверен в классе DriverManager.
Вообще говоря, вы правы, рекомендуется хранить пароль в массивах символов и явно очищать память после использования.
Самый распространенный пример:

KeyStore store = KeyStore. getInstance(KeyStore, getDefaultType()) ;
char[] password = new char[] {'s','e','c','r','e','t'};
store .load(is, password );
//After finished clear password!!!
Arrays. fill(password, '\u0000' ) ;

В JSSE и JCA проект имел в виду именно это. Вот почему API ожидают char[], а не String.
Поскольку, как вы очень хорошо знаете, строки являются неизменяемыми, пароль подходит для future сбора мусора, и вы не сможете сбросить его впоследствии. Это может привести к угрозе безопасности со стороны вредоносных программ, отслеживающих области памяти.
Я не думаю, что в этом случае вы смотрите, есть работа вокруг.
На самом деле здесь есть похожий вопрос:
Почему в Driver Manager не используются массивы символов?
но нет четкого ответа.
Похоже, концепция заключается в том, что пароль уже хранится в файле свойств (уже существует конструктор DriverManager, принимающий свойства), и поэтому сам файл уже представляет больший риск, чем фактическая загрузка пароля из файла в строку.
Или разработчики API имели некоторые предположения относительно безопасности машины, обращающейся к БД.
Я думаю, что самым безопасным вариантом было бы попытаться выяснить, если это возможно, то, как DriverManager использует пароль, т. Е. Удерживает ли он внутреннюю ссылку и т. Д.

1 голос
/ 09 марта 2011

Вы можете изменить значение внутреннего char[], используя отражение.

Вы должны быть осторожны, чтобы изменить его на массив той же длины или также обновить поле count. Если вы хотите использовать его в качестве записи в наборе или в качестве значения на карте, вам потребуется пересчитать хеш-код и установить значение поля hashCode.

При этом минимальный код для достижения этой цели -

        String password = "password";

        Field valueField = String.class.getDeclaredField("value");
        valueField.setAccessible(true);
        char[] chars  = (char[]) valueField.get(password);

        chars[0] = Character.valueOf('h');

        System.out.println(password);
0 голосов
/ 09 марта 2011

Если строка не удерживается диспетчером драйверов JDBC (большое if), я бы не стал беспокоиться о принудительном ее уничтожении.Современная JVM по-прежнему запускает сборку мусора довольно быстро, даже с большим количеством доступной памяти.Вопрос в том, эффективен ли сбор мусора «безопасное стирание».Я сомневаюсь, что это так.Я предполагаю, что он просто забывает ссылку на эту ячейку памяти и ничего не обнуляет.

0 голосов
/ 09 марта 2011

поэтому я не смогу избавиться от пароля, как только я закончу с ним.

Почему бы и нет?

Если вы получаете соединение через

Driver.getConnection 

вам просто нужно передать пароль и позволить ему получить gc'ed.

void loginScreen() {
   ... 
   connect( new String( passwordField.getPassword() ) );
   ...
}
...
void connect( String withPassword ) { 
    connection = DriverManager.getConnection( url, user, withPassword );
    ...
}//<-- in this line there won't be any reference to your password anymore.

Когда элемент управления возвращается из метода connect, ссылка на ваш пароль больше не существует. Никто не может использовать это больше. Если вам нужно создать новую сессию, вы должны снова вызвать «connect» с новым паролем. Ссылка на объект, содержащий вашу информацию, потеряна.

0 голосов
/ 09 марта 2011

Обычного способа принудительного сбора мусора нет. Это возможно с помощью нативного вызова, но я не знаю, сработает ли он для строк, поскольку они объединены.

Может быть, альтернативный подход поможет? Я не очень знаком с SQL Server, но в Oracle практика заключается в том, чтобы пользователь A владел объектами базы данных и набором хранимых процедур, а пользователь B не имел ничего, кроме как получил разрешение на запуск процедур. Таким образом, пароль больше не является проблемой.

В вашем случае это будет ваш пользователь, которому принадлежат все объекты базы данных и хранимые процедуры, а вашему сотруднику потребуются права доступа к хранимым процессам, которые, как мы надеемся, администраторы БД будут менее охотно предоставлять.

0 голосов
/ 08 марта 2011

Интересный вопрос.Некоторые гуглинги показали это: http://securesoftware.blogspot.com/2009/01/java-security-why-not-to-use-string.html. Согласно комментарию, это не будет иметь значения.

Что произойдет, если вы не сохраните String в переменной, а передадите ее через новую String (char[])

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...