Very interesting. Your program evolved over the time, you found some problems and patched them step by step. Sometimes eliminating bugs by making the same mistake twice.
Still, have some critical bugs that must been fixed. Those are mostly out of bound errors. Then there are style bugs, like global variables and the usage of wrong data types.
So, a big problem with the design of the software, that must be refactored
And last but not least, your idea how to encrypt will not work, because you store in the Key.txt" always the first letter, along with the key. In
sb = sb + (chars[(sb.length()) / 128]);
You always add the clear character followed by the 128 bytes of key. So the security of your approach is 0.
Then, next. You must understand indexes and boundaries of arrays. The array index starts at 0 and the last valid index is the size of the array minus 1. For an array { 1,2,3,4 } the size is 4, the first index is 0 and the last index is 3 !
You must always check that you are using valid indices. Example. In your statement:
chars[rand() % 95 + 1]
You create many out of bounds problems. rand can produce numbers betwenn 0 and very big. If the remainder of such a big number is 94, you add 1 to the index and get the invalid 95. And becuase, in the variable definition
char chars[95];
int runs = 0;
runs is defined after chars, you access the low byte of the varibale runs. You may test that. In the first creation of the key file, you will see many 0 characters. In the 2nd, when runs will be one, your create '\001' characters.
Best is to use modern C++ containers that help you with such checks.
Next, people who read you post cannot understand why you are doing certain things. So, we were wondering why you were writing substr(1,128) and then content = content.substr(130, content.size());. With that, you elimiated the first clear character, then read 128 bytes. And then you skipt the "\n", that you appended to your key file. All this should be refactored.
Another issue. For reading the your "chars.txt". You found out that you did not read the space ' ' character in your loop. But you did not check for the "skipws" attribute of the IO-stream, but patched that with adding a space as the last character. You could have used functionget or getline to overcome this problem.
Now to your question. In decrypt you used
for (int i = 0; i < 94; i++)
So, you read all butthe last line. End then you do
pass = pass.substr(129, pass.length());
129 is wrong. It must be 128. You stripped already away the first clear character and the "\n". Then it works, I tested it in the debugger.
However, all of this will not work, becuase you create huge encrypted output for small input strings. So for the 3 characters "abc" you will create 384 bytes of output. Who should type that in, and espcially with the wrongly create 0 characters. So, you cannot even test the code. You should store the encrytpted data int a file, and the, for decrytption, read it from the file again.
By the way, you are calling your function run recursively, which is wrong.
There are many more findings. Hence a recommend to refactor it. And please add tons of error checks. Especially for any IO-function. You must test that this worked.
If you have more questions, then please ask.
To show you, how to do error checking, I created a complete workable solution for you. Please see:
#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;
}