For my final project as a Flatiron School student I decided I wanted to understand password managers better and built a basic one. It will be a very simple password manager that probably won't be as secure as it should be, but will be a good base in understanding how things are made, secured, how they should be stored/accessed, and hopefully it will be a project that I can grow as I learn more.
Checkout my working password manager here!!!
Now, building a password manager is a no small or simple thing. There is a lot of cryptography that comes into play and data management that I have yet to fully understand, but as a good starting point for me to better understand things and research things alone the way, here are a couple things that need to be understood a bit first.
- Cryptography and Kerckhoffs's Principle
- Secure Coding Practices
- Proper Storage of Passwords
- Password Digests/ JWT/ Serializers
- Keys To the Kingdom are Important to Hide
Kerckhoffs's principle is a 19th century cryptography principle states that a "cryptosystem should be secure even if everything about the system, except the key, is public knowledge." (1)
So, if security depends on keeping the cryptographic algorithm used secret, then exposure of this "brittle" system leads to difficulties in developing, testing, and distributing implementations of a new algorithm. In contrast, if keeping the keys used with the algorithm secret is important, then disclosure of the keys simply requires the simpler, less costly process of generating and distributing new keys.
As technology progresses and systems grow bigger and bigger, the latter seems to be a more common thing for companies to do. Keys, or passwords, can be unique and difficult to guess and can be replaced every few months to keep the information tied to it secure. This is a much better practice to use rather than recreating the wheel every time data changes.
- Validate input! Validate input from any data sources as it can eliminate the vast majority of vulnerabilities.
- Heed compiler warnings and compile code using the highest warning level available.
- Architect and design for security policies. Create a software architecture and design your software to implement and enforce security policies.
- Keep the design as simple and small as possible as complex designs increase the likelihood that errors will be made in their implementation, configuration, and use.
- Set up a default deny process and base access decisions on permission rather than exclusion.
- Adhere to the principle of least privilege where every process should execute with the the least set of privileges necessary to complete the job.
- Sanitize all data sent to other systems and sanitize all data passed to complex subsystems.
- Practice defense in depth. Manage risk with multiple defensive strategies, so that if one layer of defense turns out to be inadequate, another layer of defense can prevent a security flaw from becoming an exploitable vulnerability and/or limit the consequences of a successful exploit.
- Use effective quality assurance techniques. Good quality assurance techniques can be effective in identifying and eliminating vulnerabilities.
Information systems store passwords/credentials in a variety of protected forms and having proper storage of them is important to prevent issues like malicious use of credentials or theft.
Keeping passwords secure involves a cryptographic hash and a good Password Storage Scheme.
A cryptographic hash is a mathematical algorithm that can be used to produce a checksum, or a value typically used to detect data errors.
With a cryptographic hash, it’s possible for a vendor to verify that a password is correct by crosschecking its checksum with the checksum in the database without ever knowing what the password actually is. (4)
"Password Storage Schemes encode new passwords provided by users so that they are stored in an encoded manner. This makes it difficult or impossible for someone to determine the clear-text passwords from the encoded values." (5) A few examples of Password Storage Schemes are AES, Base64, Blowfish, Clear, or Crypt.
A password digest is a authentication column used to store passwords. While utilizing Bcrypt for my main logging handling I am able to create a 'password_digest' column for the user's login credentials and then Bcrypt and Rails are able to see that a user has a password and hash it accordingly.
"JSON Web Token (JWT) is an open standard that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA." (6)
The JWT that I used used the "JWT.decode(token, ENV[HASH_KEY], true, algorithm: 'HS256')". The HS256 is
Serializers know about both a model and the current_user, so you can customize serialization based upon whether a user is authorized to see the content. In short, serializers replace hash-driven development with object-oriented development. This was very interesting to learn more about as a way to nest associated information without building out one bit table of information. I was able to have each model separated out in different tables and use a Serializer for each model to associate them. This was not super each as Rails Serializer has apparently stoped supporting new updates to is and most are starting to use Fast JSON.
Similar to "sessionStorage", localStorage is a read-only property that allows access to a "Storage object for the Document's origin; the stored data is saved across browser sessions." (7) Note: that data stored in localStorage has no expiration time so it must be set if desired.
For my project I decided to use sessionStorage over localStorage. I did this because of how sessionStorage clears out its associated cache each time the tab is closed, thus, making it more difficult to XSS the browser's localStorage and gain access to a user's JWT or other potentially stored credentials.
In my case I needed to setItem method on the sessionStorage object for the JWT object when a user logs in and save it in the browser and then clear the sessionStorage of the JWT object once the user logged out. This will allow all user associated state information to be cleared and reset, so that no one else could use their login credentials.
Always use sessionStorage when able!! Check out this other Dev Article I found on it or my article on it.
For my project I wanted to encrypt the data that the user was storing as well as decrypt it once it got to the user. In theory by encrypting it before it is sent and once it is received... it will hopefully be more secure and less susceptible to man-in-the-middle attacks as the data will be encrypted at the vulnerable point.
But, this means that I need to figure out a way to encrypt the data without keeping the key so accessible/visible.
For my project I found the Cryptr npm package that would allow me to create a key and encrypt the information before it was sent to the backend. Now, this is not 100% secure as there is always some point that data is seen in plain text. But, by encrypting it before it gets to the database
With any hashing, encryption/decryption a key is needed. This is something that needs to be protected but can be difficult to hide while still making it available. I ran into this issue that my Rails back-end could have an .env file to make my own key and keep it from being uploaded to my GitHub. And then Heroku could keep that separate from my app so that my key was still hidden. Cool. But on the front end that is a bit more difficult. As I was encrypting things before it was being sent to the back-end I needed a key to be accessible on the front end and, ideally, not stored. Apparently this is a problem. So, off I go to learn about creating secure keys and not keeping them in plain text but still making them accessible and unique. Ideally I would love for each user to have their own key or a user to be able to set a key for each item they save. Now, as my project is technically done, this is not a high priority but it is something that I want to continue learning about and working on.
Once I got my application to a point that things worked I felt it was time to deploy it into the world. Yes, some of my classmates deployed from the beginning but I wanted to wait until I had something more to show.
For Deploying it to the Internet I used Heroku for the back-end and Netlify for the front-end. This allowed me maintain things a bit desperately but knowing about each other and if one part of it went down when I could better work with that side on its own.
Before you get things going with Heroku you need to make sure that your back-end used Postgres and that the right version of the gem 'dotenv-rails'. Once I got that going it was super easy to set up which Git branch that Heroku used and which Git branch Netlifly used within their respective Repositories.