Skip to main content
Password Storage
 (And Attacking)
     In PHP
    Anthony Ferrara
Github URL
Follow Along:

github.com/ircmaxell/password-bad-web-app

A "Bad Web App"
- Has Known Vulnerabilities
- Only Use For Education!!!
- Requires only Apache + PHP
- Has Composer Dependencies
Let's Start
From The
Beginning
Plain-Text Storage
        git checkout plaintext

Stores passwords in Plain-Text

What's wrong with this picture?
Plain-Text Storage
What happens if we have a SQL-Injection
Vulnerability?

localhost/sqli

Simulates:

?offset=0'+UNION+SELECT+*+FROM+users
Plain-Text Storage
Problem!

Any attack vector results in leakage of ALL
credentials!
We Can Do Better
MD5
           git checkout md5

Uses the MD5 Cryptographic Hash function.

md5($password)

hash('md5', $password)
Wait,
What Is A Hash?
What's A Cryptographic Hash?
Like a fingerprint.

One-way.
- Easy and efficient to compute
- Very inefficient to reverse
  - (Practically impossible)
- Very hard to create collision
  - (new input with same output)
MD5
What's the problem now?

SQL-Injection still gives us hash

But the hash is one-way, how can we attack it?
Enter:
Lookup Tables
Lookup Table
Google is a great example

Maps hash to password directly

Database Table:
hash            | password
--------------+-----------
"5f4dcc3b..." | "password"
"acbd18db..." | "foo"
Lookup Table
Lookups are CPU efficient.

Require a LOT of storage space
- (Very space inefficient)

All passwords <= 7 chars (95^7, 70 Trillion)
Requires 1.5 PetaBytes
- In Most Optimal Storage Format
We Can Do Better
Rainbow Table
    Seed


     Hash


    Reduce


     Hash
Rainbow Table
a4fef...       Seed


                Hash

 Reduce
               Reduce



  New           Hash
Password
Rainbow Table

Seed 1   Hash   Reduce   Hash   Reduce   Hash   Reduce   Hash



Seed 2   Hash   Reduce   Hash   Reduce   Hash   Reduce   Hash



Seed 3   Hash   Reduce   Hash   Reduce   Hash   Reduce   Hash



Seed 4   Hash   Reduce   Hash   Reduce   Hash   Reduce   Hash



Seed 5   Hash   Reduce   Hash   Reduce   Hash   Reduce   Hash



Seed 6   Hash   Reduce   Hash   Reduce   Hash   Reduce   Hash
Rainbow Table
Time/Space Tradeoff
- Slower than a Lookup Table
- Uses Much less storage

Most (99.9%) passwords <= 7 chars
Requires only 64 GB
- Chain length of 71,000
Defense!
Salted MD5
       git checkout salted-md5

Uses the MD5 Cryptographic Hash function.
But adds a random salt UNIQUE per user.

md5($salt . $password)

hash('md5', $salt . $password)
Salts
Must be unique!
- Per Hash
- Globally

Should be random
- Strong!!!
- Reasonably long (at least 64 bits)
Salted MD5
What's the problem now?

SQL-Injection still gives us hash
- And the salt

But the salt defeats rainbow tables...
Can Anyone See
 The Problem?
What's A Cryptographic Hash?
Like a fingerprint.

One-way.
- Easy and efficient to compute
- Very inefficient to reverse
  - (Practically impossible)
- Very hard to create collision
  - (new input with same output)
What's A Cryptographic Hash?
Like a fingerprint.

One-way.
- Easy and efficient to compute
- Very inefficient to reverse
  - (Practically impossible)
- Very hard to create collision
  - (new input with same output)
Hash Functions
Are Made To Be
     FAST
Brute Forcing
Several Tools Available
- John The Ripper
- OCIHashCat

A Lot Faster Than You May Think
Brute Forcing
Multiple Ways To Attack
- Mask Based (permutations)
- Dictionary Based
- Combinator Based
  - Combinations of dictionary words
- Fingerprint Based
  - Combinators applied with permutations
- Rule Based
  - Takes input password and transforms it
Brute Forcing
               Salted MD5
2012 Macbook Pro:
- md5: 33 million per second
- sha256: 20 million per second

