Обнулить байты данных QString
Я использую QString
s для хранения паролей. Если быть более точным, я использую QString
s для получения паролей из графического интерфейса. Дело в том, что после использования пароля / устройства мне нужно обнулить (обнулить) внутренний QString
s байты данных с паролем, чтобы полностью исключить его из памяти.
Вот мои исследования:
- После
QString
уничтожение его данных остается в памяти ненулевым; - Когда я пытаюсь изменить
QString
чтобы выполнить его с нулями, он запускает идиому копирования при записи и выделяет новую память для измененного варианта данных. Старые данные остаются нетронутыми. То же самое происходит, даже если я используюQString::data()
метод. Не совсем уверен, почему - вероятно, потому что он возвращает не сыройchar *
ноQChar *
; QString::clear()
,= ""
на самом деле тот же COW, как описано выше.
Q: Как я могу реализовать правильно QString
очистка для предотвращения утечки паролей?
2 ответа
У меня есть два возможных способа обойти копирование при записи. Я попробовал их, и они, кажется, работают - не использовали просмотрщик памяти Qt Creator, но неявно разделяемые строки QStrings, используемые в моем коде, впоследствии указывали на одни и те же обнуленные данные.
Использование constData()
Как написано в документации Qt для метода 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 для неявного совместного использования:
Неявно разделяемый класс имеет контроль над своими внутренними данными. В любых функциях-членах, которые изменяют свои данные, он автоматически отключается перед изменением данных. Обратите внимание, однако, на особый случай с контейнерными итераторами; см. проблему неявного совместного использования итератора.
Итак, давайте перейдем к разделу, описывающему эту проблему итератора:
Неявное совместное использование имеет другое следствие для итераторов в стиле 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
используя пользовательский распределитель, который перед удалением может очистить блок памяти (см. пример ниже). Вы можете использовать эту новую безопасную строку для манипулирования вашим паролем вместо обычного массива char, если вы считаете это более удобным. Я не уверен, что std::basic_string
может быть специализирован с QChar
, но если нет, вы можете использовать любой из символов Unicode из C++ 11 (char16_t
, char32_t
...) вместо этого, если вам нужна поддержка, кроме ANSI.
Что касается пользовательского интерфейса, я думаю, что у вас есть возможность создать собственный виджет для ввода текста, переопределив keyPressEvent
/ keyReleaseEvent
сохранить введенный пароль в защищенную строку или массив символов. Также переопределить paintEvent
отображать только звезды, точки или любой другой маскирующий символ, который вы хотите. После того, как пароль был использован, просто очистите массив или очистите безопасную строку.
Обновление: пример безопасной строки
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) {}
};
}