fstream is writing on file against an if else statement - c++

I have a file named "password.txt" which has 2 rows of usernames and passwords
2 string vectors named, user & password
This function uses a new username and password and checks if the username already exists in the user vector using a for loop
If it does exist, it breaks out of the loop
If it does not exist, it will add the username and password to the vectors and write them both on the file
void PasswordFile::addpw(string newuser, string newpassword) {
fstream file;
file.open("password.txt", ios::app);
//if the vectors is empty it automatically adds a new user
//is working
if (user.size() < 1){
user.push_back(newuser);
password.push_back(newpassword);
file << newuser << " " << newpassword << "\r";
}
//the loop (is not working)
for (int i=0; i < user.size(); i++){
string name = user[i];
if (name == newuser) {
break;
} //it shouldn't continue past this point if the name is in the vector but it does
else {
cout << newuser;
cout << " is being written onto the file\n";
user.push_back(newuser);
password.push_back(newpassword);
file << newuser << " " << newpassword << "\r";
}
}
file.close();
}
the problem is that it is writing the second name twice and the third name three times
I know also that it is pushing the newuser and newpassword multiple times onto the vector, but I can't figure out where or why its looping more than it should or why its ignoring the break and writing onto the file anyway
"password.txt" 1 run
dbotting 123qwe
egomez qwerty
tongyu liberty
tongyu liberty
"password.txt" second run
dbotting 123qwe
egomez qwerty
tongyu liberty
tongyu liberty
egomez qwerty
tongyu liberty
tongyu liberty
it keeps adding the last 2 every time its run

WHILE you are looping through the user vector, any time you encounter an entry that does not match the newuser being searched for, you are writing the newuser/newpassword to the file and pushing them into the vectors 1 (which then affects subsequent iterations of your loop), and then you move on to the next entry.
So, for example, if you start with a file like:
dbotting 123qwe
egomez qwerty
tongyu does not match dbotting, so you add tongyu. And then tongyu does not match egomez, so you add tongyu again. So you end up with:
dbotting 123qwe
egomez qwerty
tongyu liberty
tongyu liberty
And then on the next run, tongyu doesn't match dbotting or egomez, so you add tongyu twice again. But then it does match tongyu, so you don't add it again, and break the loop. So you end up with:
dbotting 123qwe
egomez qwerty
tongyu liberty
tongyu liberty
tongyu liberty
tongyu liberty
And so on.
Online Demo
To fix this, the code inside of your else block needs to be moved out of the for loop entirely. You need to search the entire vector to determine if the user already exists or not, and then write that user to the file only if they don't exist at all, eg:
void PasswordFile::addpw(const string &newuser, const string &newpassword) {
for (size_t i = 0; i < user.size(); ++i){
if (user[i] == newuser) {
cout << newuser << " already exists" << endl;
return;
}
}
/* alternatively:
if (find(user.begin(), user.end(), newuser) != user.end()) {
cout << newuser << " already exists" << endl;
return;
}
*/
ofstream file("password.txt", ios::app);
if (!file.is_open()) {
cout << "cannot open password file" << endl;
return;
}
cout << newuser << " is being written onto the file\n";
if (!(file << newuser << " " << newpassword << "\r")) {
cout << "cannot write to password file" << endl;
return;
}
user.push_back(newuser);
password.push_back(newpassword);
}
1: On a side note, you should not maintain two separate vectors that are closely related. Just use a single vector instead, eg:
struct UserInfo
{
string username;
string password;
};
std::vector<UserInfo> users;
void PasswordFile::addpw(const string &user, const string &password) {
for (size_t i = 0; i < users.size(); ++i){
if (users[i].username == user) {
cout << user << " already exists" << endl;
return;
}
}
/* alternatively:
if (find_if(users.begin(), users.end(), [&](const UserInfo &u){ return u.username == user; }) != users.end()) {
cout << user << " already exists" << endl;
return;
}
*/
ofstream file("password.txt", ios::app);
if (!file.is_open()) {
cout << "cannot open password file" << endl;
return;
}
cout << user << " is being written onto the file\n";
if (!(file << user << " " << password << "\r")) {
cout << "cannot write to password file" << endl;
return;
}
UserInfo newuser;
newuser.username = user;
newuser.password = password;
users.push_back(newuser);
}

