Обнулить байты данных QString

Я использую QStrings для хранения паролей. Если быть более точным, я использую QStrings для извлечения паролей из GUI. Дело в том, что после использования пароля/устройства мне нужно аннулировать (ноль) внутренний QStrings байты данных с паролем, чтобы полностью исключить его из памяти.

вот мои исследования:

  • после QString разрушение это данные остаются в памяти nonzeroed;
  • когда я пытаюсь изменить QString чтобы выполнить его с нулями запускает идиому копирования при записи и выделяет новую память для измененного варианта данных. Старые данные остаются нетронутыми. То же самое происходит даже если я использую QString::data() метод. Не совсем уверен, почему-вероятно, потому, что он возвращает не raw char * но QChar *;
  • QString::clear(), = "" делает на самом деле ту же корову, как описано выше.

Q: Как я могу реализовать правильное QString очистка для предотвращения утечки паролей?

2 ответов


у меня есть два возможных способа обойти copy-on-write. Я пробовал их, и они, похоже, работают - не использовали средство просмотра памяти Qt Creator, но неявно общие QStrings, используемые в моем коде, указывали на те же обнуленные данные впоследствии.

используя constData()

как написано в Qt docs для метода QString::data ():

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

таким образом, возможное решение выглядит следующим образом:

QString str = "password";
QString str2 = str;
QChar* chars = const_cast<QChar*>(str.constData());
for (int i = 0; i < str.length(); ++i)
    chars[i] = '0';
// str and str2 are now both zeroed

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

используя итераторы

С Qt docs для неявного совместного использования:

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

Итак, перейдем к разделу, описывающему эта проблема итератора:

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

QVector<int> a, b;
a.resize(100000); // make a big vector filled with 0.

QVector<int>::iterator i = a.begin();
// WRONG way of using the iterator i:
b = a;
/*
    Now we should be careful with iterator i since it will point to shared data
    If we do *i = 4 then we would change the shared instance (both vectors)
    The behavior differs from STL containers. Avoid doing such things in Qt.
*/

a[0] = 5;
/*
    Container a is now detached from the shared data,
    and even though i was an iterator from the container a, it now works as an iterator in b.
*/

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

QString str = "password";
QString::iterator itr = str.begin();
QString::iterator nd = str.end();
QString str2 = str;

while (itr != nd)
{
    *itr = '0';
    ++itr;
} // str and str2 still point to the same data and are both zeroed

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

вы можете, однако, специализируемся std::basic_string использование пользовательского распределителя, который перед удалением может очистить блок памяти (см. ниже пример). Вы можете использовать этот новый безопасное строка для управления паролем вместо простого массива символов, если вы найдете его более удобным. Я не уверен, что это std::basic_string можно специализировать с QChar, но если нет, вы можете использовать любой из символов Юникода из C++11 (char16_t, char32_t...) вместо этого, если вам нужна поддержка ANSI.

Что касается пользовательского интерфейса, я думаю, что у вас есть возможность создать свой собственный виджет ввода текста, переопределяя keyPressEvent / keyReleaseEvent для хранения введенного пароля в безопасная строка или массив символов. Также переопределим paintEvent отображать только звезды, точки или любой другой маскирующий символ, который вы хотите. После того, как пароль был использован просто очистить массив или очистить безопасную строку.


Update: пример безопасной строки

namespace secure {
  template<class T>
  class allocator : public std::allocator<T> {
  public:
    typedef typename std::allocator<T>::pointer pointer;
    typedef typename std::allocator<T>::size_type size_type;

    template<class U>
    struct rebind {
      typedef allocator<U> other;
    };
    allocator() throw() :
      std::allocator<T>() {}
    allocator(const allocator& other) throw() :
      std::allocator<T>(other) {}
    template <class U>
    allocator(const allocator<U>& other) throw() :
      std::allocator<T>(other) {}

    void deallocate(pointer p, size_type num) {
      memset(p, 0, num); // can be replaced by SecureZeroMemory(p, num) on Windows
      std::allocator<T>::deallocate(p, num);
    }
  };

  class string : public std::basic_string<char, std::char_traits<char>, allocator<char>> {
  public:
    string() :
      basic_string() {}

    string(const string& str) :
      basic_string(str.data(), str.length()) {}

    template<class _Elem, class _Traits, class _Ax>
    string(const std::basic_string<_Elem, _Traits, _Ax>& str) :
      basic_string(str.begin(), str.end()) {}

    string(const char* chars) :
      basic_string(chars) {}

    string(const char* chars, size_type sz) :
      basic_string(chars, sz) {}

    template<class _It>
    string(_It a, _It b) :
      basic_string(a, b) {}
  };
}