问题
加密
输入:<plaintext> <key> <iv> 输出:<ciphertext> 例如 输入:<i love you, sunsiqi> <0123456789ABCDEF> <FEDCBA0123456789> 输出:<4e152ec964f73950dfc7a3d833bdaeffd6f0b8a727c13c29>
解密
输入:<ciphertext> <key> <iv> 输出:<plaintext> 例如 输入:<4e152ec964f73950dfc7a3d833bdaeffd6f0b8a727c13c29> <0123456789ABCDEF> <FEDCBA0123456789> 输出:<i love you, sunsiqi>
要求
- 进行安全内存管理
- 加密或解密操作完成后清理敏感数据
介绍
CTR模式
CTR(Counter)模式即计数器模式,是一种流密码模式。它将一个递增的计数器值通过加密算法加密后与明文进行异或运算得到密文,解密时使用相同的计数器值加密后再与密文异或得到明文。计数器模式把块密码转换为流密码,可并行处理数据,并且支持随机访问。
特点
- 流密码特性:DES - CTR 模式将 DES 块密码转换为流密码,允许对任意长度的数据进行加密,而不需要对数据进行填充。
- 并行处理:由于每个计数器值的加密操作是相互独立的,因此可以并行处理,提高加密和解密的效率。
- 随机访问:可以在任意位置开始解密,而不需要解密前面的所有数据块,这在需要随机访问加密数据的应用中非常有用。
工作原理
- 初始化计数器:选择一个初始计数器值(通常称为初始向量,IV),计数器的长度通常与 DES 算法的块长度相同(64 位)。
- 计数器递增:在每次加密或解密操作前,计数器的值递增。
- 加密计数器值:使用 DES 算法对当前计数器值进行加密,得到一个 64 位的加密块。
- 异或运算:将加密块与明文数据的相应部分进行异或运算,得到密文数据。解密时,使用相同的计数器值进行加密,然后与密文数据进行异或运算,得到明文数据。
特性
特性 CTR模式 初始化向量 称为计数器(Counter) 填充要求 无需填充 并行性 支持并行加密/解密 错误传播 只影响单个字节 密钥流重用 绝对禁止(计数器必须唯一) 典型应用场景 实时流加密、随机访问 优点
- 高效性:支持并行处理,加密和解密速度快。
- 灵活性:不需要对数据进行填充,可处理任意长度的数据。
- 随机访问:可以在任意位置开始解密,适用于随机访问场景。
缺点
- 密钥管理:需要确保计数器值的唯一性,否则可能会导致安全漏洞。如果两个不同的加密操作使用了相同的计数器值,攻击者可以通过异或两个密文来恢复部分明文信息。
- 安全性依赖:DES 算法本身的密钥长度较短,存在被暴力破解的风险,因此 DES - CTR 模式的安全性也受到一定影响。在对安全性要求较高的场景中,建议使用更安全的加密算法。
分析
为什么要清理敏感数据?
敏感数据(如加密密钥、明文数据、初始化向量等)在使用完毕后如果不及时清理,可能会残留在内存中,存在安全风险。攻击者有可能通过某些手段从内存中恢复这些敏感信息,从而造成数据泄露。
在加密或解密操作完成后需要清理敏感数据,本文主要描述两种操作方式:
- 在方法体内立即执行。在加密或解密方法的加密或解密操作之后添加安全清理敏感数据的代码。使用volatile指针防止编译器优化清零操作。
volatile char* volatile_key = reinterpret_cast<volatile char*>(&key[0]); volatile char* volatile_iv = reinterpret_cast<volatile char*>(&iv[0]); std::fill(volatile_key, volatile_key + key.size(), 0); std::fill(volatile_iv, volatile_iv + iv.size(), 0);
- 使用RAII方式。
- RAII(Resource Acquisition Is Initialization)即 “资源获取即初始化”,是 C++ 中管理资源、避免资源泄漏的一种编程惯用法。
- RAII 利用对象的生命周期来管理资源。当一个对象被创建时,它会自动获取所需的资源;当对象的生命周期结束时(例如,对象离开其作用域),它会自动释放这些资源。这里的资源可以是各种需要手动管理的东西,如内存、文件句柄、网络连接、数据库连接等。
- RAII 的核心原理基于 C++ 的构造函数和析构函数机制:
构造函数:在对象创建时自动调用,负责资源的获取。
析构函数:在对象销毁时自动调用,负责资源的释放。
通过这种方式,资源的生命周期与对象的生命周期绑定在一起,从而确保资源在不需要时被正确释放。// 新增安全擦除类,在namespace SymCryptoUtilities中添加安全擦除类 class SecureWiper { public: SecureWiper(std::string& data) : m_data(data) {} ~SecureWiper() { // 添加调试输出验证清理操作 std::cout << "Wiping data at: " << (void*)&m_data[0] << std::endl; if (!m_data.empty()) { // 使用volatile指针防止编译器优化清零操作 volatile char* ptr = reinterpret_cast<volatile char*>(&m_data[0]); std::fill(ptr, ptr + m_data.size(), 0); } } private: std::string& m_data; };
在main函数解码key和iv之后创建安全擦除对象,在此之后执行加密或解密操作,在return 0;之后自动调用wipe_key和wipe_iv的析构函数完成清理
// 创建安全擦除对象,在main函数的解码key和iv之后创建安全擦除对象,在此之后执行加密或解密操作,在return 0;之后自动调用wipe_key和wipe_iv的析构函数完成清理 SymCryptoUtilities::SecureWiper wipe_key(key); SymCryptoUtilities::SecureWiper wipe_iv(iv);
在main函数中需要移除const修饰符,允许修改key和iv。
// 原代码 const std::string key = SymCryptoUtilities::hexDecode(keyHex); const std::string iv = SymCryptoUtilities::hexDecode(ivHex); // 修改为 std::string key = SymCryptoUtilities::hexDecode(keyHex); std::string iv = SymCryptoUtilities::hexDecode(ivHex);
安全擦除机制
使用volatile指针防止编译器优化清零操作
通过RAII确保离开作用域自动清理,清理操作在栈展开时自动执行,即使加密过程中抛出异常,RAII对象也会保证清理内存
覆盖所有内存区域(包括短字符串优化的栈内存)
内存管理优化
在main函数中使用Crypto++的SecByteBlock替代std::string,SecByteBlock会在析构时自动安全擦除内存。
SecByteBlock 是 Crypto++ 库中用于管理字节数组的类,它在一定程度上提供了自动内存管理功能。当 SecByteBlock 对象的生命周期结束时,其析构函数会自动释放所占用的内存。然而,这并不意味着内存中的数据会被安全地清理。为了确保敏感数据不会残留在内存中,仍然需要在对象销毁前主动清理数据。可以通过自定义一个类,利用其构造函数和析构函数来实现 RAII 方式的敏感数据清理。
CryptoPP::SecByteBlock key = SymCryptoUtilities::hexDecode(keyHex); CryptoPP::SecByteBlock iv = SymCryptoUtilities::hexDecode(ivHex);
代码
头文件
#include <iostream> #include <string> #include <cctype> #include <cryptopp/des.h> #include <cryptopp/modes.h> #include <cryptopp/filters.h> #include <cryptopp/hex.h> #include <stdexcept>
主函数
int main(int argc, char* argv[]) { if (argc != 4) { if (argc != 4) { std::cerr << "Usage: " << argv[0] << " <plaintext> <hex_key> <hex_counter>\n" << "Example: " << argv[0] << " \"SecretData\" 0123456789ABCDEF FEDCBA9876543210\n" << "Note: Key and Counter must be 16-character hexadecimal strings (8 bytes)\n" << "Note: support arbitrary binary data such as Chinese characters\n" << "Note: no padding is required when using the CTR mode." << std::endl; return 1; } } const std::string plaintext = argv[1]; const std::string keyHex = argv[2]; const std::string counterHex = argv[3]; try { // 输入验证 if (keyHex.length() != 16 || !SymCryptoUtilities::isValidHex(keyHex)) { throw std::invalid_argument("Key must be 16-character hex string (8 bytes)"); } if (counterHex.length() != 16 || !SymCryptoUtilities::isValidHex(counterHex)) { throw std::invalid_argument("Counter must be 16-character hex string (8 bytes)"); } /* 内存管理优化,在main函数中使用Crypto++的SecByteBlock替代std::string,SecByteBlock会在析构时自动安全擦除内存。 SecByteBlock 是 Crypto++ 库中用于管理字节数组的类,它在一定程度上提供了自动内存管理功能。当 SecByteBlock 对象的生命周期结束时,其析构函数会自动释放所占用的内存。然而,这并不意味着内存中的数据会被安全地清理。为了确保敏感数据不会残留在内存中,仍然需要在对象销毁前主动清理数据。可以通过自定义一个类,利用其构造函数和析构函数来实现 RAII 方式的敏感数据清理。 CryptoPP::SecByteBlock key = SymCryptoUtilities::hexDecode(keyHex); CryptoPP::SecByteBlock iv = SymCryptoUtilities::hexDecode(ivHex); */ // 十六进制解码密钥和计数器,移除const设置,允许修改 std::string key = SymCryptoUtilities::hexDecode(keyHex); std::string counter = SymCryptoUtilities::hexDecode(counterHex); /* RAII安全擦除 创建安全擦除对象。在解码之后创建安全擦除对象,在此之后执行加密或解密操作。 程序运行完return 0;之后,自动调用wipe_key和wipe_counter的析构函数完成清理 */ SymCryptoUtilities::SecureWiper wipe_key(key); SymCryptoUtilities::SecureWiper wipe_counter(counter); // 执行加密 const std::string ciphertext = SymCryptoUtilities::desCTREncrypt(plaintext, key, counter); std::cout << "Ciphertext (hex): " << ciphertext << std::endl; } catch (const std::exception& e) { std::cerr << "Error: "; try { std::rethrow_if_nested(e); } catch (const std::exception& nested) { std::cerr << nested.what() << std::endl; return 2; } std::cerr << e.what() << std::endl; return 1; } return 0; }
加密
方法
namespace SymCryptoUtilities { bool isValidHex(const std::string& hexStr) { if (hexStr.empty()) return false; for (char c : hexStr) { if (!std::isxdigit(c)) return false; } return true; } std::string hexDecode(const std::string& hexStr) { if (hexStr.length() % 2 != 0) { throw std::invalid_argument("Hex string must have even length"); } std::string decoded; CryptoPP::StringSource ss( hexStr, true, new CryptoPP::HexDecoder( new CryptoPP::StringSink(decoded) ) ); return decoded; } std::string hexEncode(const std::string& data) { std::string encoded; CryptoPP::StringSource ss( data, true, new CryptoPP::HexEncoder( new CryptoPP::StringSink(encoded), false // 无换行符 ) ); return encoded; } /* RAII(Resource Acquisition Is Initialization)即 “资源获取即初始化”,是 C++ 中管理资源、避免资源泄漏的一种编程惯用法。 RAII 利用对象的生命周期来管理资源。当一个对象被创建时,它会自动获取所需的资源;当对象的生命周期结束时(例如,对象离开其作用域),它会自动释放这些资源。这里的资源可以是各种需要手动管理的东西,如内存、文件句柄、网络连接、数据库连接等。 RAII 的核心原理基于 C++ 的构造函数和析构函数机制: 构造函数:在对象创建时自动调用,负责资源的获取。 析构函数:在对象销毁时自动调用,负责资源的释放。 通过这种方式,资源的生命周期与对象的生命周期绑定在一起,从而确保资源在不需要时被正确释放。 通过RAII确保离开作用域自动清理,清理操作在栈展开时自动执行,即使加密过程中抛出异常,RAII对象也会保证清理内存 */ // 安全擦除类(RAII) class SecureWiper { public: SecureWiper(std::string& data) : m_data(data) {} ~SecureWiper() { // 添加调试输出验证清理操作 std::cout << "Wiping data at: " << (void*)&m_data[0] << std::endl; if (!m_data.empty()) { // 使用volatile指针防止编译器优化清零操作 volatile char* ptr = reinterpret_cast<volatile char*>(&m_data[0]); std::fill(ptr, ptr + m_data.size(), 0); } } private: std::string& m_data; }; // DES-CTR加密函数 std::string desCTREncrypt(const std::string& plaintext, const std::string& key, const std::string& counter) { try { // 参数验证 if (key.size() != CryptoPP::DES::DEFAULT_KEYLENGTH) { throw std::invalid_argument("Invalid key size. Expected 8 bytes"); } if (counter.size() != CryptoPP::DES::BLOCKSIZE) { throw std::invalid_argument("Invalid counter size. Expected 8 bytes"); } // 设置加密器 CryptoPP::CTR_Mode<CryptoPP::DES>::Encryption encryptor; encryptor.SetKeyWithIV( reinterpret_cast<const CryptoPP::byte*>(key.data()), key.size(), reinterpret_cast<const CryptoPP::byte*>(counter.data()) ); // 执行加密 std::string ciphertext; CryptoPP::StringSource ss( plaintext, true, new CryptoPP::StreamTransformationFilter( encryptor, new CryptoPP::StringSink(ciphertext) //无填充参数 ) ); /* // 安全清理敏感数据(立即执行) volatile char* volatile_key = reinterpret_cast<volatile char*>(&key[0]); volatile char* volatile_iv = reinterpret_cast<volatile char*>(&iv[0]); std::fill(volatile_key, volatile_key + key.size(), 0); std::fill(volatile_iv, volatile_iv + iv.size(), 0); */ return hexEncode(ciphertext); } catch (const CryptoPP::Exception& e) { std::throw_with_nested(std::runtime_error("Crypto++错误: " + std::string(e.what()))); }catch (const std::exception& e) { std::cerr << "Standard Exception: " << e.what() << std::endl; throw; } } }
解密
方法
// DES-CTR解密函数 std::string desCTRDecrypt(const std::string& ciphertextHex, const std::string& key, const std::string& counter) { try { // 参数验证 if (key.size() != CryptoPP::DES::DEFAULT_KEYLENGTH) { throw std::invalid_argument("Invalid key size. Expected 8 bytes"); } if (counter.size() != CryptoPP::DES::BLOCKSIZE) { throw std::invalid_argument("Invalid counter size. Expected 8 bytes"); } if (!isValidHex(ciphertextHex)) { throw std::invalid_argument("Invalid Hexadecimal ciphertext"); } // 解码十六进制 const std::string ciphertext = hexDecode(ciphertextHex); // 设置解密器(CTR模式加密解密使用相同对象) CryptoPP::CTR_Mode<CryptoPP::DES>::Decryption decryptor; decryptor.SetKeyWithIV( reinterpret_cast<const CryptoPP::byte*>(key.data()), key.size(), reinterpret_cast<const CryptoPP::byte*>(counter.data()) ); // 执行解密 std::string decrypted; CryptoPP::StringSource ss( ciphertext, true, new CryptoPP::StreamTransformationFilter( decryptor, new CryptoPP::StringSink(decrypted) ) ); return decrypted; }catch (const CryptoPP::Exception& e) { std::throw_with_nested(std::runtime_error("Crypto++错误: " + std::string(e.what()))); }catch (const std::exception& e) { std::cerr << "Standard Exception: " << e.what() << std::endl; throw; } }
运行
g++ DES64-CTR-decrypt.cpp -o decrypt -lcryptopp g++ DES64-CTR-encrypt.cpp -o encrypt -lcryptopp