Additional Password Validation

We currently have a customer requirement to implement additional password security within all of our applications (including Epicor). Most of the requirements we were able to handle using the Password Policy and core functionality in Epicor 10.1.500.14 (i.e. at least one uppercase/lowercase/number, force password change every 3 months, lock account after X failed logins). However, there is one requirement we’re currently struggling with: “Account passwords must be configured with a history file to remember and prohibit password reuse of the last five (5) passwords.”

We tried implementing a BPM to store a history of password hashes on a per user basis in a table. We ran into a roadblock though, the hashing/encryption algorithm that Epicor utilizes changes the value of the hashed password even if the password is the same each time. For example, if I use the password ‘test’ twice, it may hash to ‘123abc’ the first time and ‘456def’ the second time.

Has anyone run into something like this before, or have ideas on how we could implement this functionality?

My guess is they are hashing the password plus a salt. A salt is usually a randomly generated number or group of characters. You can find a better explanation here. I have no idea where the salt is stored.

It’s definitely being stored as a salted hash in the database, converted to a Base64 string. The issue that we’re currently having is that we’ve not found any way to a value for the password that is not already hashed, and with the salt changing each time the password is changed, a password of test could be stored as either “3gupYTuavINlceWFPmPQL2jQufzuxVqwoIvteoQGiSLci8ZbzWquSg==” or “uGVi8081ISLgME1+quAUuGn6d6BjmQMMuIaEGGzu6sFv2QYZ+LDMCg==” in the database.

As far as I know, even with the salt for each of these we’re not able to get these values to a point where we could compare them and ensure they’re unique - in order to do that we’d need the original password value.

Mixed feelings. I understand the need and am happy you find our security too good :wink:

I received the email through channels and will see what I can come up with that does not ‘hurt’ security.

The immediate reaction of course to use Windows bindings for Authentication is you really need industrial strength. I assume this is not an option?

1 Like

Correct Jeremy on the approach. We are appending a crypto random number as the salt to the password and (one way) hashing.

1 Like

Single Sign On is something that we’re looking into, but at this time it is not immediately available as an option. We have a few exceptions in our company at the moment which would prevent it from being used universally (Epicor username is different from Windows username, etc.), and thus wouldn’t completely fulfill the requirement.

Don’t think the usernames have to match between Epicor and Windows. There’s a Domain User ID box in User Account Security that would map between the two. Don’t use SSO so I can’t be 100% sure about this.

3 Likes

You may also want to let the customer know that NIST now recommends against forced password changes and arbitrary complexity. A Google news search will show that in the end, they feel it leads to a less secure situation as people tend create easy to remember patterns (same password with changing month/year - simple increasing number) or notes on the screen or under the keyboard.

From: NIST Special Publication 800-63B

Verifiers SHOULD NOT impose other composition rules (e.g., requiring mixtures of different character types or prohibiting consecutively repeated characters) for memorized secrets. Verifiers SHOULD NOT require memorized secrets to be changed arbitrarily (e.g., periodically). However, verifiers SHALL force a change if there is evidence of compromise of the authenticator.

FWIW

Mark W.

Password/phrase handling is a passion. I have parents, siblings and an entire extended family on both sides that are ‘normals’. I married a geek and our kids are geeks who married geeks. We are tech support for the rest of the family and I am top tier tech support. I have a passion for security, encryption and good password handling for purely selfish reasons.

Single Sign On is a great start / Windows as a binding for encryption and authentication is better than 99% of what you will find out there. We worked with MS on our password handling during POC days on ICE so I am ‘happy’ it’s not an easy reverse engineer. Protecting ‘data at rest’ is difficult and a growing issue we as admins need to deal with - the db backup that gets stolen and your user data is out there. On top of having insecure systems that are just plain hacked. Both areas will be getting more attention by the industry and Epicor.
*Nothing to announce, forward looking statement, Safe Harbor, you all know the drill when I post up here :slight_smile:

Windows, as many, has learned the ‘What you have / What you know / What you are’ approach is better - imperfect but a good step forward. Luckily we walk around with mainframe computers in our pockets with instant global communication so leveraging them for phone, text and email is handy (You are securing your users devices somehow right?).

1 Like

From a high-level Epicor has a little helper class to hash the password via 2 helper methods ComputeHash, VerifyHash it does support different algorithm’s such as SHA1, SHA256, SHA384, SHA512, MD5 but by default Epicor uses SHA256.

That helper lies in Epicor.System.dll usually stored in the bin folder on the IIS App Server.

High Level ComputeHash:

  1. Using RNGCryptoServiceProvider creates a random number salt as bytes
  2. Converts your plain-text password string into bytes
  3. Creates a buffer with the size of passwordBytes.Length + saltBytes.Length
  4. Combines the passwordBytes and saltBytes into a single buffer array
  5. Hash the buffer using SHA256 Managed
  6. Do some magic via a for loop
  7. Encode Base64

What you need to know is that the Random Salt is embedded in the end-result Hash and is used by the VerifyHash method.

