65.9K
CodeProject 正在变化。 阅读更多。
Home

OWASP #6 在 ASP.NET 中防止敏感数据泄露 – 第二部分

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2016年2月26日

CPOL

8分钟阅读

viewsIcon

13726

您如何存储敏感的应用程序数据,应该吗?

第一部分中,我们开始了关于保守秘密的讨论,这是开放 Web 应用程序安全项目 (OWASP) 最严峻的 #6 风险——敏感数据泄露——的主题。在第一部分中,我们讨论了适当的用户凭证存储。在这一部分中,我们将继续讨论,重点关注静态数据加密技术,这是任何存储加密数据的人都必须克服的一个重大障碍。

加密静态数据

你确定,绝对,肯定吗?肯定,绝对确定?

第一个问题:你真的需要以加密状态存储“那些”数据吗?你认为你需要保存它,现在你正在考虑将其加密存储。然而,最安全的选择是根本不存储数据。如果你没有,他们就偷不走。

你对你保存的任何敏感数据负责。数据泄露事件备受公众关注,并会带来各种法律、财务和声誉上的影响。虽然你可能采取了最佳实践来存储敏感数据,但有动机的攻击者似乎会找到其他薄弱的访问点。而你的安全只和你最薄弱的环节一样强大。

因此,如果你绝对必须保留它,请花时间寻找替代存储方案。许多应用程序功能和数据收集点都源于“总有一天我们会需要它”的想法。你标记为“重要”的数据,真的有必要吗?

回到你正常收看的内容。

如果你决定存储,你可能会转向双向加密,以保护信息免落入不法分子之手,或防止内部人员窥探。

两种最常见的双向加密类型是**非对称加密**和**对称加密**。虽然两者都可以将数据混淆成不可读格式(单向),并将其恢复为原始可读格式(双向),但它们之间存在显著差异。

非对称加密

非对称加密是非对称密码学的一个更大群体的一部分,该群体包含许多不同的应用,如数字签名和加密。数字签名可以提供真实性和完整性,而加密则提供机密性。

非对称加密使用两个密钥;一个非秘密密钥用于加密数据,一个秘密密钥用于解密数据。非对称加密是 TLS/SSL 握手的一部分,我们将在数据传输过程中进一步讨论。目前,我们的重点是第二种类型:对称加密。

对称加密

在对称加密中,同一个密钥加密和解密数据。鉴于其双重功能,密钥是秘密的。当同一个实体处理过程的两个部分(端到端)时,对称加密是有意义的。

在我们最初的场景中,我们有想要加密存储的数据,对称加密听起来是个不错的选择。不幸的是,不仅加密本身就很困难,而且对称加密也存在问题。让我们从熟悉一个简单的例子开始。

NET 中的对称加密示例

    byte[] iv;  // initialization vector
    byte[] key; // secret
    byte[] encryptedByteArr;
    string decryptedData;
    using (var aesManaged = new AesManaged ())
    {
        iv = aesManaged. IV;  
        key = aesManaged. Key;
        var encryptor = aesManaged.CreateEncryptor();
        using (var ms = new MemoryStream ())
        using (var crytpoStream = new CryptoStream (ms, encryptor, CryptoStreamMode .Write))
        {
            using (var writer = new StreamWriter (crytpoStream))
            {
                writer .Write(MessageDigest);
            }
            encryptedByteArr = ms. ToArray();
        }
    }
    using (var aesManaged = new AesManaged ())
    {
        aesManaged .IV = iv;
        aesManaged .Key = key;
        var decryptor = aesManaged.CreateDecryptor(key, iv);
        using (var memStream = new MemoryStream (encryptedByteArr))
        using (var crytpoStream = new CryptoStream (memStream, decryptor, CryptoStreamMode .Read))
        using (var reader = new StreamReader (crytpoStream))
        {
            decryptedData = reader. ReadToEnd();
        }
    }

没有什么令人兴奋的,也没有什么过于复杂的。正如你所见,我们正在使用 .NET 的 AES(高级加密标准)库来执行加密和解密过程。我不是密码学家,但如果像Stan Drapkin这样的专家说要始终使用 AES,那我就会听。现在让我们来了解我们对称加密问题中的参与者。

密钥是加密强秘密。与初始化向量(我们稍后会看到)一起,解密数据需要相同的密钥。

IV(初始化向量)

初始化向量为加密过程引入了熵。简单来说,通过在每次加密执行中使用唯一的初始化向量,可以避免任何“相同性”或可预测的模式,这些模式可能会导致加密消息的精确或部分匹配。不幸的是,正如我之前提到的,.NET 中的对称加密存在已知问题。

此路不通

你还记得 2010 年 ASP.NET 安全被填充 Oracle 攻击摧毁的事情吗?不记得?没关系。你可以在这里这里以及这个视频中阅读相关内容,以了解问题的复杂性和严重性。

