.NET DPAPI 和 AES 加密:感知检查
.NET DPAPI and AES Encryption: Sense-Check
我正准备为我目前正在开发的网站编写一个新的加密系统,如果可能的话,想看看我是否可以在开始之前找人进行感应检查!
更新: 我原来的问题应该更清楚了。我需要加密一些用户数据,我还需要能够在以后的数据中读回这些数据。而且,我还需要存储用户密码或密码的哈希值,以便在登录时验证用户。
计划是:
主密钥:创建一个DPAPI密钥-setter应用程序获取一个基于文本的主密钥,通过DPAPI加密,然后保存加密输出到服务器上的文本文件。这是我每次将站点移动到新服务器时都会执行的一次性任务。主密钥将用于执行 AES 加密。
新用户注册时:
2.1。保存密码数据/密码数据的哈希值。
2.2。加载主密钥文件,使用 DPAPI 解密密钥。使用解密的主密钥和每个用户数据的新随机 IV 来创建 AES 加密字符串。通过在加密字符串前面加上相应的随机 IV 来保存每个加密字符串,并插入到数据库中的 varchar 列中。
用户登录后:
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")。
我正准备为我目前正在开发的网站编写一个新的加密系统,如果可能的话,想看看我是否可以在开始之前找人进行感应检查!
更新: 我原来的问题应该更清楚了。我需要加密一些用户数据,我还需要能够在以后的数据中读回这些数据。而且,我还需要存储用户密码或密码的哈希值,以便在登录时验证用户。
计划是:
主密钥:创建一个DPAPI密钥-setter应用程序获取一个基于文本的主密钥,通过DPAPI加密,然后保存加密输出到服务器上的文本文件。这是我每次将站点移动到新服务器时都会执行的一次性任务。主密钥将用于执行 AES 加密。
新用户注册时:
2.1。保存密码数据/密码数据的哈希值。
2.2。加载主密钥文件,使用 DPAPI 解密密钥。使用解密的主密钥和每个用户数据的新随机 IV 来创建 AES 加密字符串。通过在加密字符串前面加上相应的随机 IV 来保存每个加密字符串,并插入到数据库中的 varchar 列中。
用户登录后:
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")。