Подтвердить что ты не робот

Как очистить (перезаписать со случайными байтами) std::string внутренний буфер?

Рассмотрим сценарий, где std::string используется для хранения секретности. Как только он будет потреблен и больше не нужен, было бы хорошо его очистить, то есть перезаписать память, содержащую его, тем самым скрыв секрет.

std::string предоставляет функцию const char* data(), возвращающую указатель на непрерывную память (начиная с С++ 11).

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

char* modifiable = const_cast<char*>(secretString.data());
OpenSSL_cleanse(modifiable, secretString.size());

Согласно стандарту, указанному здесь:

$5.2.11/7 - Примечание: в зависимости от типа объекта операция записи через указатель, lvalue или указатель на элемент данных, полученный в результате const_cast, который отбрасывает const-qualifier 68 может вызывать поведение undefined (7.1.5.1).

Что бы посоветовать иначе, но выполняйте ли указанные выше условия (непрерывный, чтобы просто удалить)?

4b9b3361

Ответ 1

Это, вероятно, безопасно. Но не гарантируется.

Однако, поскольку C++11, a std::string должен быть реализован как непрерывные данные, чтобы вы могли безопасно обращаться к его внутреннему массиву, используя адрес его первого элемента &secretString[0].

if(!secretString.empty()) // avoid UB
{
    char* modifiable = &secretString[0];
    OpenSSL_cleanse(modifiable, secretString.size());
}

Ответ 2

В стандарте явно указано, что вы не должны писать в const char*, возвращенный data(), поэтому не делайте этого.

Есть совершенно безопасные способы получить модифицируемый указатель:

if (secretString.size())
  OpenSSL_cleanse(&secretString.front(), secretString.size());

Или, если строка, возможно, уже была сжата, и вы хотите, чтобы вся ее емкость была стерта:

if (secretString.capacity()) {
  secretString.resize(secretString.capacity());
  OpenSSL_cleanse(&secretString.front(), secretString.size());
}

Ответ 3

std::string - плохой выбор для хранения секретов. Так как строки можно копировать, а иногда копии остаются незамеченными, ваш секрет может "получить ноги". Кроме того, методы расширения строк могут вызывать множественные копии фрагментов (или всех) ваших секретов.

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

Ответ 4

Вы можете использовать std::fill, чтобы заполнить строку мусором:

std::fill(str.begin(),str.end(), 0);

Обратите внимание, что простое удаление или сжатие строки (с помощью таких методов clear или shrink_to_fit) не гарантирует, что строковые данные будут удалены из памяти процесса. Вредоносные процессы могут выгружать память процесса и могут извлекать секрет, если строка не перезаписана правильно.

Бонус: Интересно, что возможность измельчения строковых данных по соображениям безопасности заставляет некоторые языки программирования, такие как Java, возвращать пароли как char[], а не String. В Java String неизменен, поэтому "trashing" будет создавать новую копию строки. Следовательно, вам нужен модифицируемый объект, например char[], который не использует copy-on-write.

Изменить: если ваш компилятор делает оптимизирует этот вызов, вы можете использовать определенные флаги компилятора, чтобы убедиться, что функция перебора не будет оптимизирована:

#ifdef WIN32

#pragma optimize("",off)
void trashString(std::string& str){
   std::fill(str.begin(),str.end(),0);
}
#pragma optimize("",on)

#endif

#ifdef __GCC__

void __attribute__((optimize("O0"))) trashString(std::string& str) {
       std::fill(str.begin(),str.end(),0);
}


#endif

#ifdef __clang__

void __attribute__ ((optnone))  trashString(std::string& str) {
       std::fill(str.begin(),str.end(),0);
}

#endif

Ответ 5

Там лучший ответ: не!

std::string - это класс, который предназначен для удобства и эффективности. Он не был разработан с криптографией, поэтому в нем есть несколько гарантий, чтобы помочь вам. Например, нет никаких гарантий того, что ваши данные не были скопированы в другом месте. В лучшем случае вы можете надеяться, что конкретная реализация компилятора предложит вам поведение, которое вы хотите.

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

Ответ 6

Протестированное решение для CentOS 6, Debian 8 и Ubuntu 16.04 (g++/clang++, O0, O1, O2, O3):

secretString.resize(secretString.capacity(), '\0');
OPENSSL_cleanse(&secretString[0], secretString.size());
secretString.clear();