Remy has done a good job of pointing out the problems you're seeing.
Rather than fixing your loop, I would fix the data structures you're using.
Instead of a pair of vectors, one for user names and one for passwords, I'd start by creating an std::map to hold the usernames and passwords:
std::map<std::string, std::string> credentials;
A map already provides most of what you want, as far as guaranteeing that an entry is unique (each username occurs only once), and lets you check whether an insertion succeeded, so an equivalent of your addpw comes down to something like this:
std::map<std::string, std::string> credentials;
void addpw(std::string newuser, std::string newpass) {
if (credentials.insert({newuser, newpass}).second) {
std::cout << "Adding new user: " << newuser << "\n";
file << newuser << " " << newpass << "\n";
} else {
std::cerr << "User already exists--not adding\n";
}
}
Of course, if you care about security at all, you really don't want to store user names and passwords in a plain text file. In fact, you usually don't want to store actual passwords at all--as a bare minimum, you want to salt the password, the hash it with a cryptographic hash algorithm (e.g., SHA-256) then store the salt and the hash, not the password itself. Then to check a user's credentials when they try to log in, you salt what they provide, hash it, and compare the resulting hash to the one you have on file and see if they match.
Now don't get me wrong: that's not the ideal to provide truly maximum security--but at least it's enough to make hackers work a little bit to steal your users' credentials.

Related

I'm comparing two variables with the same value but it says they're different

Well, I was asked to do a little job with SHA-256. I have to receive a user name and a password from the user (in console), change the password to a SHA-256 hash value and close the file (binary file). Then I have to read it again, getting its data and comparing it with new user inputs, checking if they are the same or not. A simple log-in system using SHA-256 hashing. The thing is, I write a random user name and password, but when I try to compare them later in the second step, it fails. The SHA-256 part comes straight out of the original code, it wasn't the point of this assignation.
I tried changing all my char arrays to strings, used, strcpy, strcpy_s and strncpy (just in case) and more, but it doesn't seem to work. Most of the codecomes directly from SHA-256 (my teacher sent it), but I'll put it here nonetheless
I'm putting the entire code in pastebin (it's kind of long): https://pastebin.com/W9jxsbK6
I don't know how to edit correctly in this text box, so please use the paste bin link.
struct Credentials {
char user[10];
char password[256];};
int main() {
Credentials c;
char user2[10];
char password2[256];
string test;
fstream file;
int opc;
do{
cout << "Menu:" << endl;
cout << "1.Create new user and password" << endl;
cout << "2.Validate user and password" << endl;
cin >> opc;
switch(opc){
case 1:
cout << "Type the user name" << endl;
cin >> user2;
strcpy_s(c.user, sizeof user2, user2);
cout << "Type the password" << endl;
cin >> password2;
test = SHA256::digestString(password2).toHex();
strcpy_s(c.password, sizeof test, test.c_str());
file.open("credentials.dat",ios::out|ios::binary);
if(!archivo){
cout<<"Error...\n";
return -1;
}
file.write((char*)&c,sizeof(c));
file.close();
break;
case 2:
cout << "Type user name" << endl;
cin >> user2;
cout << "Type password" << endl;
cin >> password2;
file.open("credentials.dat",ios::in|ios::binary);
if(!file){
cout<<"Error...\n";
return -1;
}
if(file.read((char*)&c,sizeof(Credentials))){
if(c.user == user2 && SHA256::digestString(password2).toHex() == c.password){
cout << endl << endl << "User validated" << endl;
}else{
cout << endl << endl << "Error" << endl;
}
}
}
} while (opc > 0 && opc < 3);
cin.ignore();
return 0;
}
if (c.user == user2)
Since user2 is a character array, and Credentials::user is also a character array, that line is not how you compare two character arrays. What that line does is compare two pointers, not contents of arrays.
The function to use is strcmp, or if you want to compare specifically N characters, the function is strncmp.
#include <cstring>
//...
if ( strcmp(c.user, user2) == 0)
{
// strings are equal
}
Now, if c.user and/or user2 were std::string, then using == to compare would have worked. That's why using std::string is much more intuitive than using char arrays in this respect -- operations such as == actually work as expected.

strcmp() function does not compare properly against user input and struct file data