If you were to call ComputeHash a thousand times with the same password string you would get a different Hash always.

| Password | Computed Epicor Hash w/ RNG Salt |
|----------|:-------------:|------:expressionless:
| manager | 46uIY6/nQjHL5mX1KeE/7NtEXD3MIOblGxpVRH5ZWXNORGsNwT3WHg== |
| manager | JIzKviG6ZG5rE52KOeKEg4AvPnU72FOUQ0y7y/R8h8GZNQaV2G+GHw== |
| manager | tjk/TfHesmjnhq7NcUbCkfQKJnqfV7Q2mJRG6hwYRD8xGIofA6tGIQ== |

Example:

// Returns: 46uIY6/nQjHL5mX1KeE/7NtEXD3MIOblGxpVRH5ZWXNORGsNwT3WHg== for example.
Epicor.Security.Cryptography.SHA.Hasher.ComputeHash("manager");

High Level VerifyHash (returns boolean if hash matched):

  1. Decode Base64
  2. Extract salt bytes from Hash
  3. Using the same salt bytes and user provided plain-text password we re-create the Hash, only this time because we pass in a salt array ComputerHash won’t create a RNG Salt it will use the one passed.
  4. Compares the newly created Hash (base64) to Hash in Database (previously generated). They should Match

Example:

Epicor.Security.Cryptography.SHA.Hasher.VerifyHash("manager", "46uIY6/nQjHL5mX1KeE/7NtEXD3MIOblGxpVRH5ZWXNORGsNwT3WHg==");

You can write alot more details on this process. But things to know that a Hash Computed on My PC would work just as fine on your machine, it does not make use of the DPAPI, hence when you copy your Database to another server, it all works just fine.

For those curious what how the Salt is created, since it’s not a hard-coded “MfgSys” salt in E10.1+

// Fills an array of bytes with a cryptographically strong sequence of random nonzero values
byte[8] saltBytes = new byte[8];
new RNGCryptoServiceProvider().GetNonZeroBytes(saltBytes)

You can with some effort, look up the Hashing Algorithm and re-create it easily in Visual Studio for your custom needs.

2 Likes

Seems very similar to bCrypt. But bCrypt adds a 3rd parameter(the password and salt being the other 2) for a count of times to do the hash. This count can be on the order of 10^4 (or higher) This can make decrypting take much longer, but makes dictionary attacks almost useless as each attempted decrypt might have to run 10,000 times.

1 Like

Example of Re-Creating the Epicor Hash methods for SHA256 in your Application:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Security.Cryptography;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create a New Hash with password manager
            Console.WriteLine(ComputeHash("manager"));

            // Lets Validate all these possible hashes against Password manager (one-way hash)
            Console.WriteLine(VerifyHash("manager", "46uIY6/nQjHL5mX1KeE/7NtEXD3MIOblGxpVRH5ZWXNORGsNwT3WHg=="));
            Console.WriteLine(VerifyHash("manager", "JIzKviG6ZG5rE52KOeKEg4AvPnU72FOUQ0y7y/R8h8GZNQaV2G+GHw=="));
            Console.WriteLine(VerifyHash("manager", "tjk/TfHesmjnhq7NcUbCkfQKJnqfV7Q2mJRG6hwYRD8xGIofA6tGIQ=="));
            Console.WriteLine(VerifyHash("WRONG_PASSWORD", "tjk/TfHesmjnhq7NcUbCkfQKJnqfV7Q2mJRG6hwYRD8xGIofA6tGIQ=="));
            Console.ReadLine();
        }

        public static string ComputeHash(string password, byte[] saltBytes = null)
        {
            HashAlgorithm algorithm = new SHA256Managed();

            if (saltBytes == null)
            {
                saltBytes = new byte[8];
                new RNGCryptoServiceProvider().GetNonZeroBytes(saltBytes);
            }

            byte[] bytes = Encoding.UTF8.GetBytes(password);
            byte[] buffer = new byte[bytes.Length + saltBytes.Length];
            for (int i = 0; i < bytes.Length; i++)
                buffer[i] = bytes[i];

            for (int j = 0; j < saltBytes.Length; j++)
                buffer[bytes.Length + j] = saltBytes[j];

            byte[] buffer2 = algorithm.ComputeHash(buffer);
            byte[] combinedArr = new byte[buffer2.Length + saltBytes.Length];
            for (int k = 0; k < buffer2.Length; k++)
                combinedArr[k] = buffer2[k];

            for (int m = 0; m < saltBytes.Length; m++)
                combinedArr[buffer2.Length + m] = saltBytes[m];

            return Convert.ToBase64String(combinedArr);
        }

        public static bool VerifyHash(string password, string hash)
        {
            byte[] saltBytes = new byte[8];
            byte[] buffer = Convert.FromBase64String(hash);
            int x = 0x100 / 8; // SHA256

            if (buffer.Length < x)
                return false;

            for (int i = 0; i < saltBytes.Length; i++)
                saltBytes[i] = buffer[x + i];

            return (hash == ComputeHash(password, saltBytes));
        }
    }
}
2 Likes