menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right ... chevron_right 009-tips chevron_right 07-正确地使用加密与认证技术.md
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    07-正确地使用加密与认证技术.md
    9.79 KB / 2021-07-17 00:01:38
        # 正确地使用加密与认证技术
    
    在密码学专家之中,“加密并不是认证”是一个简单的共识。但很多不了解密码学的开发者,并不知道这句话的意义。如果这个知识更广为人知和深入理解,那么将会避免很多的设计错误。
    
    这一概念本身并不困难,但在表面之下,还有更多丰富的细节和玄妙之处有待发现。本文就是讲述开发者对于加密和认证二者的混淆与误用,并附上了优秀的解决方案。
    
    0x01 加密与认证之间有哪些区别?
    ==================
    
    * * *
    
    加密是呈现信息,使其在没有正确的密钥情况下,变得难以卒读的过程。在简单的对称加密中,同一个密钥被用于加密和解密。在非对称加密中,可以使用用户的公钥对信息加密,使得只有对应私钥的拥有者才能读取它。
    
    认证是呈现信息,使其抗篡改(通常在某一非常低的概率之内,小于1除以已知宇宙中粒子的数量),同时也证明它起源于预期发送者的过程。
    
    注意:当本文提及真实性时,是专门指的信息真实性,而不是身份真实性。这是一个PKI和密钥管理问题,我们可能在未来的博客中详细说明。
    
    就[CIA triad](http://whatis.techtarget.com/definition/confidentiality-integrity-and-availability-cia)而言:加密提供机密性,认证提供完整性。
    
    加密不提供完整性;被篡改的信息(通常)还能解密,但结果通常会是垃圾。单独加密也不抑制恶意第三方发送加密信息。
    
    认证不提供机密性;可以为明文信息提供抗篡改。
    
    在程序员中,常见的错误是混淆这两个概念。你能很容易找到这样的一个库或者框架:加密cookie数据,然后在仅仅解密它之后就无条件地信任与使用之。
    
    0x02 加密
    =======
    
    * * *
    
    我们之前定义了加密,并且详细说明了它是提供机密性,但不提供完整性和真实性的。你可以篡改加密信息,并将产生的垃圾给予接收者。而且你甚至可以利用这种垃圾产生机制,来绕过安全控制。
    
    考虑在加密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字节的字符串作为$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里的原始数据,是看起来像这样的数组:
    
    ```
    array(2) {
      ["admin"]=>
      int(0)
      ["user"]=>
      "aaaaaaaaaaaaa"
     }
    
    ```
    
    但在仅仅改变初始化向量的一个字节之后,我们就能够改写我们的阅读信息:
    
    ```
    array(2) {
      ["admin"]=>
      int(1)
      ["user"]=>
      "aaaaaaaaaaaaa"
    }
    
    ```
    
    根据底层应用程序的设置方法,你或许可以翻转一位进而提升成为一名管理员。即使你的cookie是加密的。 如果你想再现我们的结果,我们的加密密钥是十六进制下的:`000102030405060708090a0b0c0d0e0f`
    
    0x04 认证
    =======
    
    * * *
    
    如上所述,认证旨在提供信息的完整性(我们指显著抗篡改能力),而这证明它来自预期的源(真实性)。这样做的典型方法是,为信息计算一个密钥散列消息认证码(HMAC的简称),并将信息与它连结。
    
    ```
    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,而不仅仅是一个简单的散列函数。
    
    ```
    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
    ==========================
    
    * * *
    
    ```
    /*
    // 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
    
    links
    file_download