Mask Attack:
6 char passwords: 5 hours
7 char passwords: 22 days
Entire English Language: 1.8 seconds
"LEET" Permutations: 1 hour
We Can Do Better
Brute Forcing
               Salted MD5
25 GPU Cluster
- md5: 180 Billion per second
- < $50,000

6 char passwords: 4 seconds
7 char passwords: 6 minutes
8 char passwords: 10 hours
Entire English Language:
"LEET" Permutations:
Brute Forcing
               Salted MD5
25 GPU Cluster
- md5: 180 Billion per second
- < $50,000

6 char passwords: 4 seconds
7 char passwords: 6 minutes
8 char passwords: 10 hours
Entire English Language: yeah...
"LEET" Permutations: 0.7 seconds
But Wait,
I Thought MD5
  Was Broken?
MD5 IS Broken!
But No Other Primitive Hash Is Not!!!

sha1≈ md5
sha256 ≈ md5
sha512 ≈ md5
whirlpool ≈ md5

ALL raw primitive hashes are broken for
password storage.
So, How Can We
 Combat Such
  Hardware?
Iterated MD5
      git checkout iterated-md5

Uses the MD5 Cryptographic Hash function.
But adds a random salt UNIQUE per user.
And iterates a lot of times

do {
   $h = md5($h . $salt . $password)
} while($i++ < 1000);
We're
  Intentionally
Slowing It Down
Brute Forcing
              Iterated MD5
25 GPU Cluster
- md5: 70 million per second

6 char passwords: 17 minutes
7 char passwords: 1 day
8 char passwords: 124 days

Entire English Language: 0.8 seconds
We Can Do Better
PBKDF2
          git checkout pbkdf2

Uses the standard PBKDF2 algo
- With SHA512 primitive

Slower, and harder to use on GPU

pbkdf2($pass, $salt, 10000, 40)
Brute Forcing
                PBKDF2
25 GPU Cluster
- PBKDF2(sha512): 300,000 per second

6 char passwords: 28 days
7 char passwords: 7 years
8 char passwords: 700 years

Entire English Language: 3 minutes
We Can Still
 Do Better
BCrypt
          git checkout bcrypt

Uses the standard BCrypt algo
- based on Blowfish cipher

Same execution time,
Much harder to run on GPU

crypt $2a$
Brute Forcing
                 BCrypt
25 GPU Cluster
- BCrypt: 70,000 per second

6 char passwords: 120 days
7 char passwords: 31 years
8 char passwords: 3000 years

Entire English Language: 14 minutes
A Note On Cost
BCrypt accepts a "cost" parameter

Must be tuned per server!
- Target about 0.25 to 0.5 second runtime
- Cost of 10 is a good baseline
- Cost of 11 or 12 is better
  - If you have decent hardware.
PHP 5.5 Password Hashing API
    git checkout password-compat

A thin wrapper over crypt()
- Simplifies implmentation
- Strong random salt generation
- Can specify cost as int option

password_hash($pass, $algo, $opts)
password_verify($pass, $hash)

github.com/ircmaxell/password_compat
We Can Do
Even Better!
Let's Encrypt
  Instead!
Encrypted BCrypt
git checkout bcrypt-with-encryption

Hash with BCrypt,
Then encrypt result with AES-128.

Requires key storage for the app.
- Not trivial

Use only if needed!
- BCrypt alone is typically sufficient
Brute Forcing
            Encrypted BCrypt
Attack requires low level server compromise!
- SQL Injection is not enough!

localhost/codeinject
 - Simulates code injection that reads source

Any low level compromise
Is No Worse than raw BCrypt
 - BCrypt is the baseline.
The Future
The Future
scrypt
 - Sequential Memory Hard
 - Uses a LOT of memory (32mb / hash)
 - Harder to brute-force than bcrypt

But it's VERY new
- In cryptography terms at least
- Not proven enough for use (yet)
The Future
Password Hashing Competition
- Currently being setup
- Aims to pick "standard" password hashing
algorithm
- A community effort
The Future
Brute Forcing Word Lists
- Complex combinations of words
- "horse correct battery staple"

Brute Forcing Grammar
- "I don't want no cookies"

Brute Forcing Structures
- URLs, Email Addresses, URLs, etc
Anthony Ferrara
 joind.in/7792
  @ircmaxell
ircmaxell@php.net
blog.ircmaxell.com
youtube.com/ircmaxell