Appendix D. Hashing and Passwords

Table of Contents
D.1. Hashing
D.1.1. Hashing / Encryption, Terminology
D.1.2. The Mechanics
D.1.3. Usage
D.1.4. Verify the Hash, Live Code
D.1.5. What Is Salt?
D.1.6. Just One More Thing on Hashing
D.2. Passwords
D.2.1. Theoretical Background
D.3. Assignments Passwords
D.3.1. Assignment Brute Force BF.0

D.1. Hashing

Here we shall look at hashing, hashing algorithms, and how we logically verify user input against a hash. Your guru, [Aum18, chapter 6], calls them the cryptographer's Swiss Army Knife: they are used in digital signatures, public-key encryption, integrity verification, message authentication, password protection, key agreement protocols, and many other cryptographic protocols. They are indeed everywhere, but first a few words on general terminology. We looked at the first encryption previously, but generally:

D.1.1. Hashing / Encryption, Terminology

Dealing with keeping confidentiality of information on any network pr computer we have to recognize some of the terms of the domain. Cryptography[46] sometimes also called Cryptology

is the practice and study of techniques for secure communication in the presence of third parties called adversaries.
In cryptography, ciphertext or cyphertext is the result of encryption performed on plaintext using an algorithm, called a cipher.[47]

Decryption is the opposite process, ie converting ciphertext into plaintext. If this is done securely. Alice[48] may encrypt a message in plaintext into ciphertext, send it across the network to Bob, who may decrypt it back into plaintext by a key, and then read it. Any eavesdropping on the message while it is traversing the net will only give Eve, the eavesdropper, some un-understandable ciphertext. The message is confidential.

Hashing is in some respects similar, and in others, fundamentally different.

A cryptographic hash function (CHF) is a hash function that is suitable for use in cryptography. It is a mathematical algorithm that maps data of arbitrary size (often called the "message") to a bit string of a fixed size (the "hash value", "hash", or "message digest") and is a one-way function …

Please notice that we do not call hashed text ciphertext, but message digest, or just digest. In the last bit above, the practical irreversibility of hash functions is exactly what we are looking for when obfuscating passwords. There's no key to get for Eve. The only way to crack a hash is applying brute force, ie guessing systematically the plaintext producing the hash. The quality, strength, of the CHF is dependent on the time it will take a modern computer to find the plaintext, the preimage, by brute force. Here we are preferably talking years, many years, even many, many years.

As you have already realized, we use hashing for obfuscating passwords beyond recognition before we store them. Encryption would entail the possibility of decryption. We don't want that. We don't need that. To crack message digests you will need to hash a string of plaintext, match it against the stored password, and, if there's no match, try another, then another, then … It will not help you that you might know the hashed representation of the password.

D.1.2. The Mechanics

Let me outline hashing and brute forcing inspired by [Aum18, chapter 6]

Example D.1. Hashing Pseudo Code
/*
 * THE FOLLOWING IS PSEUDO CODE TO ILLUSTRATE AN IDEA
 *
 * input: plaintext
 * output: message digest
 * doImplAlgo: some hashing algorithm
 * duration: enough to warrant synchronicity
 * warning: Do Not Write This Yourself
 */
const hash = function (plaintext) {
    let messageDigest = doImplAlgo(plaintext);
    return messageDigest;
}

Example D.2. Brute Forcing Pseudo Code
/*
 * THE FOLLOWING IS PSEUDO CODE TO ILLUSTRATE AN IDEA
 *
 * input: message digest
 * output: plaintext
 * duration: measurable, possibly significant, measure of quality
 * warning: You Should Probably Not Write This Yourself
 */
const crack = function (md) {
    while (true) {
        let niceTry = randomMessage()
        if (hash(niceTry) == md) return niceTry;
    }
}

An endless loop? Well, close, if the hashing algorithm is good enough.


D.1.3. Usage

All programming languages have functions for hashing with an array of different hashing algorithms. When we make a choice of one, choose a good one, it has some repercussions on other parts of whatever software you are building. It affects, for example, the layout of the database user table or collection you are authenticating your users against.

It should also be stated that unless you are totally sure of what you are doing, do NOT use home made hashing, with or without salt. It is not secure. Instead you should apply publicly known and recognized functions from whatever language you use for your applications.

