Last active
March 21, 2018 17:29
-
-
Save Adanos020/b53c42825ddd5d25e6661fddedc03b55 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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. | |
* | |
* \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. | |
* | |
* \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); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment