非常有趣。您的程序随着时间的推移而发展,您发现了一些问题并逐步修补它们。有时通过两次犯相同的错误来消除错误。
仍然存在一些必须修复的严重错误。这些大多是超出范围的错误。然后是样式错误,例如全局变量和错误数据类型的使用。
所以,软件设计的一个大问题,必须重构
最后但并非最不重要的一点是,您如何加密的想法行不通,因为您将第一个字母与密钥一起存储在 Key.txt" 中。在
sb = sb + (chars[(sb.length()) / 128]);
您总是在 128 字节的密钥之后添加明文字符。所以你的方法的安全性是 0。
那么,接下来。您必须了解数组的索引和边界。数组索引从 0 开始,最后一个有效索引是数组大小减 1。对于数组 { 1,2,3,4 },大小为 4,第一个索引为 0,最后一个索引为 3 !
您必须始终检查您使用的索引是否有效。例子。在您的声明中:
chars[rand() % 95 + 1]
您会产生许多越界问题。 rand 可以产生介于 0 和非常大的数字。如果这样一个大数的余数是 94,则将索引加 1,得到无效的 95。因为,在变量定义中
char chars[95];
int runs = 0;
runs 在 chars 之后定义,您访问变量 runs 的低字节。你可以测试一下。在第一次创建密钥文件时,您会看到许多 0 字符。在第二次,当运行一次时,您创建 '\001' 字符。
最好使用能够帮助您进行此类检查的现代 C++ 容器。
接下来,阅读您帖子的人无法理解您为什么要做某些事情。所以,我们想知道你为什么写substr(1,128),然后写content = content.substr(130, content.size());。这样,您消除了第一个明文字符,然后读取 128 个字节。然后跳过附加到密钥文件的“\n”。这一切都应该重构。
另一个问题。用于阅读您的“chars.txt”。您发现您没有在循环中读取空格 ' ' 字符。但是您没有检查 IO 流的“skipws”属性,而是通过添加一个空格作为最后一个字符来修补它。您可以使用 functionget 或 getline 来解决这个问题。
现在回答你的问题。在解密你使用
for (int i = 0; i < 94; i++)
所以,除了最后一行,您阅读了所有内容。结束然后你做
pass = pass.substr(129, pass.length());
129 是错误的。它必须是 128。您已经去掉了第一个透明字符和“\n”。然后就可以了,我在调试器中测试了一下。
但是,所有这些都行不通,因为您为小的输入字符串创建了巨大的加密输出。因此,对于 3 个字符“abc”,您将创建 384 个字节的输出。谁应该输入它,尤其是错误地创建 0 个字符。因此,您甚至无法测试代码。您应该将加密后的数据存储在一个文件中,然后为了解密,再次从文件中读取它。
顺便说一句,你调用你的函数递归运行,这是错误的。
还有更多的发现。因此建议重构它。请添加大量错误检查。特别是对于任何 IO 功能。您必须测试它是否有效。
如果您还有其他问题,请提出。
为了向您展示如何进行错误检查,我为您创建了一个完整的可行解决方案。请看:
#include <iostream>
#include <vector>
#include <fstream>
#include <string>
#include <random>
#include <algorithm>
#include <iterator>
#include <numeric>
// Definitions ---------------------------------------------------------------------------------------------------------------------
// Filenames that we want to use in our software
const std::string fileNamePrintableCharacters{ "chars.txt" };
const std::string fileNameKey{ "Key.txt" };
// That many printable characters we will use. We will use the ASCII and hence have characters in the range 32-126, and that are 95
constexpr size_t NumberOfUsedPrintableCharacters = 95U;
// One character will be encrypted with this number of bytes
constexpr size_t NumberOfEncryptionCharacters = 128U;
// This is datatype for our key. For each printable character we use NumberOfEncryptionCharacters.
using KeyData = std::vector<std::string>;
// ----------------------------------------------------------------------------------------------------------------------------------
// Read the printable characters from the definition file
std::string getPrintableCharacters() {
// Here we sill store the result. Initialize all values with 0
std::string printableCharacters{};
// Open the file with the printable characters and check, if the file could be opened
if (std::ifstream printableCharactersFileStream{ fileNamePrintableCharacters }; printableCharactersFileStream) {
// Read the printable characters and check, if that worked
if (!std::getline(printableCharactersFileStream, printableCharacters) || (printableCharacters.size() != NumberOfUsedPrintableCharacters)) {
std::cerr << "\n\n*** Error: Could not read the expected data from ('" << fileNamePrintableCharacters << "')\n";
printableCharacters = {};
}
}
else { // In case that we could not open the file, show error message
std::cerr << "\n\n*** Error: File with printable characters ('" << fileNamePrintableCharacters << "') could not be opened\n";
}
// Return the array with printable characters to the caller using Return Value Optimization / Copy Elision
return printableCharacters;
}
// ----------------------------------------------------------------------------------------------------------------------------------
std::string generateNewPrintableCharactersFile() {
// Here we will stor the resulting printabl characters string
std::string printableCharacters(NumberOfUsedPrintableCharacters, '\0');
// Open output file and chcke, if it could be opened
if (std::ofstream printableCharactersFileStream{ fileNamePrintableCharacters }; printableCharactersFileStream) {
// Create all printable characters
std::iota(printableCharacters.begin(), printableCharacters.end(), ' ');
if (!(printableCharactersFileStream << printableCharacters)) {
std::cerr << "\n\n*** Error: Could not create printable character file\n";
printableCharacters.clear();
}
}
return printableCharacters;
}
// ----------------------------------------------------------------------------------------------------------------------------------
KeyData getKeyData() {
// Here we will store all key data
KeyData keyData{};
// indicator for error. In case of error, we delete all key data
bool thereWasAnError = false;
// Open the file with the key data and check, if the file could be opened
if (std::ifstream keyDataFileStream{ fileNameKey }; keyDataFileStream) {
// Read NumberOfUsedPrintableCharacters lines from the key data file
for (size_t i{}; (i < NumberOfUsedPrintableCharacters) && keyDataFileStream && !thereWasAnError; ++i) {
// Read a line, and check, if that worked
if (std::string line{}; std::getline(keyDataFileStream, line)) {
// Sanity check. Check, if there were the expected number of characters in the key file
if (line.size() != NumberOfEncryptionCharacters) {
std::cerr << "\n\n*** Error: Invalid data in keyfile line " << i + 1 << "\n";
thereWasAnError = true;
}
else { // Save new read line with encryption characters
keyData.push_back(line);
}
}
}
// Next sanity check. Check, if we have read enough lines
if (keyData.size() != NumberOfUsedPrintableCharacters) {
std::cerr << "\n\n*** Error: Not enough data in key file\n";
thereWasAnError = true;
}
}
else { // In case that we could not open the file, show error message
std::cerr << "\n\n*** Error: File with key data ('" << fileNameKey << "') could not be opened\n";
thereWasAnError = true;
}
// In case of any error, reset the key data
if (thereWasAnError) keyData.clear();
// Return the array with printable characters to the caller using Return Value Optimization / Copy Elision
return keyData;
}
// ----------------------------------------------------------------------------------------------------------------------------------
KeyData generateNewKeyFile(const std::string& printableCharacters) {
// Here we will store all key data
KeyData keyData{};
// Sanity Check. Do we have the expected number of printable characters
if (NumberOfUsedPrintableCharacters == printableCharacters.size()) {
// Open already now the output file, and check, if it could be opened.
if (std::ofstream keyDataFileStream{ fileNameKey }; keyDataFileStream) {
// Initialize random number generation. Lambda for generating randim values
std::random_device rd; //Will be used to obtain a seed for the random number engine
std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd()
std::uniform_int_distribution<size_t> distribution(0U, NumberOfUsedPrintableCharacters - 1);
auto randomCharacter = [&]() { return printableCharacters[distribution(gen)]; };
// We want to create a key string for all printable characters
for (size_t i{}; i < NumberOfUsedPrintableCharacters && keyDataFileStream; ++i) {
// Temporary for string with random chars
std::string key(NumberOfEncryptionCharacters, '\0');
// Genrate string with random content
std::generate_n(key.begin(), NumberOfEncryptionCharacters, randomCharacter);
// Write data to file and check for possible error
if (!(keyDataFileStream << key << "\n")) {
std::cerr << "\n\n*** Error: Could not write line to key file\n";
keyData = {}; break;
}
else {
// Store the next line in the file in in the internal vector
keyData.push_back(std::move(key));
}
}
if (keyData.size() != NumberOfUsedPrintableCharacters) std::cerr << "\n*** Geeneral error during creation of key file\n";
}
else {
// In case that we could not open the file, show error message
std::cerr << "\n\n*** Error: File with key data ('" << fileNameKey << "') could not be opened for writing\n";
}
}
return keyData;
}
// ----------------------------------------------------------------------------------------------------------------------------------
std::string encrypt(const std::string& toBeEncrypted, const KeyData& keyData, const std::string& printableCharacters, const std::string& outputFileName) {
// Here we will store the encrypted result
std::string encryptedString{};
// First open output file, if this does not work, no need to do something else
if (std::ofstream outFileStream{ outputFileName }; outFileStream) {
// Then make some sanity checks
if (((keyData.size() != NumberOfUsedPrintableCharacters)) || (printableCharacters.size() != NumberOfUsedPrintableCharacters)) {
std::cerr << "\n\n*** Error: Preconditions for encryption not met\n";
}
else {
// Go through all data in source string that shall be encrypter
for (const char c : toBeEncrypted) {
// Search for the character in our printable character string and check, if it is available and if it is in the key data
if (size_t index{ printableCharacters.find(c) }; (index != std::string::npos) && (index < NumberOfUsedPrintableCharacters)) {
// Prepare output
encryptedString += keyData[index] + "\n";
outFileStream << keyData[index] << "\n";
}
else {
std::cerr << "\n\n*** Error: Invalid character '" << c << "' in input string\n";
}
}
}
}
else {
// In case that we could not open the file, show error message
std::cerr << "\n\n*** Error: output file ('" << outputFileName << "') for encrypted data could not be opened\n";
}
return encryptedString;
}
// ----------------------------------------------------------------------------------------------------------------------------------
std::string decrypt(const KeyData& keyData, const std::string& printableCharacters, const std::string& inputFileName) {
// Here we will store the decrypted result
std::string decryptedString{};
// First open input file, if this does not work, no need to do something else
if (std::ifstream inFileStream{ inputFileName }; inFileStream) {
// Then make some sanity checks
if (((keyData.size() != NumberOfUsedPrintableCharacters)) || (printableCharacters.size() != NumberOfUsedPrintableCharacters)) {
std::cerr << "\n\n*** Error: Preconditions for encryption not met\n";
}
else {
// Read all lines of the file
for (std::string line{}; std::getline(inFileStream, line); ) {
// Search this line in the keydata
if (KeyData::const_iterator searchResult = std::find(keyData.begin(), keyData.end(), line); searchResult != keyData.cend()) {
// Calculate the distance
if (const int index{ std::distance(keyData.begin() , searchResult) }; index < printableCharacters.size()) {
decryptedString += printableCharacters[index];
}
else { // With all the checks, this error should not occur
std::cerr << "\n\n*** Unexpected error while decrypting data\n";
}
}
else {
std::cerr << "\n\n*** Error: Could not decrypt data\n";
}
}
}
}
else {
// In case that we could not open the file, show error message
std::cerr << "\n\n*** Error: input file ('" << inputFileName << "') with encrypted data could not be opened\n";
}
// Last sanity check
if (decryptedString.empty()) std::cerr << "\nGeneral Error during decryption\n";
return decryptedString;
}
int main() {
// First read the printable character string. Check, if that was successfull
if (std::string printableCharacters{ getPrintableCharacters() }; printableCharacters.length() == NumberOfUsedPrintableCharacters ||
(printableCharacters = generateNewPrintableCharactersFile()).size() == NumberOfUsedPrintableCharacters) {
// Next, try to read a KeyData file
if (KeyData keyData{ getKeyData() }; keyData.size() == NumberOfUsedPrintableCharacters ||
(keyData = generateNewKeyFile(printableCharacters)).size() == NumberOfUsedPrintableCharacters) {
// main programm loop
for (bool doRunMainLoop{ true }; doRunMainLoop; ) {
// Show menu to the user
std::cout << "\n\n\nPlease select, what to do. Press\n\n\t1\tfor encryption\n\t2\tfor decryption\n\t3\tto generate new key file\n\t4\tto exit\n\nSelection: ";
// Get input from user and check, if that worked
if (unsigned int selection{}; std::cin >> selection) {
switch (selection) {
case 1U:
// Encryption
std::cout << "\nEncryption\n\nEnter first a filename and then the word that you want to encrypt:\n";
if (std::string filename{}, word{}; std::cin >> filename >> word) {
std::cout << "\nEncrypted Data:\n\n" << encrypt(word, keyData, printableCharacters, filename) << "\n";
}
else std::cout << "\n\nError while reading your data. Please try again\n";
break;
case 2U:
// Decryption
std::cout << "\nDecryption\n\nEnter first a filename with the ecrypted information:\n";
if (std::string filename{}; std::cin >> filename) {
std::cout << "\nDecrypted Data:\n\n" << decrypt(keyData, printableCharacters, filename) << "\n";
}
else std::cout << "\n\nError while reading your data. Please try again\n";
break;
case 3U:
// Generate new keyfile
if ((keyData = generateNewKeyFile(printableCharacters)).size() != NumberOfUsedPrintableCharacters) {
std::cerr << "\n\n*** Error: New keyfile could not be generated. Exit\n";
doRunMainLoop = false;
}
break;
case 4U:
doRunMainLoop = false;
break;
default:
std::cout << "\nInvalid selection. Pleas try again\n";
} // End select
}
else std::cerr << "\n\n\nError: Unexpected error while reading from console\n";
} // End for
}
else std::cerr << "\n\n*** Error: Problem with key data generation\n";
}
else std::cerr << "\n\n*** Error: No printable character data present\n";
return 0;
}