简而言之,这个问题根源于密码块的填充模式。块密码在固定长度的字节块(例如 16 字节)上运行。如果一个数据块不完全符合块的大小,它将用专门的填充来填充。如果提交的密文填充不正确,处理加密字符串的接收方将发出相关错误。要清晰地了解攻击者如何通过对密码消息进行小幅度增量修改,并根据填充错误或成功响应来解密每个字节,请参阅Moxie Marlinspike的文章。

由于这个漏洞,一些专家甚至会遵循以下规则:如果你使用对称加密,它必须是经过认证的加密。

认证加密

我们将要研究的认证加密方法是“先加密-后 MAC”(ETM)。以下过程摘自 Stan Drapkin 的Security Driven .NET一书(如果你还没有读过,那这本书非常值得一读)。

该过程需要两个密钥,一个用于加密原始明文。另一个用于生成整个消息的消息认证码 (MAC)。以下是使用 ETM 认证加密的加密和解密过程的快速概述。

加密

  1. 使用秘密密钥 **Ke** 和新生成的随机 IV 对明文 **P** 进行 AES 加密,得到密文 **C1**。
  2. 将密文 **C1** 附加到使用的 IV 上,得到 **C2 = IV + C1**。
  3. 计算 **MAC = HMAC(Km , C2)**,其中 Km 是一个不同于 Ke 的秘密密钥。
  4. 将 **MAC** 附加到 **C2** 上,得到 **C3 = C2 + MAC** 并返回。

解密

  1. 如果输入 **C** 的长度小于(预期的 **HMAC** 长度 + 预期的 **AES IV** 长度),则中止。
  2. 从 **C** 的最后 HMAC 长度字节读取 **MACexpected**。
  3. 计算 **MACactual = HMAC(Km , C** - 最后一个 **HMAC** 长度字节)。
  4. 如果 **BAC(MACexpected , MACactual)** 为 false,则中止。
  5. 设置 **IV** =(从 **C** 的开头获取 IV 长度字节)。设置 **C2** =(**C** 中介于 **IV** 和 **MAC** 之间的字节)。
  6. 使用 **IV** 和 **Ke** 对 **C2** 进行 AES 解密,得到明文 **P**。返回 **P**。

– Security Driven .NET - Stan Drapkin

为了加密,我们使用唯一的 IV,用我们的第一个密钥生成密文 (C1)。然后,我们对连接的 IV 和密文 (C2) 生成一个 MAC。最后,我们将 MAC 附加到 (C2) 上,得到最终值 (C3)。

这使我们可以在解密过程开始时检查预期的长度,然后再进行任何其他步骤。然后,我们可以生成实际的 MAC,并将其与提交的 (C3) 密文的 MAC 进行比较。我们可以再次验证消息的真实性,如果该值不正确,则中止。然后,我们使用附加的 IV 和 (C2) 继续解密密文 (C1)。

那么,上面冗长的解释在代码示例中看起来会是怎样的?很高兴你问了。下面是一个 .NET 代码示例,用于说明该过程,以便更好地理解。

private static readonly Func<Aes > AesFactory = () => new AesCryptoServiceProvider ();
private static readonly Func<HMAC > HmacFactory = () => new HMACSHA512 ();
private static readonly Aes Aes = AesFactory();
private static readonly HMAC Hmac = HmacFactory();
private static readonly int AesIvLength = Aes.BlockSize /8;
private static readonly int MacLength = Math. Min(128, Hmac .HashSize)/ 8;
private static readonly int MinCipherTextLength = AesIvLength + MacLength;
private static readonly int AesKeyLength = Aes.KeySize /8;
private static readonly int MacKeyLength = Math. Max(256, Hmac .HashSize - Aes.KeySize)/ 8;
       
private static readonly byte[] Enckey = 
GenerateRandomKey(AesKeyLength); //Creates Cryptographically strong Random Key based on size
private static readonly byte[] MacKey = 
GenerateRandomKey(MacKeyLength); //Creates Cryptographically strong Random Key based on size

private static string _messageToEncrypt = "She turned me into a newt" ;

private static void AeSymmetricEncryptionExample()
{
  var messageToEncryptByteArr = Encoding .UTF8. GetBytes(_messageToEncrypt);
  var encryptedByteArray = Encrypt(messageToEncryptByteArr);

  var decryptedByteArray = Decrypt(encryptedByteArray);
  var decryptedText = Encoding. UTF8.GetString(decryptedByteArray);
}

