.NET DPAPI 和 AES 加密:感知检查

.NET DPAPI and AES Encryption: Sense-Check

我正准备为我目前正在开发的网站编写一个新的加密系统,如果可能的话,想看看我是否可以在开始之前找人进行感应检查!

更新: 我原来的问题应该更清楚了。我需要加密一些用户数据,我还需要能够在以后的数据中读回这些数据。而且,我还需要存储用户密码或密码的哈希值,以便在登录时验证用户。

计划是:

  1. 主密钥:创建一个DPAPI密钥-setter应用程序获取一个基于文本的主密钥,通过DPAPI加密,然后保存加密输出到服务器上的文本文件。这是我每次将站点移动到新服务器时都会执行的一次性任务。主密钥将用于执行 AES 加密。

  2. 新用户注册时:

    2.1。保存密码数据/密码数据的哈希值。

    2.2。加载主密钥文件,使用 DPAPI 解密密钥。使用解密的主密钥和每个用户数据的新随机 IV 来创建 AES 加密字符串。通过在加密字符串前面加上相应的随机 IV 来保存每个加密字符串,并插入到数据库中的 varchar 列中。

  3. 用户登录后:

    3.1。匹配密码哈希以验证用户。

    3.2。对于每个加密的用户数据字段,将内容分为两部分:IV 和加密数据。从 DPAPI 中获取主密钥和 IV,解密数据并显示在屏幕上。

听起来怎么样?以上是否有明显缺陷?

我是新手,过去曾使用过 Enterprise Library Security 处理过此类内容(.NET Core 中不再可用!),因此非常感谢任何帮助!

如果可以避免,则永远不要存储密码。

我不清楚第 1 步中的密码是什么。如果新用户直到第 2 步才出现,这是谁的密码?

无论如何,对于您的用户来说,更好的计划是 use/save 派生 material。例如,在新用户注册时执行类似

byte[] exportBytes;
byte[] exportSalt;
int exportPasswordSettingsVersion = YourSystemConfiguration.NewPasswordSettingsVersion;

using (Rfc2898DeriveBytes registerer = new Rfc2898DeriveBytes(
    newUserPassword,
    YourSystemConfiguration.GetSaltSize(exportPasswordSettingsVersion),
    YourSystemConfiguration.GetIterationCount(exportPasswordSettingsVersion)))
{
    exportSalt = registerer.Salt;

    exportBytes = registerer.GetBytes(
        YourSystemConfiguration.GetDerivedKeySize(exportPasswordSettingsVersion));
}

然后导出盐字节(为您随机生成)、派生密码字节、作为用户配置文件一部分的设置。当用户登录时,您加载这些值并检查它们是否匹配:

using (Rfc2898DeriveBytes verifier = new Rfc2898DeriveBytes(
    inputPassword,
    loadedProfile.Salt,
    YourSystemConfiguration.GetIterationCount(loadedProfile.PasswordSettingsVersion)))
{
    byte[] verifyBytes = registerer.GetBytes(loadedProfile.PasswordVerify.Length);

    if (!ConstantTimeEquals(verifyBytes, loadedProfile.PasswordVerify))
    {
        return false;
    }

    if (loadedProfile.PasswordSettingsVersion < YourSystemConfiguration.GetIterationCount(exportPasswordSettingsVersion))
    {
        // Re-derive their password and save it with your newer (stronger, presumably) cryptographic settings.
    }

    return true;
}

本方案:

  • 使用 RFC2898 的 PBKDF2 算法从密码中导出密钥,存储此导出值比存储密码更好,因为如果您的凭据数据库遭到破坏,它不会泄露密码。
  • 为每个新用户使用新的随机盐。 (也可以在更改密码或升级登录时重新生成)
  • 保存(和加载)有关设置密码时使用的设置的信息,以便您将来可以更改默认设置。
  • 不使用 DPAPI(DPAPI 不错,但它只是 Windows,如果您使用的是 .NET Core,那么您可能需要一个跨平台的解决方案)

忽略密码问题,只考虑用户数据,你的方案没问题,但可以改进。

基本上,您有一个主密钥——一个对称密钥,或者可能是 16 或 32 字节,它在静止时(在磁盘上)受到保护,您可以在服务器的内存中对其进行解密。

用户数据使用主密钥加密,每条数据都有一个随机 IV。请务必使用加密强度高的随机数据。

您将 IV 和密文存储在一起,这很好。尽管 IV 和密文是二进制的,因此您要么必须使用 blob 数据库类型,要么使用 Base64 对二进制进行编码以将其存储为 varchar。

您应该考虑添加某种形式的篡改检测。例如,您可以使用两个主密钥——一个用于 encrypting/decrypting,另一个用于数据上的 HMAC。您将使用加密密钥来加密数据,然后在 IV + 密文上应用 HMAC(使用 HMAC 的第二个主密钥)并将 HMAC 与 IV 和密文一起存储(您甚至可以附加它)。在解密之前,您验证 HMAC。这会告诉您 IV + 密文中的任何内容是否已被更改。

你没有提到填充。如果您使用 AES CBC 模式,如果数据不是 16 字节的倍数,则需要填充数据。使用填充时,您只需要注意不要意外提供 "padding oracle",攻击者可以在其中向服务器发送任意密文,服务器的响应会告诉攻击者 "padding error" 或 "invalid decryption" -- 即服务器响应不同。

如果您使用 AES GCM 模式,那么 HMAC 的等价物是内置的——您只需要一个主密钥,GCM 本身会检测密文的篡改。这还允许您包含 "associated data",它不是加密的一部分,但包含在 "authentication" 中,即就像它包含在 HMAC 中一样。例如,用户名 "Joe" 可以作为关联数据包含在 Joe 数据的 GCM 加密中。然后,GCM模式解密数据时,不仅会检测到IV+密文被篡改,而且用户名也必须仍然是"Joe"——注意"Joe"这里没有加密。

个人IV,每次随机,每条数据,是一个很好的主意。您可能还会考虑主密钥在更换之前使用了多长时间。它应该有一个有限的生命周期,当它是 "rotated" 或 "rolled" 或 "rekeyed" (所有的意思都是一样的 - 你正在更换它)你需要一些方法来重新 -加密一切。您希望定期(可能是 3 个月,也可能是一年)更改主密钥以 1) 限制在主密钥被泄露时暴露的数据量(例如只有 3 个月的价值)和 2) 限制单个密钥用于加密的数据量(因为从技术上讲,您可以使用单个密钥加密多少数据并且在数学上仍然是有限的 "secure")。