PHP Security: Passwords
Tags: php, security, cryptography
Published: 2018-09-22 02:53:35

---
title: PHP Security: Passwords
published: true
description: Securely storing passwords with PHP.
tags: php, security, cryptography
---

# My Password Rules
* Minimum length of 16 characters.
* Maximum length of 256, 512, 1,024 or 2,048 characters.
* Make sure the password isn't pwned.
* **ALLOW PASTING OF PASSWORDS!**

Microsoft has a **maximum** password length of *16 characters.*

Does that sound good to you? Well, it certainly shouldn't. It's not often applications have such low maximum lengths for passwords(correct me if I'm wrong). But it's really bad to limit your password lengths like Microsoft did.

It's debatable whether you should enforce password rules such as special characters, uppercase letter, lowercase letter, etc. I don't believe that's the better thing to do. **I strongly disagree** with password rules like that.

All the application should do is make sure my secret is long enough, and hash it slowly so my secret won't be found out within anyone's lifetime.

A lot of sites have a minimum password length of 6 or 8 characters. That's not good. It really isn't. Even if they're using Argon2 with a high cost. 12 characters is okay ... but I like to set the minimum to around 16 characters long.

**Why allow such a big maximum number of characters? It's ridiculous!**

You're right. It *probably* is. But I like to make sure password managers are able to input super long passwords.

# Storing Passwords

I'll show you how easy it is to securely hash passwords in PHP >= 5.6. It's automatically salted.

```php
$hashed_password = password_hash('string', PASSWORD_DEFAULT);
```

The default password hashing algorithm as of PHP 7.2 and lower is bcrypt. But note, while bcrypt is good, it truncates passwords at 72 characters, and is vulnerable to null bytes.

My preferred way of storing passwords will *probably* differ from yours.

## My Technique

* HMAC the password.
* Hash the HMAC.
* Encrypt the hash.

**Why the heck are you HMACing the password?!**

Well, that's because when say someone is registering to your site. What if they put in a 4 megabyte password, and send *multiple* requests? This could effectively DDoS you. However, you can prevent that, by pre-hashing with a fast algorithm such as SHA256. Or you can validate the password and make sure it doesn't go past a maximum number of characters. And if it passes the validation, then you can hash the password, etc.

But that's not the only problem it can solve. What if you're storing PINs? Only 4 digit numbers, can be brute-forced within a reasonable amount of time. Think about it, you're only hashing 4 characters. You can turn 4 characters, into a really long string with an HMAC. And there you go, it's better than just storing 4 characters.

**How does it DDoS you?**

It requires a lot of computing power for say Argon2. Now remember, password hashing algorithms are *meant* to be slow. So if it takes 1 second to hash a 8 character password, it might take 10 seconds to hash a 1 megabyte password. Which is *why* we shorten or lengthen a password to a set length so we don't have that issue.

**But mixing up different algorithms is dangerous!**

It can be, but I *highly* doubt hashing a SHA3-512 hexadecimal output will do anything bad in a Argon2 hash function, because it's literally just numbers and letters. Isn't that what a password usually is?

**Okay, but why are you encrypting the hash?**

It's not a performance issue if you're using fast encryption algorithms.

**Still! Why? It adds no benefits, and makes your application harder to maintain.**

That's actually a good argument. However, say your database server is separate from your application server. **If** an attacker *only* gets access, to the database, they first have to find the encryption key to decrypt the hash, or eventually break it, only to have to stop dead in their tracks with a really slow hashed password. But in the real world, I doubt someone would try to brute-force AES encryption, they'd probably start looking for the key. But doing this *can* buy you time, possibly enough time to rotate your keys and tell users their data has been compromised.

### The Code

I can also show you a quick basic implementation in PHP.

First, we of course need a password.

```php
$password = 'my super secret password';
```

Now that's done, we can setup the HMAC's algorithm and key.

```php
$hmac_algorithm = 'sha3-512';
$hmac_key       = random_bytes(32);
```

Step 1: Make the HMAC.

```php
$hmac = hash_hmac($hmac_algorithm, $password, $hmac_key);
```

Ta da! Now that's done, let's move on to setting the password hashing up.

```php
$password_hash_cost      = 4;
$password_hash_algorithm = PASSWORD_ARGON2I;
$password_hash_options   = [
    'memory_cost' => $password_hash_cost * PASSWORD_ARGON2_DEFAULT_MEMORY_COST,
    'time_cost'   => $password_hash_cost * PASSWORD_ARGON2_DEFAULT_TIME_COST,
    'threads'     => $password_hash_cost * PASSWORD_ARGON2_DEFAULT_THREADS
];
```

Step 2: Make the hash.

```php
$hash = password_hash(
    $hmac,
    $password_hash_algorithm,
    $password_hash_options
);
```

Yep! That's all done. We need to setup that next step though.

```php
$encryption_key   = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
$encryption_nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
```

Step 3: Make the ciphertext.

```php
$ciphertext = sodium_crypto_secretbox(
    $hash,
    $encryption_nonce,
    $encryption_key
);
```

I ran the above code, and these were the following outputs:

```
HMAC output: 876dd083d78abd57c267a4cb3b64788c468f7ff9a88ab91800e5ae3cc3e25f646510fc2e2a9ccd9395ba01b814dbe76efa2acb985a7733330f4abc6b5157474c

Hash output: $argon2i$v=19$m=4096,t=8,p=8$ZEJXdlN2QU8vb3RIU3RxeA$0hU3ZokcZpPJwfmdmzwXD5KFfdmh/MyZRAFx4tLIJkc

Ciphertext output: 319dfbac430ad505b7daccfb8c827f36389b1dc747d3b3fc6cd1334e060b156800dec5268c79fe46367ad1be3ddac70540ddc19a6fb70348018a5ed27bcd2fb822a83e289833ec6d3294881eed6e45b94fa8c5a6f502ddb0851956587d6a2817bc45f82251b7e633de9cc3c2779e9b
```

Note, I converted the ciphertext to hexadecimal. And by default the HMAC output is converted to hexadecimal. I was using PHP 7.2.9, and I was using libsodium for encryption.

**I am by no means an expert in cryptography, or it's implementation. And this was my opinion on passwords, yours will probably differ.**

That'll be it for today, I hope you enjoyed the article, and that perhaps you opened your mind a little more about passwords.

# **EDIT, PLEASE READ:**

**Don't use my technique.** Read the comments. Just use password hashing algorithms such as Argon2 or Bcrypt to store your passwords.

Like I said before. Encrypting the password hashes will only be effective if you keep the **application and database on separate servers/hardware,** assuming the attackers only gain access to your database.