加密/解密/雜湊 看 PHP 版本的演進
Encrypt/Decrypt/Hash are in PHP versions
Slide link

Outline
-
About me
-
Why?
-
雜湊/Hash
-
加密/Encrypt
-
解密/Decrypt
-
Demo
About me
-
Peter
-
Working for ITRI currently
-
A Back-end developer
-
3+ years for PHP development
-
PHP 5.3 → PHP 7+
-
Little with Python
-
No framework→Slim→Laravel
-
3 years about open source contributions

Why?
故事是這樣的...
It's about a story....




PM
Resigned Engineer
Me
leave engineer
有一天呢
-
PM:XXX 要請假,OOO 要離職
-
PM:你要去接 OOXX 平台
-
我:恩好
OOXX platform
-
Ubuntu 14.04
-
Laravel 5.2
-
PHP-5.5.9
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
OOXX平台儲存密碼方式
-
Receive a password from client
-
Using MD5 hash to hash passwords and store in database
MD5風險
hash value databases

MD5雜湊
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雜湊尋找

還有更嚴重問題.....
MD5 collision
I don't use MD5 hash.
Can I use SHA-1 hash?
SHA-1雜湊風險
一樣有碰撞的風險

Attack proof

SHA-1找尋示範

mysql> mysql> select sha1('werty'); +------------------------------------------+ | sha1('werty') | +------------------------------------------+ | 80a56aa9b9f2116798d51cc86586f309e54c1870 | +------------------------------------------+
SHA-1雜湊
sha1('werty') "80a56aa9b9f2116798d51cc86586f309e54c1870"
補救方法(還是換個雜湊法吧)
-
加鹽 -
增加decrypt的困難度 -
===
$salt = 'this_is_salt'; md5($salt . 'password') "2a413b9d39a34c49a036a08416e3a2b2" sha1($salt . 'password') "9588eb8d0bc9c24ee23cadb34296e16e53c19b39"
SHA-256
SHA-384
SHA-512
SHA3-256
SHA3-384
SHA3-512
尚未發現有效破解方式
SHA-2 decrypt?


雜湊資料庫
-
收集明文雜湊過後的值
-
類似字典文檔
-
無法防止暴力破解
-
規定讓使用者複雜度較高的密碼
那我還可以用什麼雜湊演算法保護密碼?
看看PHP官網怎麼說?
MD5 function

MD5 function

看看人家的建議怎麼說?
PHP
The Right Way
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基本用法
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, ]);
加密/解密
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
-
一段明文
-
進行AES加密
-
需要對明文進行分組
-
密文被篡改時
-
解密後對應的明文分組也會出錯
-
不能提供對密文的完整性驗證
-
在任何情況下都不推薦使用
-
ECB

CBC, Cipher Block Chaining
-
每個明文塊先與前一個密文塊進行互斥 (XOR)
-
進行加密/解密
-
每個密文塊都依賴於它前面的所有明文塊
-
為了保證每條訊息的唯一性,在第一個塊中需要使用初始化向量。
CBC

CTR (Counter mode)

Message authentication code

GMAC
Galois message authentication code mode

GCM mode
-
GMAC + CTR
-
提供對於加密訊息完整驗證
-
有些訊息不需要保密
-
讓接收者確認與驗證
-
IP,port number,target IP,IV
-
將這類訊息以額外訊息加入到MAC值計算
-
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
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?
加密/解密/雜湊 看 PHP 版本的演進 Encrypt/Decrypt/Hash are in PHP versions
加密/解密/雜湊 看 PHP 版本的演進
By peter279k
加密/解密/雜湊 看 PHP 版本的演進
加密/解密/雜湊 看 PHP 版本的演進
- 2,361