I'm trying to solve this issue where when i try and search for a certain module name in my .dat file, it doesn't show the info of some module like module name, module code.
Example: if I search CSCI124, it shows all of the needed info i need in the output.
However if i try searching for CSCI114 or MATH121, it doesn't show any info except for "Subject Code not found.."
I have tried playing around with not putting in the while loop however it doesn't work as well.
It would be awesome if you guys could help me out, just started learning about c++
Subject subjectDB;
char subCode[MAX];
int printOnce = 0;
int position = 0;
cout << "Enter Subject Code: ";
cin >> subCode;
// Open binary file
ifstream fin("Subject.dat", ios::out | ios::binary);
if (!fin)
{
cout << "\nError opening database..\n"
<< "\tQuitting System..";
exit(-1);
}
cin.clear();
cin.ignore(100, '\n');
while(fin.read(reinterpret_cast<char*>(&subjectDB), sizeof(Subject)))
{
if (!(strcmp(subCode, subjectDB.subjectCode) == 0))
{
// Print this section once
if (printOnce == 0)
{
cout << "Subject Code not found..\n";
printOnce++;
}
}
else
{
// Print this section once
if (printOnce == 0)
{
cout << "\nSubject Code: "
<< subjectDB.subjectCode
<< "\nSubject Name: "
<< subjectDB.subjectName
<< "\n"
<< endl;
cout << "Task\t"
<< "Title\t\t"
<< "Weight\t"
<< "Upon\t"
<< "Mark\t"
<< "Obtained\n";
// PrintOnce++ : 1 != 0
// So it only prints once
printOnce++;
}
cout << position + 1
<< "\t"
<< subjectDB.assessment[position].title
<< "\t"
<< subjectDB.assessment[position].weight
<< "\t"
<< subjectDB.assessment[position].upon
<< "\t"
<< subjectDB.assessment[position].taskMark
<< "\t"
<< "testing\n";
position++;
}
}
Writing a struct to a file or reading a struct from a file is totally non-portable. Compile the same code on two different compilers, or on the same compiler with different settings, and what you wrote and what you read might be very different. That's not necessarily your problem, but it will be your problem one day.
Your display code logic is flawed.
Explanation:
If the subject code is in the first record you read, you'll get the correct answer. However as soon as your fist record does not match, the "Not found" message is displayed, printOnce is incremented. If the matching subject coe is found later in the file printOnce is no longer 0 and it' will not be displayed.
Solution:
Organise your loop in the opposite way:
while (...) {
if (strcmp(..)==0) {
// your code to display the found item here
printOnce++;
break; // ?? optional: you could stop the loop at first found occurence unless you suppose there could be duplicates
}
}
and once the loop is finished, check OUTSIDE OF THE LOOP if you've found something:
if (printOnce==0) { // nothing was found in the loop
// display that nothing was found !
}
Remark:
Reading directly from file as you do has limitations. It can only work with plain old data (POD), not with more advanced types using string members or containters. For learning it's a good start but I'd suggest to foresee a Subject member function that loads the data from a stream. This proves to be more flexible when your data structure evolves: the member function could ealsily read each member data and sub-objects using the most apporpriate way.
Thanks for your help! I managed to get the function to compare properly against user input and struct file by creating a function that checks the .dat file if the subject is existing or not.
int row = checkNumberOfData(fileName);
if (row > 0)
exist = doesSubjectExist(file, fileName, code); // checks input
if (!exist)
{
file.open(fileName, ios::out | ios::app | ios::binary);
if (!file)
{
cout << "Error opening database..\n"
<< "\tQuitting System ..\n";
exit(-1);
}
strcpy(subjectDB.code, code);
cout << "Subject Name: ";
cin.getline(subjectDB.name, MAX);
cout << "No of assessment tasks: ";
cin >> subjectDB.num;
cout << endl;
.... etc
}

File IO - delete a line of text that contains a specific string C++

