Finding the Index of a String in a Vector

Viewed 1092

I am in the process of porting my "encryptor/decryptor" program, which uses a key (like this one) to flip-flop text. The encryption part is done, now I am working on the decryptor which:

  1. Looks for a 128 character string in an array
  2. Gets the index of that string
  3. Gets the character from chars[index_of_long_string]
  4. Concatenates that character to a string pass


With some googling, I was able to find out that I would be better off using vectors in C++. I wrote the following code to form a vector from my array:

vector<string> arrayAllReplaceDecryptVector(std::begin(arrayAllReplaceDecrypt), end(arrayAllReplaceDecrypt));

Then use this code to; get the index of the key_string, concatenate the string with chars[index], then delete the first 128 characters of the key_string:

size_t index = std::distance(arrayAllReplaceDecryptVector.begin(), find(arrayAllReplaceDecryptVector.begin(), arrayAllReplaceDecryptVector.end(), pass.substr(0,128)));
        output = output + chars[index];
        pass = pass.substr(128, pass.length());

The image below is the current failure at line 127, where the cin >> pass gets cut off when passed to the function. This seems to happen at the first occurrence of a space character in the input. This yields a 95 from the index code above. The input shown is "Hello" after being encrypted, and the index numbers should be 14, 9, 22, 22, then 28. The final output should be "Hello," again.

enter image description here

This code will reproduce the error (however note you will need these two .txt files in the same directory as the compiled code to run the program - [1], [2]):

#include <iostream>
#include <fstream>
#include <vector>
#include <stdlib.h>
#include <time.h>
#include <string>

using namespace std;

void fillChars(char(&chars)[95]);
string decrypt(char(chars)[95], string pass, string& lastJob);

char chars[95];
int runs = 0, lastJobID = 0;
bool keyCreated = false;
bool charsFilled = false;

int main()
{
    string pass, lastJob;
    //One time fills the character array
    if (charsFilled == false)
    {
        fillChars(chars);
    }
    cout << "\nEnter a message to decrypt:\n";
    cin >> pass;
    lastJob = decrypt(chars, pass, lastJob);
    lastJobID = 2;
}
void fillChars(char(&chars)[95])
{
    //Opens chars.txt
    ifstream characters("chars.txt");
    for (int i = 0; i <= 94; i++)
    {
        if (i == 94)
        {
            chars[i] = ' ';
        }
        else
        {
            //Puts characters into array
            characters >> chars[i];
        }
    }
    characters.close();
}

string arrayAllReplaceDecrypt[95];
string decrypt(char(chars)[95], string pass, string& lastJob)
{
    cout << "\n" << pass << "\n";
    //Encrypted string
    string output;
    //Opens key
    ifstream ifs("Key.txt");
    //Reads entire key to string
    string content((std::istreambuf_iterator<char>(ifs)),
        (std::istreambuf_iterator<char>()));
    //Loops to add chars to
    for (int i = 0; i < 94; i++)
    {
        //Adds 128 chars to replace array
        arrayAllReplaceDecrypt[i] = content.substr(1, 128);
        try
        {
            //Deletes old chars
            content = content.substr(130, content.size());
        }
        catch (const exception& e)
        {
            //content.clear();
        }
    }
    ifs.close();
    vector<string> arrayAllReplaceDecryptVector(std::begin(arrayAllReplaceDecrypt), end(arrayAllReplaceDecrypt));
    while (pass.length() > 0)
    {
        size_t index = std::distance(arrayAllReplaceDecryptVector.begin(), find(arrayAllReplaceDecryptVector.begin(), arrayAllReplaceDecryptVector.end(), pass.substr(0, 128)));
        cout << "\n" << index << "\n";
        output = output + chars[index];
        pass = pass.substr(129, (pass.length() - 128));
    }
    return(output);
}
1 Answers

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;
}
Related