You are here:

C++/C++ Password & ID

Advertisement


Question
This is the basic's of password programming. What I would like to know is how to make it to the next level. I DO NOT want the password to premade in the program already. I would like the program to store the USER'S password the first time around and search for it the second time to find the match. I think it might have to deal more with Arrays than I/O's, but I am not sure.

#include <iostream>
#include <string>
using namespace std;

int main()
{
   string password, id;

   cout << "Please Enter ID: ";
   cin >> id;
   cout << "Please Enter Password: ";
   cin >> password;

         
         /* I want the user to create a password of their own*/
   if (id == "apples" && password == "google")
   {
       cout << "That is correct\n";
   }
   else
   {
       cout << "ERROR... Incorrect Password\n";
   }

   return 0;
}

Answer
I presume this is a practice exercise of some kind - for coursework maybe - as no one in their right minds should be storing passwords in plain text -- or hard coding them as plain text in code for that matter as literal values are present in compiled executable code.

(it is outside the scope of the answer to go into the subject of protecting passwords but there are probably some decent articles somewhere on the WWW - I found these from a quick search - https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet and https://crackstation.net/hashing-security.htm which might some use if you are interested in the subject)

What I will do is run through a process of arranging code to do what you wish in a way where storage can be done eventually but will be almost an anti climax when it is added - hopefully! I *will* be using fairly up to date C++ language and library facilities which you may not have come across before. This is because I do not want to spend all day on this by grunging around by using C++ like C that you are quite possibly being taught (assuming you are a student). I leave it as an exercise for you to lookup the details on language and library features I use in your own time.

So what you need to do is lookup the password _associated_ with the user's id - note the stress on associated. This would indicate that some form associative collection - or container - of user ids and passwords would be useful - one that maps passwords to user ids. Guess what - the C++ standard library has such an associative container type - called map - or more precisely std::map - also more precisely it is a class template as it is a generic type template from which map types between various pairs of type values can created. In this case we require a map between strings - we wish to map the id string to the associated password string. To use the std::map class template we have to include the map header:

#include <iostream>
#include <string>
#include <map>

Using (!!) a modern C++11 feature of the using keyword we can create an alias name for the map type we require:

using user_credentials_map = std::map<std::string, std::string>;

(note: I tend not to use using namespace::std anymore and just type the std:: prefix to the names of C++ library entities as it makes it clear I mean the standard library variants as names like string and map are very common)

Then we can create a map of known credentials and add in the one set of credentials you have so far:

int main()
{
   user_credentials_map    known_credentials;
   known_credentials["apples"] = "google";

Now you can lookup the password for the entered ID also using the [] idiom:

   if (known_credentials[id] == password)

But this has the problem that if the ID is not already known the code behind the [] operator 'helpfully' creates a map entry for us using an empty password string as the mapped value (as there is no other value it could use). This is not useful in this case as it mean all unknown users will suddenly become users with empty passwords - that is they can from then on pass authentication by just hitting enter when prompted for a password - oops.

To prevent this we have to ask the map to find the value we want because in this case what is returned is a something that refers to the pair of objects found - the id key and the password value. This returned thing is called an iterator and is sort of like a position into the map where the information was found. The cool thing is that if the id key requested is not found then an iterator 'position' indicating the end of the map is returned (which in fact represents one-past-the-end) and map objects have a member function called end that also returns this value so we can write:

   auto credentials_position = known_credentials.find(id);
   if (    credentials_position!=known_credentials.end()
       and credentials_position->second == password
      )

First we try to find the position for the known credentials for the value of the entered user id.
If the returned position is not (past) the end of the known credentials - indicating the id has a known associated password - and that password matches the one the user entered then it is a correct match, otherwise either the user is unknown or the password is incorrect. The auto keyword in C++11 and later means "hey compiler! You know what the type is here just fill it in for me so I don't need to spell it out - as in this case the type is quite long and tedious). The arrow operator -> used to access second after the 'and' clause in the if condition expression is provided by iterators to access the members of the things at the position they represent. In this case that thing is a pair (see std::pair) in which the first value is the id key and the second is the password value.

So now you can easily add more known user ids and passwords:

   user_credentials_map    known_credentials;
   known_credentials["apples"] = "google";
   known_credentials["pears"] = "yahoo";
         .
         .
         .

But they are still hard coded in your code. Before getting to how to store and retrieve the values I will first move the naked known_credentials map into a user defined type specifically for the purpose of working with user id, password credentials:

class user_credentials
{
public:
   void set(std::string const & id, std::string const & password)
   {
       credentials[id] = password;
   }
   
   bool is_match(std::string const & id, std::string const & password) const
   {
       auto credentials_position = credentials.find(id);
       return  credentials_position!=credentials.end()
         and credentials_position->second == password;
   }

private:
   using user_credentials_map = std::map<std::string, std::string>;
   user_credentials_map    credentials;
};

The class only contains a credentials map data member - which is private - i.e. internal to the class and class instances, and two public member functions that provide support for the two things we were doing: setting credentials and checking to see if an id and password are a valid match. The use of const indicates things we do not intend to modify - on the end of the is_match function declaration it indicates we are not going to modify the data of any objects of type user_credentials - which means we are not going to change the credentials map. The & indicates 'pass by reference' as opposed to 'pass by value' - known terms you can look up - you can also look up C++ reference types.

It is used like so:

int main()
{
   user_credentials    known_credentials;
   known_credentials.set("apples", "google");
   known_credentials.set("pears", "yahoo");
         .
         .
         .

   if ( known_credentials.is_match(id, password) )

Which I think you'll agree is a bit tidier - especially for the match checking.

Now we have the credentials handled by their own type we can add further functionality to it. For example we can add operator<< and operator>> so we can stream the id, password to and from I/O - console, files etc.

Now for various technical reasons it is best to provide these operators as stand alone functions rather than member function of the class. However such operator functions will need access to the private credentials member, which it does not have. There are two solutions to this problem. The first is to make the operator functions friends of the class. The other is to simple define named public member functions to perform the required behaviours and have the operator functions simply pass the call on to them, which is what I shall do here.

First the public member functions of the user_credentials class, which I shall call to_stream and from_stream. As they are more involved I declare them inside the class definition but define them fully outside the class definition - as this hints they should not be inlined (see inline functions).

class user_credentials
{
public:
   ...

   std::ostream & to_stream(std::ostream & out) const;
   std::istream & from_stream(std::istream & in);

private:
   ...
};

std::ostream & user_credentials::to_stream(std::ostream & out) const
{
   for(auto const & kv : credentials)
   {
       out << kv.first << '\n' << kv.second << '\n';
   }
   return out;
}

std::istream & user_credentials::from_stream(std::istream & in)
{
   while (in.good())
   {
       std::string id;
       std::string password;
       in >> id >> password;
       if (in.good())
       {
         set(id,password);
       }
   }
   return in;
}

These can be used like so:

   user_credentials io_example;
   io_example.from_stream(std::cin);
   io_example.to_stream(std::cout);

If you add this to main then you will need to know what the end-of-file control character is for your console (terminal, DOS box etc.). On the Linux system I am using to check the code I am presenting it is ctrl-D, on Windows it might still be ctrl-Z but I have not tried it in years (and years and years...).

Note that the loop in to_stream is a for-each loop (more C++11) that iterates over all values in the container - which for maps, as we have seen, is a pair. The name of each item in the loop is kv - short for key-value - i.e. it is a key-value pair. Again you can look up the details. The while loop in from_stream iterates while the in stream is good - meaning not in fail, error or, most importantly, end of file. We must only set credentials that were read properly - this is because even in normal cases with no stream errors or failures the final iteration will hit the end of file and fail and if we did not check the stream was still good a probably empty set of credentials would be set.

The format of the data written is very simply one item per line:
key1
password1
key2
password2
  .
  .
  .

Data read can be the same but simply requires each item to be separated by some white space (spaces, tabs and new lines).
Note that this means that while data written can contain spaces it will not read back correctly.
I have also not added full stream error checking - again more things for you to look up for yourself.

The stream operator functions look like this:

std::ostream & operator<<(std::ostream & out, user_credentials const & uc)
{
   return uc.to_stream(out);
}

std::istream & operator>>(std::istream & in, user_credentials & uc)
{
   return uc.from_stream(in);
}

As you can see they take a stream by reference as well as a reference to a user_credentials object - the output stream insertion operator - operator<< - takes its user_credentials by reference to a const user_credentials as we should not have to modify a user_credentials object just to write out its state.

This allows the example usage shown previously to become:

   user_credentials io_example;
   std::cin >> io_example;
   std::cout << io_example;

Oh, and strictly speaking we should include headers istream and ostream to make use of std:istream and std::ostream:

#include <istream>
#include <ostream>
#include <iostream>
   .
   .
   .

But it is likely that the iostream header will pull them in.

So to write and read to/from a file all you have to do is use file streams:

#include <fstream>
   .
   .
   .

First include the fstream header to use file streams.

Next I created two functions: load_credentials and store_credentials which load and store credentials to a file, which for the purposes of the examples used here is set as a pointer to a const string literal value (which is a C thing  inherited by C++ - and of course you can lookup pointer types if you want to know more):

char const * CredentialsFilename("user-credentials-in-plain-text.txt");

The load_credentials function is defined so:

int load_credentials( user_credentials & uc )
{
   std::ifstream file(CredentialsFilename);
   if (!file)
   {
       std::cerr   << "Could not open credentials file '"
         << CredentialsFilename << "' to load credentials.\n";
       return EXIT_FAILURE;
   }
   file >> uc;
   return EXIT_SUCCESS;
}

An input file stream - std::ifstream - is created and passed the file (path) name string of the file we wish to read from. If this worked the file is read into the passed uc parameter - which is passed by reference you will note.

EXIT_FAILURE and EXIT_SUCCESS are more things inherited by C++ from C (include cstdlib) and are the values that are defined as standard return values from main in case of failure and success respectfully. I use them here for convenience - as I modified main to return them (in fact 0 or EXIT_SUCCESS are specified by the standard to be returned to indicate successful termination by the C++ standard - so I suspect EXIT_SUCCESS is a macro for 0 in most if not all implementations but the EXIT_FAILURE value is implementation dependent).

The store_credentials function is similar:

int store_credentials( user_credentials const & uc )
{
   std::ofstream file(CredentialsFilename);
   if (!file)
   {
       std::cerr   << "Could not open credentials file '"
         << CredentialsFilename << "' to store credentials.\n";
       return EXIT_FAILURE;
   }
   file << uc;
   return EXIT_SUCCESS;
}

A file output stream - std::ofstream - is opened in this case and if it worked then the contents of the passed (by reference to const) user_credentials is inserted into the file stream. Note that by default an existing file is 'truncated' when opened by ofstream - meaning its previous contents are removed.

I have not added any logic to fully make use of the load_credentials and store_credentials functions in your main function - I leave that as an exercise to you. However if you create a file like so:

apples
google
pears
yahoo

and so on then you can get the file loaded when starting up and saved on return from main:

int main()
{
   user_credentials    known_credentials;
   if (load_credentials(known_credentials)!=EXIT_SUCCESS)
   {
       return EXIT_FAILURE;
   }

...

   return store_credentials(known_credentials);
}

All you need to do is to extend the code in between - the ... part!

I know I have covered a lot of stuff to get you to this point and it has taken me a couple of hours to do so. I hope you will therefore appreciate why I have left you to look up so much your self. There are things you need to consider - for example while it is nice to have your user defined type be able to act like other types with stream via the << and >> operators is it really a good idea? There are two main reasons for reading and writing object representations to streams: the first is as used here, because we wish to 'freeze' as it were the state of the object so it can be re-created later - or rather another object can be created with the same state. The other use is to display an object's value for us humans to read or allow us humans to enter and the two may not always be the same - we would probably like more formatting in the representations intended for us to read for example, which tends to complicate 'the machine' reading case.

Other things could be:
   - should the loading and saving of the credentials be more integrated with the user_credentials type?
   - should credentials be loaded on user_credentials creation and saved on destruction? If so maybe a second class that does just that and
     takes an existing user_credentials object as it construction parameter.

I'm sure there are lot more things I could add to the list but these are the ones that spring immediately to mind.

On a final note: please do not expect such detailed answers in the future. I have taken the time to run through a scenario in which the program developed a bit at a time and showed the sort of morphings of the code (called refactoring) that you can encounter and apply as thing developed as I feel it is a useful thing for you to see.

However it takes a _lot_ of my time and I fully expect you to use you brain and time to fill in the gaps and work things out for yourself much more from here on.

Hope it has been useful for you in any case.  

C++

All Answers


Answers by Expert:


Ask Experts

Volunteer


Ralph McArdell

Expertise

I am a software developer with more than 15 years C++ experience and over 25 years experience developing a wide variety of applications for Windows NT/2000/XP, UNIX, Linux and other platforms. I can help with basic to advanced C++, C (although I do not write just-C much if at all these days so maybe ask in the C section about purely C matters), software development and many platform specific and system development problems.

Experience

My career started in the mid 1980s working as a batch process operator for the now defunct Inner London Education Authority, working on Prime mini computers. I then moved into the role of Programmer / Analyst, also on the Primes, then into technical support and finally into the micro computing section, using a variety of 16 and 8 bit machines. Following the demise of the ILEA I worked for a small company, now gone, called Hodos. I worked on a part task train simulator using C and the Intel DVI (Digital Video Interactive) - the hardware based predecessor to Indeo. Other projects included a CGI based train simulator (different goals to the first), and various other projects in C and Visual Basic (er, version 1 that is). When Hodos went into receivership I went freelance and finally managed to start working in C++. I initially had contracts working on train simulators (surprise) and multimedia - I worked on many of the Dorling Kindersley CD-ROM titles and wrote the screensaver games for the Wallace and Gromit Cracking Animator CD. My more recent contracts have been more traditionally IT based, working predominately in C++ on MS Windows NT, 2000. XP, Linux and UN*X. These projects have had wide ranging additional skill sets including system analysis and design, databases and SQL in various guises, C#, client server and remoting, cross porting applications between platforms and various client development processes. I have an interest in the development of the C++ core language and libraries and try to keep up with at least some of the papers on the ISO C++ Standard Committee site at http://www.open-std.org/jtc1/sc22/wg21/.

Education/Credentials

©2016 About.com. All rights reserved.