Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- * A simple database of user accounts.
- *
- * Author: Adam Gasior
- */
- #include <cassert>
- #include <cstdio>
- #include <cstdlib>
- #include <fstream>
- #include <string>
- #include <unordered_map>
- #include <vector>
- // Utility functions.
- /**
- * \brief Generates a hash for a given string.
- *
- * Takes each character of a given string and performs series of bitwise operations
- * on it and large prime numbers resulting in a huge number. Its purpose is to
- * represent any string of characters as a unique number, however collisions are
- * inevitable.
- *
- * \param str A string to hash.
- *
- * \return The resulting hash.
- */
- long long hash(std::string str)
- {
- // Taking some not too big prime.
- long long hash = 37;
- // XOR-ing each character's multiply by a large prime with our previous prime's
- // multiply by another large prime.
- for (char& c : str)
- {
- hash = (hash * 54059) ^ (c * 76963);
- }
- return hash;
- }
- /**
- * \brief Replaces characters of a string from one to other set of characters.
- *
- * Takes a string and based on a character's index in one character array replaces it with
- * a character of the same index from the other array. Used for keyword encryption.
- *
- * \param str The string to encrypt.
- * \param chars Set of characters from which index of <code>str</code>'s character is taken.
- * \param sybst Set of characters with which the characters of <code>str</code> are replaced.
- *
- * \return Encrypted version of <code>str</code>.
- */
- std::string replace(std::string str, std::string chars, std::string subst)
- {
- // These two strings must be of equal length.
- assert(chars.length() == subst.length());
- // Iterating through str and chars to find the index of a str's character in chars.
- for (char& c : str) for (int i = 0; i < chars.length(); ++i)
- if (c == chars[i])
- {
- c = subst[i]; // If the character is found in chars then a str's character is
- break; // replaced with its corresponding version in subst.
- }
- return str;
- }
- /**
- * \brief Generates a random string of a given length, used as salt.
- *
- * \param length Desired length of the random string.
- *
- * \return A random string.
- */
- std::string randstr(size_t length)
- {
- // Initialising an empty string.
- std::string res = "";
- // Generating specified number of characters.
- for (size_t i = 0; i < length; ++i)
- {
- res += [](char c) -> char {
- return (c != ',' && c != '\n' ? c : c + 1); // Making sure that the resulting string
- } // doesn't contain a comma or a new line
- ((char) rand() % 255 + 1); // character.
- }
- return res;
- }
- /**
- * \brief Splits a string into an array of subsequent its parts.
- *
- * Takes a string and every time it encounters a delimiting character it adds a substring
- * between the previous and the current delimiter to an array. After the string ends, it
- * returns the array of segments without the delimiters.
- *
- * \param str The string to split.
- * \param delim Delimiter.
- *
- * \return The array of string segments.
- */
- std::vector<std::string> split(std::string str, char delim)
- {
- // Creating the array of segments.
- std::vector<std::string> segms;
- // Initialising the auxiliary string storing current str's segment.
- std::string segm = "";
- // Iterating through str.
- for (char c : str)
- {
- // If a delimiter is encountered, adding the segment to the array and
- // clearing it.
- if (c == delim)
- {
- segms.push_back(segm);
- segm = "";
- }
- else segm += c; // If not a delimiter, then it can be appended to the segment.
- }
- // After iterating the last resulting segment can be added to the array,
- // given it is not empty.
- if (segm != "") segms.push_back(segm);
- return segms;
- }
- // Structures.
- /**
- * Data of a user account.
- */
- struct UserAccount
- {
- public:
- std::string username_enc; ///< Encrypted username.
- std::string salt_enc; ///< Encrypted salt.
- long long hashed_pword; ///< Hash of salted password.
- std::string name_enc; ///< Encrypted first name.
- std::string surname_enc; ///< Encrypted surname.
- std::string age_enc; ///< Encrypted age.
- std::string favFood_enc; ///< Encrypted favourite food.
- public:
- /**
- * \brief Creates a user account.
- *
- * Encrypts user account data, salts and hashes the password and construct a user
- * account object.
- *
- * \param username Username.
- * \param password Password that will be salted and hashed.
- * \param name First name.
- * \param surname Surname.
- * \param age Age.
- * \param favFood Favourite food.
- */
- UserAccount(std::string username = "",
- std::string password = "",
- std::string name = "",
- std::string surname = "",
- std::string age = "",
- std::string favFood = "")
- {
- this->username_enc = encrypt(username); // Encrypting the username.
- this->salt_enc = randstr(rand() % 50 + 50); // Generating the salt.
- this->hashed_pword = hash(this->salt_enc + password); // Salting and hashing the password.
- this->name_enc = encrypt(name); // Encrypting the first name.
- this->surname_enc = encrypt(surname); // Encrypting the surname.
- this->age_enc = encrypt(age); // Encrypting the age.
- this->favFood_enc = encrypt(favFood); // Encrypting favourite food.
- this->salt_enc = encrypt(this->salt_enc); // Encrypting the salt.
- }
- /**
- * \brief Represents the account as a string containing the encrypted data. Used to save the
- * \brief data into a file.
- *
- * \returns The string representation of the account.
- */
- std::string to_string() const
- {
- return username_enc + "," + salt_enc + "," + std::to_string(hashed_pword) + "," + name_enc
- + "," + surname_enc + "," + age_enc + "," + favFood_enc;
- }
- /**
- * \brief Parses a string to initialise data of the account.
- *
- * \param str The string to parse.
- */
- void parse_string(std::string str)
- {
- // Splitting the array into legible data.
- auto entries = split(str, ',');
- // There must be exactly 7 entries as there are 7 fields to initialise.
- assert(entries.size() == 7);
- username_enc = entries[0]; // Assigning the encrypted username.
- salt_enc = entries[1]; // Assigning the encrypted salt.
- hashed_pword = atoll(entries[2].c_str()); // Assigning the hash of salted pasword.
- name_enc = entries[3]; // Assigning the encrypted first name.
- surname_enc = entries[4]; // Assigning the encrypted surname.
- age_enc = entries[5]; // Assigning the age.
- favFood_enc = entries[6]; // Assigning the encrypted favourite food.
- }
- private:
- /**
- * \brief Encrypts a string using keyword encryption. It's hidden for safety reasons.
- *
- * \return The encrypted string.
- */
- std::string encrypt(std::string str)
- {
- // Setting up the available characters.
- std::string chars = "0123456789abcdefghijklmnopqrtsuvwxyz";
- // Setting up the keyword mixed into chars.
- std::string subst = "rocksmith2014356789abdefgjlnpquvwxtz";
- // Encrypting.
- return replace(str, chars, subst);
- }
- };
- /**
- * Database storing data of all users.
- */
- struct UserDatabase
- {
- private:
- std::unordered_map<std::string, UserAccount> users; ///< Hash map of all users.
- public:
- /**
- * \brief Creates a user account and adds it to the database.
- *
- * \param username Username.
- * \param password Password that will be salted and hashed.
- * \param name First name.
- * \param surname Surname.
- * \param age Age.
- * \param favFood Favourite food.
- */
- void add_user(std::string username,
- std::string password,
- std::string name,
- std::string surname,
- std::string age,
- std::string favFood)
- {
- users[encrypt(username)] = UserAccount(username, password, name, surname, age, favFood);
- }
- /**
- * \brief Logs into a user account with matching username and password.
- *
- * If the usernam and password are found in the database then it prints the user data to the console.
- *
- * \param username Username.
- * \param password Password.
- */
- void login(std::string username, std::string password)
- {
- // Checking if the username is found.
- if (users.find(encrypt(username)) == users.end())
- {
- puts("Invalid login or password."); // Printing an ambiguous message to make it
- return; // harder to hack into an account.
- }
- auto user = users[encrypt(username)];
- long long hashed_pword = hash(decrypt(user.salt_enc) + password);
- // Checking if password is correct.
- if (hashed_pword != user.hashed_pword)
- {
- puts("Invalid login or password."); // Printing an ambiguous message to make it
- return; // harder to hack into an account.
- }
- // Now print the greeting message.
- puts("-----------------------");
- puts(("Hello, " + decrypt(user.name_enc) + " " + decrypt(user.surname_enc) + "!").c_str());
- puts(("You are " + decrypt(user.age_enc) + " years old.").c_str());
- puts(("Your favourite food is " + decrypt(user.favFood_enc) + ".").c_str());
- }
- /**
- * \brief Loads the database from a text file.
- *
- * \param file The input file stream from which the database will be read.
- */
- void load(std::ifstream& file)
- {
- std::string line;
- while (std::getline(file, line))
- {
- UserAccount acc = UserAccount();
- acc.parse_string(line);
- users[acc.username_enc] = acc;
- }
- }
- /**
- * \brief Saves the database to a text file.
- *
- * \param file The output file stream to which the database will be saved.
- */
- void save(std::ofstream& file)
- {
- for (auto& user : users)
- {
- file << user.second.to_string() << '\n';
- }
- }
- private:
- /**
- * \brief Encrypts a string using keyword encryption. It's hidden for safety reasons.
- *
- * \return The encrypted string.
- */
- std::string encrypt(std::string str)
- {
- // Setting up the available characters.
- std::string chars = "0123456789abcdefghijklmnopqrtsuvwxyz";
- // Setting up the keyword mixed into chars.
- std::string subst = "rocksmith2014356789abdefgjlnpquvwxtz";
- // Encrypting.
- return replace(str, chars, subst);
- }
- /**
- * \brief Decrypts a string using keyword decryption. It's hidden for safety reasons.
- *
- * \return The decrypted string.
- */
- std::string decrypt(std::string str)
- {
- // Setting up the keyword mixed into chars.
- std::string chars = "rocksmith2014356789abdefgjlnpquvwxtz";
- // Setting up the available characters.
- std::string subst = "0123456789abcdefghijklmnopqrtsuvwxyz";
- // Decrypting.
- return replace(str, chars, subst);
- }
- };
- // User interface functions.
- /**
- * \brief Displays a list of options a user can choose from.
- */
- void display_menu();
- /**
- * \brief Asks the user for the option number.
- */
- unsigned int read_option();
- /**
- * \brief Reads an unsigned integer and handles all input errors.
- */
- unsigned int safe_read_int();
- /**
- * \brief Performs task assigned to an option chosen by the user.
- *
- * \param option Number of the option assigned to a task.
- * \param database Reference to a database to perform a task on.
- *
- * \return <code>true</code> if the program has to be running after the task is completed,
- * <code>false</code> otherwise.
- */
- bool perform_task(unsigned int option, UserDatabase& database);
- /**
- * \brief Asks the user for login detais and gives an access to their account.
- *
- * \param database Reference to a database to perform a task on.
- */
- void login(UserDatabase& database);
- /**
- * \brief Asks the user for login details and account data to create them an account.
- *
- * \param database Reference to a database to perform a task on.
- */
- void add_user(UserDatabase& database);
- /**
- * \brief The program's main function.
- *
- * Initialises the user database and runs the program's main loop.
- */
- int main()
- {
- // Setting the random number generator's seed to current time.
- srand(time(0));
- // Creating the database.
- auto ub = UserDatabase();
- // Loading data.
- std::ifstream file("data.txt");
- if (file.good()) ub.load(file);
- // Main loop.
- while (true)
- {
- // Displaying menu.
- display_menu();
- // Reading option number.
- unsigned int opt = read_option();
- // Performing a task and exiting if told to do so.
- if (!perform_task(opt, ub)) break;
- }
- return 0;
- }
- void display_menu()
- {
- puts("-----------------------");
- puts("What do you want to do?");
- puts("0. Exit.");
- puts("1. Login.");
- puts("2. Register.");
- }
- unsigned int read_option()
- {
- return safe_read_int();
- }
- unsigned int safe_read_int()
- {
- // Setting a negative number to keep the loop going until correct data is entered.
- int num = -1;
- while (num < 0)
- {
- // Printing prompt.
- printf("~> ");
- // Reading the value.
- scanf(" %d", &num);
- // If the input is incorrect, the buffer needs to be cleared.
- char c;
- while ((c = getchar()) != '\n' && c != EOF) {}
- }
- return (unsigned int) num;
- }
- bool perform_task(unsigned int option, UserDatabase& database)
- {
- switch (option)
- {
- case 0: // Saving the database to a text file and exiting.
- {
- std::ofstream file("data.txt");
- file.clear();
- database.save(file);
- file.close();
- return false;
- }
- case 1: // Logging in.
- login(database);
- break;
- case 2: // Registering a new user.
- add_user(database);
- break;
- default:
- puts("Unknown option, please enter it again.");
- break;
- }
- return true;
- }
- void login(UserDatabase& database)
- {
- // Setting up username and password buffers.
- char username[51];
- char password[51];
- // Reading username.
- puts("Please enter your username.");
- printf("~> ");
- scanf(" %s", username);
- // Reading password.
- puts("Please enter your password.");
- printf("~> ");
- scanf(" %s", password);
- // Logging in.
- database.login(username, password);
- }
- void add_user(UserDatabase& database)
- {
- // Setting up buffers for login and account data.
- char username[51];
- char password[51];
- char name[51];
- char surname[51];
- std::string age;
- char favFood[51];
- // Reading username.
- puts("Please enter your username.");
- printf("~> ");
- scanf(" %s", username);
- // Reading password.
- puts("Please enter your password.");
- printf("~> ");
- scanf(" %s", password);
- // Reading first name.
- puts("Please enter your first name.");
- printf("~> ");
- scanf(" %s", name);
- // Reading last name.
- puts("Please enter your last name.");
- printf("~> ");
- scanf(" %s", surname);
- // Reading age.
- puts("Please enter your age.");
- age = std::to_string(safe_read_int());
- // Reading favourite food.
- puts("Please enter your favourite food.");
- printf("~> ");
- scanf(" %s", favFood);
- // Registering a new user.
- database.add_user(username, password, name, surname, age, favFood);
- }
Add Comment
Please, Sign In to add comment