Hash, Cryptography, and PHP

Slide link

Outline

  • About me

  • Why?

  • Hash

  • Encrypt

  • Decrypt

  • My Experiences

About me

  • Peter

  • GitHub

  • Active open source contributor

  • A Back-end developer

  • 3+ years for PHP development

  • PHP 5.3 → PHP 7+

  • No framework→Slim→Laravel

  • Working for ITRI currently

  • Smart Grid technology

Why?

It's about a story....

PM

Resigned Engineer

Me

vacation engineer

senior developer

One Day...

  • PM

    • one will take pregnant vacation

    • one will resign this job

  • PM:You need to take over XXX

  • Me: Okay...

PM

Me

senior developer

One Day...

Demand Response system

  • Ubuntu 14.04

  • Laravel 5.2

  • PHP-5.5.9

  • No README

  • It only has codes!

  • It's part of smart grid system

Storing Password  approach

ag "md5" ./app/Http/

$sql='update `dispatchAdmin` set pw = md5(?),...';

if($objs[0]->pw==md5($password)){
    $newPw = md5($pwd);
......

Something went wrong?

Finally, I got a summary

The password storing approach

  • Receive a password from client

  • Using MD5 hash to hash passwords and store in database

MD5 Risky

hash value databases

MD5 Hash

md5('werty')

"3f931c18b44ac93ac5b4b6c653f7c0b0"

md5('werty')

"3f931c18b44ac93ac5b4b6c653f7c0b0"
mysql> 
mysql> select md5('werty');
+----------------------------------+
| md5('werty')                     |
+----------------------------------+
| 3f931c18b44ac93ac5b4b6c653f7c0b0 |
+----------------------------------+
1 row in set (0.00 sec)

Tricky PHP MD5 Hash Equal

var_dump(md5('240610708') == md5('QNKCDZO'));
var_dump(md5('aabg7XSs') == md5('aabC9RqS'));
var_dump(sha1('aaroZmOk') == sha1('aaK1STfY'));
var_dump(sha1('aaO8zKZF') == sha1('aa3OFF9m'));
var_dump('0010e2' == '1e3');
var_dump('0x1234Ab' == '1193131');
var_dump('0xABCdef' == '     0xABCdef');


md5('240610708') // 0e462097431906509019562988736854
md5('QNKCDZO')  //  0e830400451993494058024219903391

MD5 Decryption

Critical risky.....

MD5 collision

Don't do MD5 hash to store passwords

I don't use MD5 hash.

Can I use SHA-1 hash?

SHA-1 risky

Attack proof

SHA-1 Decryption

mysql> 
mysql> select sha1('werty');
+------------------------------------------+
| sha1('werty')                            |
+------------------------------------------+
| 80a56aa9b9f2116798d51cc86586f309e54c1870 |
+------------------------------------------+

SHA-1 Hash Approach


sha1('werty')
"80a56aa9b9f2116798d51cc86586f309e54c1870"

Improvement Approach

  1. Salt

  2. ===

$salt = 'this_is_salt';

md5($salt . 'password')
"2a413b9d39a34c49a036a08416e3a2b2"

sha1($salt . 'password')
"9588eb8d0bc9c24ee23cadb34296e16e53c19b39"

SHA-256

SHA-384

SHA-512
SHA3-256
SHA3-384
SHA3-512

don't find effective way to crack 

SHA-2 decrypt?

Hash Database

  • Collecting hash results

  • Just like data dictionary files

  • Cannot avoid Brute Force Attack

What kinds of hash should be proper?

Take look at references on php.net

MD5 & SHA-1 function references

MD5 function

Let me organize recommendations

Hash algorithms

password_hash function

$passwordHash = password_hash('secret-password',
    PASSWORD_DEFAULT);

if (password_verify('bad-password', $passwordHash)) {
    // Correct password
} else {
    // Wrong password
}

// password_hash ( 
//    string $password,
//    int $algo
//    [, array $options ] ) : string

password_hash function usage

Password hash algorithms

  • PASSWORD_DEFAULT

    • Use BCRYPT (default as of PHP 5.5.0)

    •  To change over time as new and stronger algorithms

  • PASSWORD_BCRYPT

    • BCRYPT_BLOWFISH

    • salt option

    • cost option (default is 10)

Password hash algorithms

  • PASSWORD_ARGON2I
    PASSWORD_ARGON2ID

    • memory_cost (KB)

    • time_cost

    • threads

password_hash('123456', PASSWORD_ARGON2I)
"$argon2i$v=19$m=65536,t=4,p=1$cERERktQb0xWUG5NNkIvTQ$SP/1Lx36WCO6yhaNgiw0EJXizMmddhrCDLpEc6LyvsY"

password_hash('123456', PASSWORD_ARGON2I, 
    [
        'time_cost' => 10,
        'memory_cost' => 60000,
        'threads' => 2,
    ]
)
"$argon2i$v=19$m=60000,t=10,p=2$Rk0vWWd2MVBEQjZTaWtmWA$YKRjIJk2qXuLutbtSC7zf1ikOkLlXhx2AS7h1/WlbRk"

Argon2I password hash

password_hash('123456', PASSWORD_ARGON2ID, 
    [
        'time_cost' => 10,
        'memory_cost' => 60000,
        'threads' => 2,
    ]
)
"$argon2id$v=19$m=60000,t=10,p=2$T3NlUDFLY0MyVFpxdURkVQ$k6jLfAHP/yIK1QhVL4rKjPky73HRdalDB3xxfMLXH0Q"

Argon2ID password hash

Laravel framework

use Illuminate\Support\Facades\Hash;

$hashed = Hash::make('password', [
    'rounds' => 12
]);


$hashed = Hash::make('password', [
    'memory' => 1024,
    'time' => 2,
    'threads' => 2,
]);

Encryption/Decryption

Cryptography

The real reason

AES Cipher Encryption Mode

  • ECB, Electronic codebook

  • CBC, Cipher-block chaining

  • GCM, Calois / Counter Mode

ECB

  • A plain text block

  • Using AES to encrypt them

  • Need to group the plain texts

    • When cipher texts are loss or tampered

    • Plain text will be wrong after encryption

    • It cannot provide complete cipher validation

    • It's not recommended in any situation

ECB

CBC, Cipher Block Chaining

  • Each plain text block should XOR with previous cipher block

  • Every cipher block depends on previous all plain text blocks

  • To guarantee every message is unique, the first block should use initialized vector

CBC

CTR (Counter mode)

Message authentication code

GMAC

Galois message authentication code mode

GCM mode

  • GMAC + CTR

  • Providing completed encrypted message validation

  • Some message should not be confidential

    • Letting receiver can validate message

    • IP,port number,target IP,IV

    • These messages will be added on MAC value calculation

GCM mode

OpenSSL encrypt (PHP 5.6+)

$plaintext = "message to be encrypted";

$key = "your-secret-key";

$ivlen = openssl_cipher_iv_length($cipher="AES-128-CBC");

$iv = openssl_random_pseudo_bytes($ivlen);
$ciphertext_raw = openssl_encrypt(
    $plaintext,
    $cipher,
    $key,
    $options=OPENSSL_RAW_DATA,
    $iv
);

OpenSSL encrypt (PHP 5.6+)

$hmac = hash_hmac(
    'sha256',
    $ciphertext_raw,
    $key,
    $as_binary=true
);

$ciphertext = base64_encode(
    $iv . $hmac . $ciphertext_raw
);

OpenSSL decrypt (PHP 5.6+)

$c = base64_decode($ciphertext);

$ivlen = openssl_cipher_iv_length(
    $cipher="AES-128-CBC"
);

$iv = substr($c, 0, $ivlen);
$hmac = substr($c, $ivlen, $sha2len=32);

$ciphertext_raw = substr($c, $ivlen+$sha2len);

OpenSSL decrypt (PHP 5.6+)

$original_plaintext = openssl_decrypt(
    $ciphertext_raw,
    $cipher,
    $key,
    $options=OPENSSL_RAW_DATA,
    $iv
);

$calcmac = hash_hmac('sha256',
    $ciphertext_raw,
    $key,
    $as_binary=true
);

OpenSSL decrypt (PHP 5.6+)


if (hash_equals($hmac, $calcmac))
{
    //PHP 5.6+ timing attack safe comparison
    echo $original_plaintext."\n";
}

OpenSSL encrypt (PHP 7.1+)

$key = "your-secret-key";

$plaintext = "message to be encrypted";

$cipher = "aes-128-gcm";

if (in_array($cipher, openssl_get_cipher_methods()))
{
    $ivlen = openssl_cipher_iv_length($cipher);
    $iv = openssl_random_pseudo_bytes($ivlen);
    
    $ciphertext = openssl_encrypt(
        $plaintext,
        $cipher,
        $key,
        $options=0,
        $iv,
        $tag = 'tag');

OpenSSL encrypt (PHP 7.1+)


    //store $cipher, $iv, and $tag for decryption later
    $original_plaintext = openssl_decrypt(
        $ciphertext,
        $cipher,
        $key,
        $options=0,
        $iv,
        $tag
    );

    echo $original_plaintext."\n";
}

Sodium

Modern cryptography


NaCl: Networking and Cryptography library

Sodium details

  • Curve25519 (ECC)

  • Salsa20 and ChaCha20

  • Poly1305

  • Ed25519

  • Argon2 and Scrypt

  • AES-GCM

Sodium details

  • Avoid side channel attacks

  • PHP 7.2+ supports

  • PHP 7.0, PHP 7.1 via PECL

Sodium use cases

  • Encrypt/Authenticate with a shared key

    • Salsa20

  • Sending secret messages

    • XSalsa20 to encrypt

    • Poly1305 for MAC, and XS25519 for key exchange

  • Digital signature

    •  Elliptic Curve algorithm, Ed25519.

  • Authenticated encryption with AES-GCM

  • Store passwords safely, Argon2i

  • Derive a key from a user’s password, Argon2i

Encrypt/Authenticate with a shared key

$msg = 'This is a super secret message!';

// Generating an encryption key and a nonce
$key   = random_bytes(
    SODIUM_CRYPTO_SECRETBOX_KEYBYTES
); // 256 bit

$nonce = random_bytes(
    SODIUM_CRYPTO_SECRETBOX_NONCEBYTES
); // 24 bytes
 
// Encrypt
$ciphertext = sodium_crypto_secretbox(
    $msg, $nonce, $key
);

// Decrypt
$plaintext = sodium_crypto_secretbox_open(
    $ciphertext, $nonce, $key
);
 
echo $plaintext === $msg ? 'Success' : 'Error';

Sending secret messages

Generating their public/private keys

$bob_key_pair = sodium_crypto_box_keypair();
$bob_public_key = sodium_crypto_box_publickey(
    $bob_key_pair
);
$bob_private_key = sodium_crypto_box_secretkey(
    $bob_key_pair
);

$alice_key_pair = sodium_crypto_box_keypair();
$alice_public_key = sodium_crypto_box_publickey(
    $alice_key_pair
);
$alice_private_key = sodium_crypto_box_secretkey(
    $alice_key_pair
);

Bob sends encrtyped secret message to Alice

$secret_message = 'secret-message';

$throw_off_bytes = random_bytes(
    SODIUM_CRYPTO_BOX_NONCEBYTES
);

$encryption_key = 
sodium_crypto_box_keypair_from_secretkey_and_publickey(
    $bob_private_key,
    $alice_public_key
);

$encrypted = sodium_crypto_box(
    $secret_message,
    $throw_off_bytes,
    $encryption_key
);

echo base64_encode($encrypted);

Alice receives encrypted secret message

$throw_off_bytes = random_bytes(
    SODIUM_CRYPTO_BOX_NONCEBYTES
);

$decryption_key = 
sodium_crypto_box_keypair_from_secretkey_and_publickey(
    $alice_private_key,
    $bob_public_key
);

$decrypted = sodium_crypto_box_open(
    $encrypted,
    $throw_off_bytes,
    $decryption_key
);

echo $decrypted;

Digital signature

$key_pair = sodium_crypto_sign_keypair();

$public_key = sodium_crypto_sign_publickey(
    $key_pair
);
$secret_key = sodium_crypto_sign_secretkey(
    $key_pair
);

$message = 'message';

$signed_message = sodium_crypto_sign(
    $message,
    $secret_key
);
$signature = sodium_crypto_sign_detached(
    $message,
    $secret_key
);

Digital signature

$original = sodium_crypto_sign_open(
    $signed_message,
    $public_key
);

echo $original === $message ? 'ok' : 'error';

echo sodium_crypto_sign_verify_detached(
    $signature,
    $message,
    $public_key
) ? 'Signed is ok' : 'Error signature';

Authenticated encryption with AES-GCM

if (!sodium_crypto_aead_aes256gcm_is_available()) {
    exit(1);
}

$message = 'secret message';
$key = random_bytes(
    SODIUM_CRYPTO_AEAD_AES256GCM_KEYBYTES
);

$nonce = random_bytes(
    SODIUM_CRYPTO_AEAD_AES256GCM_NPUBBYTES
);

$ad = 'additional public data';

$cipher_text = sodium_crypto_aead_aes256gcm_encrypt(
    $message,
    $ad,
    $nonce,
    $key
);

Authenticated encryption with AES-GCM

echo base64_encode($cipher_text);

$decrypted = 
sodium_crypto_aead_aes256gcm_decrypt(
    $cipher_text,
    $ad,
    $nonce,
    $key
);

Store passwords safely, Argon2i

$password = 'password';

$hash = sodium_crypto_pwhash_str(
    $password,
    SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
    SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);

echo sodium_crypto_pwhash_str_verify(
    $hash,
    $password
) ? 'Ok' : 'Error';

$password_hash = password_hash(
    $password,
    PASSWORD_ARGON2I
);
echo password_verify(
    $password,
    $password_hash
) ? 'Ok' : 'Error';

ARGON2I hash comparison

Comparison hash

// 97
"$argon2id$v=19$m=65536,t=2,p=1$IOiuZanSOlw6Sn8C0AX9GA$QWjVw9IEfbY5a5hMu/NJcxBRaAYFS6ApmR5T62nClqc"

// 96
"$argon2i$v=19$m=65536,t=4,p=1$eW9lSDQzL1hMWnF0RXAzSw$yRKvmnPj1k/bb4U5Y55eZFvga7JNr1WAQC2xnFlJkeg"

Argon2I hash compatibility

// since php-7.3+ has the PASSWORD_ARGON2ID

echo sodium_crypto_pwhash_str_verify(
    $hash,
    $password
) ? 'Ok' : 'Error';

$password_hash = password_hash(
    $password,
    PASSWORD_ARGON2ID
);
echo password_verify(
    $password,
    $password_hash
) ? 'Ok' : 'Error';

Argon2I hash compatibility

// 97
"$argon2id$v=19$m=65536,t=2,p=1$IOiuZanSOlw6Sn8C0AX9GA$QWjVw9IEfbY5a5hMu/NJcxBRaAYFS6ApmR5T62nClqc"

// 97
"$argon2id$v=19$m=65536,t=4,p=1$NDY1Q1hVWFlYc3JNQ1hNRw$Gz4h4yb5yf4jtKLnKcnjQgEds+Bd4AlXn1K/JSXNoZI"

Argon2I hash compatibility check

$password = 'password';

$sodium_hash = sodium_crypto_pwhash_str(
    $password,
    SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
    SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE
);

$password_hash = password_hash(
    $password,
    PASSWORD_ARGON2ID
);

Argon2I hash compatibility check

echo sodium_crypto_pwhash_str_verify(
    $password_hash,
    $password
) ? 'Ok' : 'Error';

echo password_verify(
    $password,
    $sodium_hash
) ? 'Ok' : 'Error';

Derive a key from a user’s password, Argon2i

Generating binary key of 32 bytes

$password = 'password';

$salt = random_bytes(
    SODIUM_CRYPTO_PWHASH_SALTBYTES
);

$key = sodium_crypto_pwhash(
    32,
    $password,
    $salt,
    SODIUM_CRYPTO_PWHASH_OPSLIMIT_INTERACTIVE,
    SODIUM_CRYPTO_PWHASH_MEMLIMIT_INTERACTIVE,
    SODIUM_CRYPTO_PWHASH_ALG_DEFAULT
);

Sodium utility

sodium_memzero($sensitive_data);

sodium_hex2bin(
    string $hex 
    [, string $ignore ]
) : string

sodium_bin2hex(
    string $bin
) : string

Sodium utility

sodium_bin2base64(
    string $bin,
    int $id
) : string

sodium_base642bin(
    string $b64,
    int $id
    [, string $ignore ]
) : string

PHP cryptography package

Sodium wrapper

OpenSSL wrapper

Sodium wrapper

Sodium wrapper

Sodium wrapper

  • Sodium has been released since 2018

  • Let's do open source contributions!

OpenSSL wrapper

OpenSSL wrapper

  • OpenSSL has been released for whiles

  • They've completed wrapper ecosystem

My Experiences

Hash Migration

Demand Response system

Hash Migration Approach

  • Reset user passwords with Bcrypt

  • Forcing user to reset all passwords

Encrypt/Decrypt

Application

CIM Model Application

  • Follow IEC-61970 standard

    • Formatting Power Plant Bid Data

  • Follow IEC-62325 standard

    • Follow message transmission rule

    • Receiving Bid Data Results

    • AES encryption

Summary

  • Migrating to PHP 7+ (recommended PHP 7.2+)

  • Don't use Mcrypt

    • ​Using OpenSSL or Sodium instead

  • Don't use MD5 hash

    • Using password_hash function instead

  • Docker Demo enviroment

Any questions?