读取密码后覆盖 std::cin 读取缓冲区
Overwrite std::cin read-buffer after reading password
从 std::cin
读取密码后
std::cout << "Password: ";
SecureString pw{}; // secure string, wipes automatically on destruction
SetConsoleEcho(false); // hide input in terminal
std::getline(std::cin, pw); // input the password
SetConsoleEcho(true); // switch echo back on
密码一直存储在 cin(堆内存)的读取缓冲区中,直到它被新输入覆盖,并且可以使用 Process Hacker 等工具轻松嗅探,或者在内存转储的情况下写入磁盘。
有谁知道 std::cin.rdbuf() 是如何 cleared/overwritten 的,最好是跨平台的?或者有没有办法避免首先使用缓冲区?
以上代码使用了 SecureString,它是使用 Crypto++ 的 AllocatorWithCleanup 实现的,它在销毁时擦除内存。 SetConsoleEcho 切换控制台回显 on/off 以避免在屏幕上看到明文密码。
我意识到这个问题与 this one 非常相似,但已经超过 10 年了,接受的答案实际上并没有回答这个问题。
链接的 post 提供了您需要的所有答案,不幸的是,没有一种方法可以安全地读取然后清除密码,同时将密码泄露的风险降为 0。见
Even if you scribble over the buffer immediately, it's still possible the password is written to disk. A system i/o buffer might be paged to disk, as might the working memory which std::cin is in. I used to develop forensic software which sniffed out exactly these conditions.
此外,如果恶意行为者可以访问 PC,则可以使用键盘记录器提取密码。
Or is there a way how to avoid using the buffer in the first place?
我找到了 cross-platform 方法(Linux、Mac、Windows)如何使用 readline 替换库避免 cin 的读取缓冲区。我使用了 replxx 但其他 readline 替换库也可能会这样做。
我围绕 replxx 创建了一个简单的包装器 class PasswordInput
,它在每次击键时清除其输入缓冲区,并使用 Crypto++ 的 AllocatorWithCleanup 将输入的密码存储在安全字符串中。创建密码摘要后,安全字符串会自动擦除,因此不会在进程内存中留下普通密码的副本。
所有这一切都是为了避免明文密码在进程内存中的存储时间超过所需时间。它既不能防止键盘记录器,也不能防止在计算摘要时进程崩溃时泄露密码的可能性,也不能防止适时的嗅探攻击,也不能防止任何其他可能的攻击向量。
到目前为止,我只在 Windows 上进行了测试,而且我只使用 Process Hacker 进行了基本的内存嗅探测试。这些显示没有纯密码的痕迹(与使用 std::cin 相反)
头文件:
using SecureString =
std::basic_string<char, std::char_traits<char>, CryptoPP::AllocatorWithCleanup<char>>;
class PasswordInput {
public:
PasswordInput();
void Read(const std::string& prompt);
inline const SecureString& Secret() const { return secret_; }
private:
void RxPwModifyCb(std::string& line, int& pos);
Replxx rx_;
SecureString secret_;
};
实施:
namespace ph = std::placeholders;
PasswordInput::PasswordInput() :
rx_{}
{
rx_.set_modify_callback(std::bind(&PasswordInput::RxPwModifyCb, this, ph::_1, ph::_2));
}
void PasswordInput::Read(const std::string& prompt)
{
char const* cinput{nullptr};
do
{
cinput = rx_.input(prompt);
} while ((cinput == nullptr) && (errno == EAGAIN));
}
void PasswordInput::RxPwModifyCb(std::string& line, int& pos)
{
if (!line.empty())
{
secret_.append(1, line[0]);
line.clear();
}
}
用法:
PasswordInput pwi{}; // create password input handler
pwi.Read("Password: "); // read the password
std::cout << "\x1b[A" << "[2K\r"; // go one line up, delete line and go to beginning (does nothing in terms of security but looks cool)
CalculatePassphrase(pwi.Secret().c_str(), pwi.Secret().size()); // Scrypt, pbkdf2, etc..
从 std::cin
读取密码后std::cout << "Password: ";
SecureString pw{}; // secure string, wipes automatically on destruction
SetConsoleEcho(false); // hide input in terminal
std::getline(std::cin, pw); // input the password
SetConsoleEcho(true); // switch echo back on
密码一直存储在 cin(堆内存)的读取缓冲区中,直到它被新输入覆盖,并且可以使用 Process Hacker 等工具轻松嗅探,或者在内存转储的情况下写入磁盘。
有谁知道 std::cin.rdbuf() 是如何 cleared/overwritten 的,最好是跨平台的?或者有没有办法避免首先使用缓冲区?
以上代码使用了 SecureString,它是使用 Crypto++ 的 AllocatorWithCleanup 实现的,它在销毁时擦除内存。 SetConsoleEcho 切换控制台回显 on/off 以避免在屏幕上看到明文密码。
我意识到这个问题与 this one 非常相似,但已经超过 10 年了,接受的答案实际上并没有回答这个问题。
链接的 post 提供了您需要的所有答案,不幸的是,没有一种方法可以安全地读取然后清除密码,同时将密码泄露的风险降为 0。见
Even if you scribble over the buffer immediately, it's still possible the password is written to disk. A system i/o buffer might be paged to disk, as might the working memory which std::cin is in. I used to develop forensic software which sniffed out exactly these conditions.
此外,如果恶意行为者可以访问 PC,则可以使用键盘记录器提取密码。
Or is there a way how to avoid using the buffer in the first place?
我找到了 cross-platform 方法(Linux、Mac、Windows)如何使用 readline 替换库避免 cin 的读取缓冲区。我使用了 replxx 但其他 readline 替换库也可能会这样做。
我围绕 replxx 创建了一个简单的包装器 class PasswordInput
,它在每次击键时清除其输入缓冲区,并使用 Crypto++ 的 AllocatorWithCleanup 将输入的密码存储在安全字符串中。创建密码摘要后,安全字符串会自动擦除,因此不会在进程内存中留下普通密码的副本。
所有这一切都是为了避免明文密码在进程内存中的存储时间超过所需时间。它既不能防止键盘记录器,也不能防止在计算摘要时进程崩溃时泄露密码的可能性,也不能防止适时的嗅探攻击,也不能防止任何其他可能的攻击向量。
到目前为止,我只在 Windows 上进行了测试,而且我只使用 Process Hacker 进行了基本的内存嗅探测试。这些显示没有纯密码的痕迹(与使用 std::cin 相反)
头文件:
using SecureString =
std::basic_string<char, std::char_traits<char>, CryptoPP::AllocatorWithCleanup<char>>;
class PasswordInput {
public:
PasswordInput();
void Read(const std::string& prompt);
inline const SecureString& Secret() const { return secret_; }
private:
void RxPwModifyCb(std::string& line, int& pos);
Replxx rx_;
SecureString secret_;
};
实施:
namespace ph = std::placeholders;
PasswordInput::PasswordInput() :
rx_{}
{
rx_.set_modify_callback(std::bind(&PasswordInput::RxPwModifyCb, this, ph::_1, ph::_2));
}
void PasswordInput::Read(const std::string& prompt)
{
char const* cinput{nullptr};
do
{
cinput = rx_.input(prompt);
} while ((cinput == nullptr) && (errno == EAGAIN));
}
void PasswordInput::RxPwModifyCb(std::string& line, int& pos)
{
if (!line.empty())
{
secret_.append(1, line[0]);
line.clear();
}
}
用法:
PasswordInput pwi{}; // create password input handler
pwi.Read("Password: "); // read the password
std::cout << "\x1b[A" << "[2K\r"; // go one line up, delete line and go to beginning (does nothing in terms of security but looks cool)
CalculatePassphrase(pwi.Secret().c_str(), pwi.Secret().size()); // Scrypt, pbkdf2, etc..