關(guān)于加密技術(shù)和認證的區(qū)別
關(guān)于加密,我們通常理解就是把正常的可讀可寫的數(shù)據(jù)進行處理,有的只是簡單的隱藏顯示屬性,有的對文件頭部進行了修改,但是加密并不是認證。
這一概念本身并不困難,但在表面之下,還有更多豐富的細節(jié)和玄妙之處有待發(fā)現(xiàn)。本文就是講述開發(fā)者對于加密和認證二者的混淆與誤用,并附上了優(yōu)秀的解決方案。
0x01 加密與認證之間有哪些區(qū)別?
加密是呈現(xiàn)信息,使其在沒有正確的密鑰情況下,變得難以卒讀的過程。在簡單的對稱加密中,同一個密鑰被用于加密和解密。在非對稱加密中,可以使用用戶的公鑰對信息加密,使得只有對應(yīng)私鑰的擁有者才能讀取它。
認證是呈現(xiàn)信息,使其抗篡改(通常在某一非常低的概率之內(nèi),小于1除以已知宇宙中粒子的數(shù)量),同時也證明它起源于預(yù)期發(fā)送者的過程。
注意:當(dāng)本文提及真實性時,是專門指的信息真實性,而不是身份真實性。這是一個PKI和密鑰管理問題,我們可能在未來的博客中詳細說明。
就CIA triad而言:加密提供機密性,認證提供完整性。
加密不提供完整性;被篡改的信息(通常)還能解密,但結(jié)果通常會是垃圾。單獨加密也不抑制惡意第三方發(fā)送加密信息。
認證不提供機密性;可以為明文信息提供抗篡改。
在程序員中,常見的錯誤是混淆這兩個概念。你能很容易找到這樣的一個庫或者框架:加密cookie數(shù)據(jù),然后在僅僅解密它之后就無條件地信任與使用之。
0x02 加密
我們之前定義了加密,并且詳細說明了它是提供機密性,但不提供完整性和真實性的。你可以篡改加密信息,并將產(chǎn)生的垃圾給予接收者。而且你甚至可以利用這種垃圾產(chǎn)生機制,來繞過安全控制。
考慮在加密cookie的情況下,有如下代碼:
- function?setUnsafeCookie($name,?$cookieData,?$key)
- {
- ????$iv?=?mcrypt_create_iv(16,?MCRYPT_DEV_URANDOM);
- ????return?setcookie(
- ????????$name,
- ????????base64_encode(
- ????????????$iv.
- ????????????mcrypt_encrypt(
- ????????????????MCRYPT_RIJNDAEL_128,
- ????????????????$key,
- ????????????????json_encode($cookieData),
- ????????????????MCRYPT_MODE_CBC,
- ????????????????$iv
- ????????????)
- ????????)
- ????);
- }
- function?getUnsafeCookie($name,?$key)
- {
- ????if?(!isset($_COOKIE[$name]))?{
- ????????return?null;
- ????}
- ????$decoded?=?base64_decode($_COOKIE[$name]);
- ????$iv?=?mb_substr($decoded,?0,?16,?'8bit');
- ????$ciphertext?=?mb_substr($decoded,?16,?null,?'8bit');
- ????$decrypted?=?rtrim(
- ????????mcrypt_decrypt(
- ????????????MCRYPT_RIJNDAEL_128,
- ????????????$key,
- ????????????$ciphertext,
- ????????????MCRYPT_MODE_CBC,
- ????????????$iv
- ????????),
- ????????"\0"
- ????);
- ????return?json_decode($decrypted,?true);
- }
上面的代碼提供了在密碼段鏈接模塊的AES加密,如果你傳入32字節(jié)的字符串作為$key,你甚至可以聲稱,為你的cookie提供了256位的AES加密,然后人們可能被誤導(dǎo)相信它是安全的。
0x03 如何攻擊未經(jīng)認證的加密
比方說,在登錄到這個應(yīng)用程序之后,你會發(fā)現(xiàn)你收到一個會話cookie,看起來就像
kHv9PAlStPZaZJHIYXzyCnuAhWdRRK7H0cNVUCwzCZ4M8fxH79xIIIbznxmiOxGQ7td8LwTzHFgwBmbqWuB+sQ==
讓我們改變一個字節(jié)的第一塊(初始化向量),并反復(fù)發(fā)送我們的新的cookie,直到出現(xiàn)一些變化。應(yīng)該采取共4096次HTTP請求,以嘗試變量IV所有可能的單字節(jié)變化。在上面的例子中,經(jīng)過2405次請求后,我們得到一個看起來像這樣的字符串:
kHv9PAlStPZaZZHIYXzyCnuAhWdRRK7H0cNVUCwzCZ4M8fxH79xIIIbznxmiOxGQ7td8LwTzHFgwBmbqWuB+sQ==
相比之下,在base64編碼的cookie中只有一個字符不同(kHv9PAlStPZaZ J vs kHv9PAlStPZaZ Z):
- kHv9PAlStPZaZJHIYXzyCnuAhWdRRK7H0cNVUCwzCZ4M8fxH79xIIIbznxmiOxGQ7td8LwTzHFgwBmbqWuB+sQ==
+ kHv9PAlStPZaZZHIYXzyCnuAhWdRRK7H0cNVUCwzCZ4M8fxH79xIIIbznxmiOxGQ7td8LwTzHFgwBmbqWuB+sQ==
我們存儲在這個cookie里的原始數(shù)據(jù),是看起來像這樣的數(shù)組:
- array(2)?{
- ??["admin"]=>
- ??int(0)
- ??["user"]=>
- ??"aaaaaaaaaaaaa"
- ?}
但在僅僅改變初始化向量的一個字節(jié)之后,我們就能夠改寫我們的閱讀信息:
- array(2)?{
- ??["admin"]=>
- ??int(1)
- ??["user"]=>
- ??"aaaaaaaaaaaaa"
- }
根據(jù)底層應(yīng)用程序的設(shè)置方法,你或許可以翻轉(zhuǎn)一位進而提升成為一名管理員。即使你的cookie是加密的。 如果你想再現(xiàn)我們的結(jié)果,我們的加密密鑰是十六進制下的:000102030405060708090a0b0c0d0e0f
0x04 認證
如上所述,認證旨在提供信息的完整性(我們指顯著抗篡改能力),而這證明它來自預(yù)期的源(真實性)。這樣做的典型方法是,為信息計算一個密鑰散列消息認證碼(HMAC的簡稱),并將信息與它連結(jié)。
- function?hmac_sign($message,?$key)
- {
- ????return?hash_hmac('sha256',?$message,?$key)?.?$message;
- }
- function?hmac_verify($bundle,?$key)
- {
- ????$msgMAC?=?mb_substr($bundle,?0,?64,?'8bit');
- ????$message?=?mb_substr($bundle,?64,?null,?'8bit');
- ????return?hash_equals(
- ????????hash_hmac('sha256',?$message,?$key),
- ????????$msgMAC
- ????);
- }
重要的是,這里使用一個適當(dāng)?shù)墓9ぞ?,如HMAC,而不僅僅是一個簡單的散列函數(shù)。
- function?unsafe_hash_sign($message,?$key)
- {
- ????return?md5($key.$message)?.?$message;
- }
- function?unsafe_hash_verify($bundle,?$key)
- {
- ????$msgHash?=?mb_substr($bundle,?0,?64,?'8bit');
- ????$message?=?mb_substr($bundle,?64,?null,?'8bit');
- ????return?md5($key.$message)?==?$msgHash;
- }
我在這兩個函數(shù)名前面加了unsafe,是因為它們還是易受到一些缺點的危害:
Timing Attacks
Chosen Prefix Attacks on MD5 (PDF)
Non-strict equality operator bugs (largely specific to PHP)
現(xiàn)在,我們有點接近我們強大的對稱加密認證的目標(biāo)。目前仍有幾個問題,如:
如果我們的原始信息以空字節(jié)結(jié)尾會發(fā)生什么?
有沒有一個比mcrypt擴展庫默認使用的更好的填充策略?
由于使用AES,有哪些通信方面是易受攻擊的?
幸運的是,這些問題在現(xiàn)有的加密函數(shù)庫中已有了解答。我們強烈推薦你使用現(xiàn)有的庫,而不是寫自己的加密功能。對于PHP開發(fā)人員來說,你應(yīng)該使用defuse/php-encryption(或者libsodium)。
0x05 用Libsodium安全加密Cookies
- /*
- //?At?some?point,?we?run?this?command:
- $key?=?Sodium::randombytes_buf(Sodium::CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES);
- */
- /**
- ?*?Store?ciphertext?in?a?cookie
- ?*
- ?*?@param?string?$name?-?cookie?name
- ?*?@param?mixed?$cookieData?-?cookie?data
- ?*?@param?string?$key?-?crypto?key
- ?*/
- function?setSafeCookie($name,?$cookieData,?$key)
- {
- ????$nonce?=?Sodium::randombytes_buf(Sodium::CRYPTO_SECRETBOX_NONCEBYTES);
- ????return?setcookie(
- ????????$name,
- ????????base64_encode(
- ????????????$nonce.
- ????????????Sodium::crypto_secretbox(
- ????????????????json_encode($cookieData),
- ????????????????$nonce,
- ????????????????$key
- ????????????)
- ????????)
- ????);
- }
- /**
- ?*?Decrypt?a?cookie,?expand?to?array
- ?*
- ?*?@param?string?$name?-?cookie?name
- ?*?@param?string?$key?-?crypto?key
- ?*/
- function?getSafeCookie($name,?$key)
- {
- ????$hexSize?=?2?*?Sodium::Sodium::CRYPTO_SECRETBOX_NONCEBYTES;
- ????if?(!isset($_COOKIE[$name]))?{
- ????????return?array();
- ????}
- ????$decoded?=?base64_decode($_COOKIE[$name]);
- ????$nonce?=?mb_substr($decoded,?0,?$hexSize,?'8bit');
- ????$ciphertext?=?mb_substr($decoded,?$hexSize,?null,?'8bit');
- ????$decrypted?=?Sodium::crypto_secretbox_open(
- ????????$ciphertext,
- ????????$nonce,
- ????????$key
- ????);
- ????if?(empty($decrypted))?{
- ????????return?array();
- ????}
- ????return?json_decode($decrypted,?true);
- }
對于沒有l(wèi)ibsodium庫的開發(fā)人員,我們的一個博客讀者,提供了一個安全cookie實現(xiàn)的例子,其使用了defuse/php-encryption(我們推薦的PHP庫)。
0x06 使用關(guān)聯(lián)數(shù)據(jù)的認證加密
在我們前面的示例中,我們集中精力于,同時使用加密和認證,使其作為必須小心使用的單獨組件,以避免加密的悲劇。具體而言,我們專注于密碼段鏈接模塊的AES加密。
然而,密碼學(xué)家已經(jīng)開發(fā)出更新,更具有彈性的加密模型,其加密和認證信息在同一操作。這些模型被稱為AEAD模型(Authenticated Encryption with Associated Data)。關(guān)聯(lián)數(shù)據(jù)意味著,無論你的應(yīng)用程序需要什么進行認證,都不加密。
AEAD模型通常用于有狀態(tài)的目的,如網(wǎng)絡(luò)通信中,其中一個隨機數(shù)可以很容易地管理。
AEAD兩個可靠的實現(xiàn)是AES-GCM和ChaCha20-Poly1305。
AES-GCM是Galois/Counter模式中的高級加密標(biāo)準(zhǔn)(又名Rijndael算法加密)。這種模式在OpenSSL的最新版本中加入,但它目前在PHP中還不被支持。
ChaCha20-Poly1305結(jié)合了ChaCha20流密碼與Poly1305消息認證碼。這種模式在libsodium PHP擴展可用。Sodium::crypto_aead_chacha20poly1305_encrypt() Sodium::crypto_aead_chacha20poly1305_decrypt()
總結(jié)一下,你該記住的
加密不是認證
加密提供機密性
認證提供完整性
將兩者混為一談你就得自擔(dān)風(fēng)險
為了完成CIA triad,你需要單獨解決可用性。這通常不是一個加密問題。
更重要的是:在密碼學(xué)專家的監(jiān)督下,使用具有韌性被證實記錄的庫,而不是自己在那里閉門造車,你會好得多。










