Hash, Cryptography, and PHP
Slide link
Outline
-
About me
-
Why?
-
Hash
-
Encrypt
-
Decrypt
-
My Experiences
About me
-
Peter
-
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
-
Salt -
===
$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
-
BCrypt
-
Argon2 (recommended)
password_hash function
-
PHP >= 5.5.9
-
PHP < 5.5.9
$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
Mcrypt
Don't use this.
2015-secure-php-data-encryption
if-you-re-typing-word-mcrypt-into-your-code-you-re-doing-it-wrong
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?
Hash, Cryptography, and PHP
By peter279k
Hash, Cryptography, and PHP
PHPConf Japan 2019
- 2,868