In order to work with hashing and encryption we have to get the necessary software. The two essential modules for node are bcryptjs, and crypto-js. You just need one of them, for hashing. bcryptjs is best practice. The other one, crypto-js has other, and also standardized algorithms. It also has encryption functions. Let us see them in practice:

We may have hinted that the primary use of hashing is to create obfuscated passwords. [Aum18, chapter 6] says otherwise. He holds that:

The notion of security for hash functions is different from what we’ve seen thus far. Whereas ciphers protect data confidentiality in an effort to guarantee that data sent in the clear can’t be read, hash functions protect data integrity in an effort to guarantee that data — whether sent in the clear or encrypted — hasn’t been modified. If a hash function is secure, two distinct pieces of data should always have different hashes. A file’shash can thus serve as its identifier.

Consider the most common application of a hash function: digital signatures, or just signatures. When digital signatures are used, applications process the hash of the message to be signed rather than the message itself, as shown in Figure 6-2. The hash acts as an identifier for the message. If even a single bit is changed in the message, the hash of the message will be totally different. The hash function thus helps ensure that the message has not been modified. Signing a message’s hash is as secure as signing the message itself, and signing a short hash of, say, 256 bits is much faster than signing a message that may be very large. In fact, most signature algorithms can only work on short inputs such as hash values.

Meaning that signing documents or mails for transmission is main use of hashes. In our context, however, we focus on the use in authentication, ie for password obfuscation.

Example D.3. Hashing Example (fragment)
const bcrypt = require('bcryptjs');                 // added for bcrypt hashing
const sha256 = require('crypto-js/sha256');         // added for encryption and other hashes
const sha512 = require('crypto-js/sha512');         // added for encryption and other hashes
const md5 = require('crypto-js/md5');               // added for encryption and other hashes
const sha1 = require('crypto-js/sha1');             // added for encryption and other hashes
const sha3 = require('crypto-js/sha3');             // added for encryption and other hashes
const saltiter = 10;

const bchash = async function (req, algo='bcrypt') {
    let hash;
    switch (algo) {
        case 'md5':
            hash = await String(md5(req.body.password));
            break;
        case 'sha1':
            hash = await String(sha1(req.body.password));
            break;
        case 'sha3':
            hash = await String(sha3(req.body.password));
            break;
        case 'sha256':
            hash = await String(sha256(req.body.password));
            break;
        case 'sha512':
            hash = await String(sha512(req.body.password));
            break;
        default:
            hash = await bcrypt.hash(req.body.password, 10);
            break;
    }
    let obj = {
        algo: algo,
        length: hash.length,
        digest: hash
    };
    return obj;
}

Click here! (https://limitless-brushlands-73272.herokuapp.com/).


Take a detailed look.

Please notice that the four instances of using the bcrypt algorithm of the bcrypt.hash function results in four different hashes, yet they all verify as correct with bcrypt.compare.

D.1.4. Verify the Hash, Live Code

Example D.4. The bcrypt Case. Fragment of nodeAuthDemo/models/handleUsers.js
exports.verifyUser = async function (req) {
    let check = { email: req.body.email };
    let u = await this.getUsers(check);         // here the database is read
    let success = await bcrypt.compare(req.body.password, u[0].password);
                                                // u[0].password was read in the database
    return success;
}

The bcrypt requires the stored password digest to be read in order to extract the salt, then hash the entered password with the same salt as the stored password. Otherwise verification would be meaningless.


D.1.5. What Is Salt?

There is a phenomenon called salting that helps reenforcing the password hashing to be even harder to brute force. Please refer to Salt (cryptography). Here it is stated that salt is random data that is used as an additional input to a one-way function that hashes data.

Please study that, and put special emphasis on the section Example usage, as well as on the following quote: … a salt cannot protect common or easily guessed passwords. Without a salt, the hashed value is the same for all users that have a given password, making it easier for hackers to guess the password from the hashed value …

D.1.6. Just One More Thing on Hashing

The passwords used by your operating system are also interesting in order to get some more perspective on hashing. Regarding Linux especially, but not only, you might want to read https://crypto.stackexchange.com/questions/40841/what-is-the-algorithm-used-to-encrypt-linux-passwords and, for more background: https://security.stackexchange.com/questions/211/how-to-securely-hash-passwords



[46] https://en.wikipedia.org/wiki/Cryptography
[47] https://en.wikipedia.org/wiki/Ciphertext
[48] https://en.wikipedia.org/wiki/Alice_and_Bob