使 QString 数据字节无效

Nullify QString data bytes

我使用 QStrings 来存储密码。更准确地说,我使用 QStrings 从 GUI 获取密码。 关键是在密码 usage/appliance 之后,我需要用密码使(零)内部 QStrings 数据字节无效,以将其完全从内存中删除。

这是我的调查:

问:如何实施适当的 QString 清理以防止密码泄露?

您必须了解所有可能完成的临时副本。如果你想避免这种情况,你必须在释放每个临时副本之前手动擦除内存。不幸的是,由于实施已关闭,标准 QString 无法做到这一点。

但是,您可以使用自定义分配器专门化 std::basic_string,它可以在删除之前清理内存块(参见下面的示例)。如果您觉得更方便,可以使用这个新的 安全字符串 代替普通的字符数组来操作您的密码。我不确定 std::basic_string 是否可以专门用于 QChar,但如果不能,您可以使用 C++11 中的任何 Unicode 字符(char16_tchar32_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) {}
  };
}

我有两种绕过写时复制的方法。我已经尝试过它们,它们似乎工作 - 没有使用 Qt Creator 的内存查看器,但我的代码中使用的隐式共享 QString 之后都指向相同的归零数据。

使用 constData()

Qt docs for QString::data() method中所写:

For read-only access, constData() is faster because it never causes a deep copy to occur.

因此可能的解决方案如下所示:

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 for implicit sharing

An implicitly shared class has control of its internal data. In any member functions that modify its data, it automatically detaches before modifying the data. Notice, however, the special case with container iterators; see Implicit sharing iterator problem.

那么让我们转到描述 this iterator problem 的部分:

Implicit sharing has another consequence on STL-style iterators: you should avoid copying a container while iterators are active on that container. The iterators point to an internal structure, and if you copy a container you should be very careful with your iterators. E.g.:

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.
*/

据我了解,基于上述文档片段,您应该能够利用迭代器的这个 "wrong usage" 来使用迭代器操作您的原始字符串,因为它们不会触发写时复制.在进行任何复制之前 "intercept" 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