加密/解密/雜湊 看 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 版本的演進
By peter279k
加密/解密/雜湊 看 PHP 版本的演進
加密/解密/雜湊 看 PHP 版本的演進
- 2,238