關(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的情況下,有如下代碼:

  1. function?setUnsafeCookie($name,?$cookieData,?$key)
  2. {
  3. ????$iv?=?mcrypt_create_iv(16,?MCRYPT_DEV_URANDOM);
  4. ????return?setcookie(
  5. ????????$name,
  6. ????????base64_encode(
  7. ????????????$iv.
  8. ????????????mcrypt_encrypt(
  9. ????????????????MCRYPT_RIJNDAEL_128,
  10. ????????????????$key,
  11. ????????????????json_encode($cookieData),
  12. ????????????????MCRYPT_MODE_CBC,
  13. ????????????????$iv
  14. ????????????)
  15. ????????)
  16. ????);
  17. }
  18. function?getUnsafeCookie($name,?$key)
  19. {
  20. ????if?(!isset($_COOKIE[$name]))?{
  21. ????????return?null;
  22. ????}
  23. ????$decoded?=?base64_decode($_COOKIE[$name]);
  24. ????$iv?=?mb_substr($decoded,?0,?16,?'8bit');
  25. ????$ciphertext?=?mb_substr($decoded,?16,?null,?'8bit');
  26. ????$decrypted?=?rtrim(
  27. ????????mcrypt_decrypt(
  28. ????????????MCRYPT_RIJNDAEL_128,
  29. ????????????$key,
  30. ????????????$ciphertext,
  31. ????????????MCRYPT_MODE_CBC,
  32. ????????????$iv
  33. ????????),
  34. ????????"\0"
  35. ????);
  36. ????return?json_decode($decrypted,?true);
  37. }

上面的代碼提供了在密碼段鏈接模塊的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ù)組:

  1. array(2)?{
  2. ??["admin"]=>
  3. ??int(0)
  4. ??["user"]=>
  5. ??"aaaaaaaaaaaaa"
  6. ?}

但在僅僅改變初始化向量的一個字節(jié)之后,我們就能夠改寫我們的閱讀信息:

  1. array(2)?{
  2. ??["admin"]=>
  3. ??int(1)
  4. ??["user"]=>
  5. ??"aaaaaaaaaaaaa"
  6. }

根據(jù)底層應(yīng)用程序的設(shè)置方法,你或許可以翻轉(zhuǎn)一位進而提升成為一名管理員。即使你的cookie是加密的。 如果你想再現(xiàn)我們的結(jié)果,我們的加密密鑰是十六進制下的:000102030405060708090a0b0c0d0e0f

0x04 認證

如上所述,認證旨在提供信息的完整性(我們指顯著抗篡改能力),而這證明它來自預(yù)期的源(真實性)。這樣做的典型方法是,為信息計算一個密鑰散列消息認證碼(HMAC的簡稱),并將信息與它連結(jié)。

  1. function?hmac_sign($message,?$key)
  2. {
  3. ????return?hash_hmac('sha256',?$message,?$key)?.?$message;
  4. }
  5. function?hmac_verify($bundle,?$key)
  6. {
  7. ????$msgMAC?=?mb_substr($bundle,?0,?64,?'8bit');
  8. ????$message?=?mb_substr($bundle,?64,?null,?'8bit');
  9. ????return?hash_equals(
  10. ????????hash_hmac('sha256',?$message,?$key),
  11. ????????$msgMAC
  12. ????);
  13. }

重要的是,這里使用一個適當(dāng)?shù)墓9ぞ?,如HMAC,而不僅僅是一個簡單的散列函數(shù)。

  1. function?unsafe_hash_sign($message,?$key)
  2. {
  3. ????return?md5($key.$message)?.?$message;
  4. }
  5. function?unsafe_hash_verify($bundle,?$key)
  6. {
  7. ????$msgHash?=?mb_substr($bundle,?0,?64,?'8bit');
  8. ????$message?=?mb_substr($bundle,?64,?null,?'8bit');
  9. ????return?md5($key.$message)?==?$msgHash;
  10. }

我在這兩個函數(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

  1. /*
  2. //?At?some?point,?we?run?this?command:
  3. $key?=?Sodium::randombytes_buf(Sodium::CRYPTO_AEAD_CHACHA20POLY1305_KEYBYTES);
  4. */
  5. /**
  6. ?*?Store?ciphertext?in?a?cookie
  7. ?*
  8. ?*?@param?string?$name?-?cookie?name
  9. ?*?@param?mixed?$cookieData?-?cookie?data
  10. ?*?@param?string?$key?-?crypto?key
  11. ?*/
  12. function?setSafeCookie($name,?$cookieData,?$key)
  13. {
  14. ????$nonce?=?Sodium::randombytes_buf(Sodium::CRYPTO_SECRETBOX_NONCEBYTES);
  15. ????return?setcookie(
  16. ????????$name,
  17. ????????base64_encode(
  18. ????????????$nonce.
  19. ????????????Sodium::crypto_secretbox(
  20. ????????????????json_encode($cookieData),
  21. ????????????????$nonce,
  22. ????????????????$key
  23. ????????????)
  24. ????????)
  25. ????);
  26. }
  27. /**
  28. ?*?Decrypt?a?cookie,?expand?to?array
  29. ?*
  30. ?*?@param?string?$name?-?cookie?name
  31. ?*?@param?string?$key?-?crypto?key
  32. ?*/
  33. function?getSafeCookie($name,?$key)
  34. {
  35. ????$hexSize?=?2?*?Sodium::Sodium::CRYPTO_SECRETBOX_NONCEBYTES;
  36. ????if?(!isset($_COOKIE[$name]))?{
  37. ????????return?array();
  38. ????}
  39. ????$decoded?=?base64_decode($_COOKIE[$name]);
  40. ????$nonce?=?mb_substr($decoded,?0,?$hexSize,?'8bit');
  41. ????$ciphertext?=?mb_substr($decoded,?$hexSize,?null,?'8bit');
  42. ????$decrypted?=?Sodium::crypto_secretbox_open(
  43. ????????$ciphertext,
  44. ????????$nonce,
  45. ????????$key
  46. ????);
  47. ????if?(empty($decrypted))?{
  48. ????????return?array();
  49. ????}
  50. ????return?json_decode($decrypted,?true);
  51. }

對于沒有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)督下,使用具有韌性被證實記錄的庫,而不是自己在那里閉門造車,你會好得多。