private static byte[] Encrypt( byte[] text)
{
    using (var aes = AesFactory())
    {
        aes .Key = Enckey;
        var iv = aes.IV;

        using (var ms = new MemoryStream ())
        {
            ms .Write(iv, 0 , iv.Length);
            using (var encryptor = aes .CreateEncryptor())
            {
                using (var crypto = new CryptoStream (ms, encryptor, CryptoStreamMode .Write))
                {
                    crypto .Write(text, 0 , text.Length);
                    crypto .FlushFinalBlock();

                    using (var hmac = HmacFactory())
                    {
                        hmac .Key = MacKey;
                        var mac = hmac.ComputeHash(ms .GetBuffer(), 0 , (int) ms. Length);
                        ms .Write(mac, 0 , MacLength);
                        return ms. ToArray();
                    }
                }
            }
        }
    }
}

private static byte[] Decrypt( byte[] cipherText)
{
    var cipherLength = cipherText.Length - MinCipherTextLength;
    if (cipherLength <= 0) return null ;

    var ivCipherLength = AesIvLength + cipherLength;

    using (var aes = AesFactory())
    {
        aes .Key = Enckey;

        using (var hmac = HmacFactory())
        {
            hmac .Key = MacKey;

            var actualMac = hmac.ComputeHash(cipherText, 0, ivCipherLength);
            // Compares before moving on
            if (!XorCompare(actualMac, 0, MacLength, cipherText, ivCipherLength, MacLength)) return null;

            var iv = new byte[AesIvLength];
            Buffer.BlockCopy(cipherText, 0, iv, 0 , AesIvLength);

            aes .IV = iv;

            using (var ms = new MemoryStream ())
            {
                using (var decryptor = aes .CreateDecryptor())
                {
                    using (var crypto = new CryptoStream (ms, decryptor, CryptoStreamMode .Write))
                    {
                        crypto .Write(cipherText, AesIvLength, cipherLength);
                    }
                }
                return ms. ToArray();
            }
        }
    }
}

private static byte[] GenerateRandomKey(int keyLen)
{
    var csprng = new RNGCryptoServiceProvider();
    var generatedSalt = new byte[keyLen];
    csprng.GetBytes(generatedSalt);
    return generatedSalt;
}

// XorCompare courtesy of Stan Drapkin - Security Driven .NET
public static bool XorCompare(byte[] a, 
int first, int firstCount, byte[] b, int second, int secondCount)
{
    var x = firstCount ^ secondCount;
    for (var i = 0; i < firstCount; ++i)
    {
        x |= a[first + i] ^ b[second + i % secondCount];
    }
    return x == 0;
}

但我们不是密码学家,所以在自己实现之前,我强烈建议尽可能使用像InfernoLibsodium这样的库来执行认证加密。但上面的例子可以清楚地演示 ETM 认证加密过程的含义。但所有这些例子中都有一个显而易见的问题——密钥。我们如何安全地存储密钥?

密钥管理

所有加密中最重要的问题之一是密钥管理,无论加密类型或目的如何。保护密钥是一个薄弱环节,特别是当我们谈论对称加密时,因为同一密钥用于两个过程。没有完美的解决方案。然而,我将提出一个可能的解决方案,而不是喋喋不休地谈论问题的难度。当然,这个解决方案并不适合每个人的情况,但对于那些适合的人来说,我认为它提供了保护、易于访问性和经过充分测试的系统的支持。

Azure Key Vault

Azure Key Vault 提供了一种安全且经济高效的方式来存储密钥和机密,并可根据需要获得硬件安全模块 (HSM) 的安全支持。Azure Key Vault 可以做到以下以及更多:

  • 应请求提供本地存储的加密密钥(或主密钥)的解密版本,用于认证加密。
  • 在 Azure Key Vault 中将加密密钥作为机密提供。
  • 执行必要的加密/解密(有限功能)。

要使用 Azure Key Vault,你需要将你的应用程序注册到 Azure Active Directory (AAD),以便获得 Key Vault 的授权。依赖关系超出了本文的范围,但如果你想了解 Azure Key Vault 的入门知识,可以查看这篇文章,还有这篇文章,最后还有微软的另一篇逐步操作指南文章

总结(但并非结束)

关于静态数据,以下是需要记住的关键点:

  1. 最重要的是,评估是否有真正的必要以加密状态存储数据。记住,你没有的东西,就不会被盗。
  2. 使用对称加密存储加密数据时,请使用认证加密。
  3. 如果可能,请利用成熟可靠的加密库,例如InfernoLibsodium
  4. 最后,加密静态数据时最棘手的问题是密钥管理。如果你的情况允许,可以考虑 Azure Key Vault 等云服务。

在下一篇文章中,我们将讨论保守秘密的最后一部分——保护传输中的数据。

OWASP #6 在 ASP.NET 中防止敏感数据泄露 – 第二部分 首次出现在 LockMeDown.com


想知道结合了叙事故事和技术的播客听起来会是什么样吗?那么,别再犹豫了,Lock Me Down 播客与你听过的任何其他技术播客都不同。神秘、引人入胜的故事?没问题。为开发人员提供信息丰富、指导性的网络安全信息?没问题。公司安全方面的离谱失误?没错。

© . All rights reserved.