正確地使用加密與認證技術

wyzsk發表於2020-08-19
作者: XDSEC · 2015/05/14 14:23

在密碼學專家之中,“加密並不是認證”是一個簡單的共識。但很多不瞭解密碼學的開發者,並不知道這句話的意義。如果這個知識更廣為人知和深入理解,那麼將會避免很多的設計錯誤。

這一概念本身並不困難,但在表面之下,還有更多豐富的細節和玄妙之處有待發現。本文就是講述開發者對於加密和認證二者的混淆與誤用,並附上了優秀的解決方案。

0x01 加密與認證之間有哪些區別?


加密是呈現資訊,使其在沒有正確的金鑰情況下,變得難以卒讀的過程。在簡單的對稱加密中,同一個金鑰被用於加密和解密。在非對稱加密中,可以使用使用者的公鑰對資訊加密,使得只有對應私鑰的擁有者才能讀取它。

認證是呈現資訊,使其抗篡改(通常在某一非常低的機率之內,小於1除以已知宇宙中粒子的數量),同時也證明它起源於預期傳送者的過程。

注意:當本文提及真實性時,是專門指的資訊真實性,而不是身份真實性。這是一個PKI和金鑰管理問題,我們可能在未來的部落格中詳細說明。

CIA triad而言:加密提供機密性,認證提供完整性。

加密不提供完整性;被篡改的資訊(通常)還能解密,但結果通常會是垃圾。單獨加密也不抑制惡意第三方傳送加密資訊。

認證不提供機密性;可以為明文資訊提供抗篡改。

在程式設計師中,常見的錯誤是混淆這兩個概念。你能很容易找到這樣的一個庫或者框架:加密cookie資料,然後在僅僅解密它之後就無條件地信任與使用之。

0x02 加密


我們之前定義了加密,並且詳細說明了它是提供機密性,但不提供完整性和真實性的。你可以篡改加密資訊,並將產生的垃圾給予接收者。而且你甚至可以利用這種垃圾產生機制,來繞過安全控制。

考慮在加密cookie的情況下,有如下程式碼:

#!php
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位元組的字串作為$key,你甚至可以聲稱,為你的cookie提供了256位的AES加密,然後人們可能被誤導相信它是安全的。

0x03 如何攻擊未經認證的加密


比方說,在登入到這個應用程式之後,你會發現你收到一個會話cookie,看起來就像

kHv9PAlStPZaZJHIYXzyCnuAhWdRRK7H0cNVUCwzCZ4M8fxH79xIIIbznxmiOxGQ7td8LwTzHFgwBmbqWuB+sQ==

讓我們改變一個位元組的第一塊(初始化向量),並反覆傳送我們的新的cookie,直到出現一些變化。應該採取共4096次HTTP請求,以嘗試變數IV所有可能的單位元組變化。在上面的例子中,經過2405次請求後,我們得到一個看起來像這樣的字串:

kHv9PAlStPZaZZHIYXzyCnuAhWdRRK7H0cNVUCwzCZ4M8fxH79xIIIbznxmiOxGQ7td8LwTzHFgwBmbqWuB+sQ==

相比之下,在base64編碼的cookie中只有一個字元不同(kHv9PAlStPZaZ J vs kHv9PAlStPZaZ Z):

- kHv9PAlStPZaZJHIYXzyCnuAhWdRRK7H0cNVUCwzCZ4M8fxH79xIIIbznxmiOxGQ7td8LwTzHFgwBmbqWuB+sQ==

+ kHv9PAlStPZaZZHIYXzyCnuAhWdRRK7H0cNVUCwzCZ4M8fxH79xIIIbznxmiOxGQ7td8LwTzHFgwBmbqWuB+sQ==

我們儲存在這個cookie裡的原始資料,是看起來像這樣的陣列:

#!php
array(2) {
  ["admin"]=>
  int(0)
  ["user"]=>
  "aaaaaaaaaaaaa"
 }

但在僅僅改變初始化向量的一個位元組之後,我們就能夠改寫我們的閱讀資訊:

#!php
array(2) {
  ["admin"]=>
  int(1)
  ["user"]=>
  "aaaaaaaaaaaaa"
}

根據底層應用程式的設定方法,你或許可以翻轉一位進而提升成為一名管理員。即使你的cookie是加密的。 如果你想再現我們的結果,我們的加密金鑰是十六進位制下的:000102030405060708090a0b0c0d0e0f

0x04 認證


如上所述,認證旨在提供資訊的完整性(我們指顯著抗篡改能力),而這證明它來自預期的源(真實性)。這樣做的典型方法是,為資訊計算一個金鑰雜湊訊息認證碼(HMAC的簡稱),並將資訊與它連結。

#!php
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
    );
}

重要的是,這裡使用一個適當的雜湊工具,如HMAC,而不僅僅是一個簡單的雜湊函式。

#!php
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;
}

我在這兩個函式名前面加了unsafe,是因為它們還是易受到一些缺點的危害:

  1. Timing Attacks

  2. Chosen Prefix Attacks on MD5 (PDF)

  3. Non-strict equality operator bugs (largely specific to PHP)

現在,我們有點接近我們強大的對稱加密認證的目標。目前仍有幾個問題,如:

  1. 如果我們的原始資訊以空位元組結尾會發生什麼?

  2. 有沒有一個比mcrypt擴充套件庫預設使用的更好的填充策略?

  3. 由於使用AES,有哪些通訊方面是易受攻擊的?

幸運的是,這些問題在現有的加密函式庫中已有了解答。我們強烈推薦你使用現有的庫,而不是寫自己的加密功能。對於PHP開發人員來說,你應該使用defuse/php-encryption(或者libsodium)。

0x05 用Libsodium安全加密Cookies


#!php

/*
// 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);
}

對於沒有libsodium庫的開發人員,我們的一個部落格讀者,提供了一個安全cookie實現的例子,其使用了defuse/php-encryption(我們推薦的PHP庫)。

0x06 使用關聯資料的認證加密


在我們前面的示例中,我們集中精力於,同時使用加密和認證,使其作為必須小心使用的單獨元件,以避免加密的悲劇。具體而言,我們專注於密碼段連結模組的AES加密。

然而,密碼學家已經開發出更新,更具有彈性的加密模型,其加密和認證資訊在同一操作。這些模型被稱為AEAD模型(Authenticated Encryption with Associated Data)。關聯資料意味著,無論你的應用程式需要什麼進行認證,都不加密。

AEAD模型通常用於有狀態的目的,如網路通訊中,其中一個隨機數可以很容易地管理。

AEAD兩個可靠的實現是AES-GCM和ChaCha20-Poly1305。

  • AES-GCM是Galois/Counter模式中的高階加密標準(又名Rijndael演算法加密)。這種模式在OpenSSL的最新版本中加入,但它目前在PHP中還不被支援。

  • ChaCha20-Poly1305結合了ChaCha20流密碼與Poly1305訊息認證碼。這種模式在libsodium PHP擴充套件可用。Sodium::crypto_aead_chacha20poly1305_encrypt() Sodium::crypto_aead_chacha20poly1305_decrypt()

總結一下,你該記住的

  • 加密不是認證

  • 加密提供機密性

  • 認證提供完整性

將兩者混為一談你就得自擔風險

為了完成CIA triad,你需要單獨解決可用性。這通常不是一個加密問題。

更重要的是:在密碼學專家的監督下,使用具有韌性被證實記錄的庫,而不是自己在那裡閉門造車,你會好得多。

原文:https://paragonie.com/blog/2015/05/using-encryption-and-authentication-correctly

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章