加密/解密/雜湊 看 PHP 版本的演進

 

Encrypt/Decrypt/Hash are in PHP versions

Slide link

Outline

  • About me

  • Why?

  • 雜湊/Hash

  • 加密/Encrypt

  • 解密/Decrypt

  • Demo

About me

  • Peter

  • GitHub

  • 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"

補救方法(還是換個雜湊法吧)

  1. 加鹽

  2. 增加decrypt的困難度

  3. ===

$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

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基本用法

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

密碼學

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?