I've written a function that reads in a text file, allows the user to pick an account to delete and deletes the specified account. What I need to do now is delete the line of text from the text file that lists the accounts on it. What good does it do to delete an account, but still have it show up in the list of accounts, right? Below is my code. What happens is that the entire contents of the account list text file get deleted, not just the line with the specific account number. The actual deletion of the account's text file works fine, it's just the deletion of that one line of text that is giving me trouble. Thanks for any assistance!
void UserInfo::deleteAccount() {
vector<string> accounts;
string line;
char answer;
ifstream acctList("accountList.txt");
if (acctList.fail()) {
cout << "There is a problem opening the file.\n";
exit(1);
}
//populate vector with the list of accounts and display them.
while (getline(acctList, line)) {
accounts.push_back(line);
}
for (unsigned int i = 0; i < (accounts.size()); i++) {
cout << accounts[i] << endl;
}
cout << "\nEnter the account number of the account you would like to delete: ";
cin >> acctNo;
cout << "Are you sure you want to delete account number " << acctNo << "? ";
cin >> answer;
const char * result = (acctNo + ".txt").c_str(); //convert the selection choice to a c-string
if (answer == 'y' || answer == 'Y') {
if (remove(result) != 0)
cout << "Unable to delete account." << endl;
else
cout << "\nThe account has been deleted successfully." << endl;
//delete the account name and number from the list of accounts
//temporary file to store the new list of accounts
ofstream out("newAcctList.txt", ios::app);
while (getline(acctList, line)) {
if (line != acctNo)
out << line << "\n";
acctList.close();
out.close();
// delete the original file
}
if(out){
remove("accountList.txt");
// rename old to new
rename("newAcctList.txt", "accountList.txt");
} else {
cout << "Error on output" << endl;
}
}//end if
You've already read acctList in the first while loop, so when you try to read it again to write out, it's already at the end. You can just iterate over the vector accounts - throwing away the entry matching acctNo (which should be a local variable BTW) - instead of re-reading the file.

Need help deleting loaded array and clearing it from count

I have been messing around with some code. I've really just mashed things together and exploring ways to get what I want out of it.
Right now something that has been bothering me is that when I try to delete a user it leaves the user blank instead of deleting it completely. I would rather it delete/remove the line completely and adjust the count without removing any other user data.
EDIT:
The problem is that after I delete a user. When I list every user it displays a blank User: pass: etc instead of not listing those to begin with. if I were to save the file it would have 5 empty spaces. I'd rather it remove those lines completely as if they were never there to begin with.
This is how I load the code:
int Loadpdata()
{
ifstream fp_in;
if(count > 0)
{
cout << "pdata already Loaded.\nTotal of " << count << " users loaded." << endl;
return 0;
}
fp_in.open("p.data"); //Open user file
if(fp_in == NULL)
{
cout << "Could not open user file, exiting." << endl;
}
while(!fp_in.eof()) { //While there is something to read
getline(fp_in,userd[count]);
getline(fp_in,passd[count]); //Read a line from the file
getline(fp_in,aged[count]); //Read a line from the file
getline(fp_in,locationd[count]); //Read a line from the file
getline(fp_in,emaild[count]); //Read a line from the file
getline(fp_in,mid[count]); //Read a line from the file
cout << "User: " << userd[count] << " Loaded Successfully." << endl;
userstotal++;
count++;
}
fp_in.close(); //Done with the user file
cout << "Total Users Loaded: " << count << endl;
if(!count > 0)
{
cout << "Userlist is empty, exiting" << endl;
return -2;
}
return 0;
}
Now this is how I delete the user:
int Deletedata()
{
char user[80];
int logged_in=0;
while(!logged_in) { //Force user to login. Give a quit option if you'd like
cout << "Enter user name: ";
cin >> user;
int found = 0;
for(int x=0;x<count && !found;x++) { //For each user/password pair..
if(userd[x].compare(user) == 0) { //Matched the username
found = 1;
logged_in = 1;
userd[x].clear();
passd[x].clear();
aged[x].clear();
locationd[x].clear();
emaild[x].clear();
mid[x].clear();
}
}
if(!found) {
cout << "Invalid username!" << endl;
}
}
//Once we're done with that loop, they logged in successfully.
cout << "Deleted " << user << " Successfully." << endl;
return 0;
}
The more I think about this the more I realize I may have to scrap it and come up with a new format.
If you want to just remove one entry and not touch any of the other entries, you'll need to use a node-based container, e.g., a std::list<T> or a std::map<K, V> (the latter would match your needs of locating an object by a key anyway).
That said, if you want to stick with an array and fill the gap, you have two principle options:
If you don't care about the order of the objects, you could just std::swap() the to-be-removed object with the last copy and then remove the newly last element.
If the order matters to you, you'll need to move all the objects after the gap forward. The easiest way to do that is probably to use std::remove_if().
BTW, trying to read a file and checking in.eof() does not work reliable. In particular, when there are no more objects eof() may or may not be true and you don't check after reading whether the read was successful. Also note that eof() may never become true, e.g., if the file contains a format error and gets into fail state. You should read you data using something like
while (std::getline(in, userd[count])
&& std::getline(in, passd[count])
// ...
&& std::getline(in, mid)) {
++count;
}
Instead of clearing the element at position x, use erase on the vector (must be std::vector, and not C-array)

Reading in Encrypted Username/Password

I am in the process of developing a console application that acts as a Diary. At this stage I am developing the login authentication and have hit a bit of a wall! As I will be dealing with text files for both my login and diary storage, I would like to encrypt these text files from prying eyes.
Now, the problem is I do not know how to go about the decrypt >> check user&&pass >> encrypt again.
Would it be along these lines?:
Program Loads
Decrypt passwords.txt
If at any point the program closes, encryptFile() is ran.
Validate user entry
Encrypt passwords.txt
If I am along the right lines how do I go about implementing this? I searched for encryption tutorials for text files using c++ and they were not very helpful.
Here is my butchered beginner password.txt code, where shall I go from here? If there is an encryption tutorial/article you recommend that I missed please post it!
void checkPasswordFile() {
string username;
string password;
string passwordAgain;
string userIn;
string passIn;
string line;
ifstream passwordFile("passwords.txt");
istringstream instream;
if (passwordFile.good()) {
cout << "\t==================================" << endl;
cout << "\t----------------------------------" << endl;
cout << "\tYou are a returning user, please fill in your details" << endl;
while(!passwordFile.eof()) {
getline(passwordFile, line);
instream.clear();
instream.str(line);
username = line;
getline(passwordFile, line);
instream.clear();
instream.str(line);
password = line;
}
do {
cout << "Username: " << endl;
cin >> userIn;
cout << "Password: " << endl;
cin >> passIn;
if (userIn == username && passIn == password) {
displayMenu();
} else {
cout << "Username and Password Do Not Match, Try Again" << endl;
}
} while(userIn != username && passIn != password);
} else {
cout << "file no exist";
ofstream passwordFile;
passwordFile.open ("passwords.txt", ios:: in | ios::app);
cout << "\t==================================" << endl;
cout << "\t----------------------------------" << endl;
cout << "\tThis is your first run, please enter a username and password" << endl;
cout << "\tUsername: " << endl;
cin >> username;
cout << "\tPassword: " << endl;
cin >> password;
/*
Do Loop:
Prompts Re-Entry if PasswordAgain is not equal to Password
*/
do {
cout << "Re-Type Password: ";
cin >> passwordAgain;
if(password != passwordAgain) {
cout << "Passwords Do Not Match! Try Again" << endl;
}
} while(password != passwordAgain);
passwordFile << username << "\n";
passwordFile << password;
}
}
Thank you very much for your time.
p.s for the life of me I cannot find out how to do:
Username:[cin>>username] on the same console line, sorry for doubling up but didn't deem it a big enough question for its own post! Thanks.
EDIT:
I have succesfully been able to decrypt the username and pass when created and stored in the text file. Then when the user comes back, what they entered is encrypted and compared with the file.
Problem being this only works for short words, user pass works, but username and password does not... any ideas why? Here is my encryption code:
char encryptKey = 'h';
cout << "\tUsername: ";
cin >> userIn;
cout << "\tPassword: ";
cin >> passIn;
for (int i = 0; i < userIn.size(); i++) {
userIn[i] ^= encryptKey;
}
for (int x = 0; x < passIn.size(); x++) {
passIn[x] ^= encryptKey;
}
if (userIn == username && passIn == password) {
displayMenu();
} else {
cout << "\tUsername and Password Do Not Match, Try Again" << endl;
}
The right thing to is not to encrypt the passwords file - the issue is that the encryption key for the file would need to be stored somewhere that the program can access it, which would make it relatively easy to find and abuse.
Instead, you should be using password hashing (using a strong hash algorithm like SHA1). A hash algorithm is a algorithm that deterministically maps a piece of text onto a large number (called its hash), and is designed to be irreversible without great effort. The basic concept is that you take the password, use it to compute its hash, and then store that hash. Later, when the user enters the password to log in, you compute its hash again and compare the resulting hash to the stored hash. Even if someone gains access to the hashes, they do not obtain the password, which is important, because people often share passwords between applications. Don't implement your own SHA1 hash - see "What is the best encryption library in C/C++?" for a list of libraries.
You must also use salting and key stretching to defend against common brute force attacks.