【问题标题】:Generating a unique *and* random URL in C#在 C# 中生成唯一的 *and* 随机 URL
【发布时间】:2012-08-27 18:42:01
【问题描述】:

我的最终目标是创建一个独一无二且无法猜测/预测的 URL。此 URL 的目的是允许用户执行验证其电子邮件地址和重置密码等操作。这些 URL 将在设定的时间内(当前设置为 24 小时)过期。

我最初为此目的使用了Guid,但我现在理解这介于“很好”和“非常不安全”之间,具体取决于您听哪位专家。所以,我想我会稍微加强一下我的代码,以防万一。起初我以为我会坚持使用Guid,但从随机字节而不是Guid.NewGuid() 工厂方法生成它。这是我想出的方法:

public static Guid GetRandomGuid()
{
    var bytes = new byte[16];
    var generator = new RNGCryptoServiceProvider();
    generator.GetBytes(bytes);
    return new Guid(bytes);
}

我不太清楚当您使用new Guid(bytes) 而不是Guid.NewGuid() 时究竟会发生什么,但我认为这个方法所做的只是生成一个随机字节数组并将其存储 Guid 数据结构。换句话说,它不再保证是唯一的,它只保证是随机的。

由于我的 URL 需要是唯一的 随机的,这似乎不是一个可靠的解决方案。我现在在想,我的 URL 应该基于唯一 ID(可能是 Guid 或者,如果有的话,数据库自动递增 ID)和从 RNGCryptoServiceProvider 生成的随机序列的组合。

问题

生成一个既保证唯一又极难/不可能预测/猜测的验证/密码重置 URL 的最佳方法是什么?

  • 我是否应该通过连接一个唯一字符串和一个随机字符串来简单地构造我的 URL?

  • .NET Framework 是否有内置方法可以轻松生成唯一的可用于 URL 的随机(不可预测)序列?

  • 如果没有,有没有好的开源解决方案?

更新

如果有人有类似的要求,我目前正在使用以下方法:

public static string GenerateUniqueRandomToken(int uniqueId)
    // generates a unique, random, and alphanumeric token
{
    const string availableChars =
        "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    using (var generator = new RNGCryptoServiceProvider())
    {
        var bytes = new byte[16];
        generator.GetBytes(bytes);
        var chars = bytes
            .Select(b => availableChars[b % availableChars.Length]);
        var token = new string(chars.ToArray());
        return uniqueId + token;
    }
}

如果您对此有任何问题,请发表评论。

【问题讨论】:

  • this回答对你有帮助吗?
  • 我怀疑这将是您最好的选择。它显然不符合 GUID 标准。
  • 看起来您只是创建了一个 16 字节的随机数,而不是 GUID。
  • @Gabe,是的,请看我的编辑。
  • 似乎只需生成一个 guid,然后使用该 guid 的哈希值即可满足您的需求(唯一性和随机性)

标签: c# security authentication cryptography guid


【解决方案1】:

Guid 并不意味着是一项安全功能。创建 Guid make no claims about how secure it is 的标准,或者猜测的难易程度。 它只是声称它的独特性。

如果您尝试使用 Guid 来保护您的系统,那么您将不会拥有一个安全的系统。

【讨论】:

  • 我的代码以前只是Guid.NewGuid()。我编写上述方法是为了提高安全性。我尝试使用此方法是使用加密随机数生成器生成字节,然后将这些字节简单地存储在Guid 数据结构中(用于数据库存储)。这不是我的代码所做的吗?如果不是,你有什么建议?我使用Guids 来生成用于密码重置等的唯一 URL。所有这些 URL 在发出 24 小时后过期。
  • 我认为问题在于 GUID 本质上是可预测的,因为它遵循特定的规则。为什么不根据您自己的规则集创建一个随机加密哈希。您可以基于用户名和特定于您的服务器设置的其他信息......如果不熟悉您的服务器,这基本上是不可能猜到的。 GUID 是特定的,规则是已知的,因此理论上是可以猜测的,尽管在数学上不太可能。
  • 哈希的问题在于它不能保证是唯一的。我很确定我在这里所做的也不能保证是独一无二的。但我既需要独特又需要随机。
【解决方案2】:

[编辑:我错过了上面对 RNGCryptoServiceProvider 的调用。对此深表歉意。]

对于您的情况,RNGCryptoServiceProvider 等 RNG 生成器的问题在于它们不能保证唯一性。如您所知,Guids 是 statistically unlikely 是唯一的,但不保证它是唯一的。保证唯一性和随机性的唯一真正方法是生成 GUID,就像您将它放在可搜索的存储中一样,例如数据库表。每当您生成一个新值时,请检查它是否已经存在。如果是,则丢弃它并生成一个新值。

【讨论】:

  • 请仔细看看我的代码。我已经在使用 RNGCrptoServiceProvider。
  • @DanM 我很抱歉。我已经重写了我的答案来解决这个问题。
  • 我记得读过 Eric Lippert 的博客文章。听起来如果所有Guids 都是在同一台机器上生成的(就像在这个特定应用程序的情况下一样),那么确实可以保证唯一性。也就是说,分配了自动递增 ID 的数据库也将保证唯一,并且看起来我在当前使用 Guid 的每种情况下都可以访问一个。所以,我可以将这个唯一的 ID 与一个 128 位加密强度随机数结合起来,并将整个事物作为字符串存储在数据库中。你觉得这样就够了吗?
【解决方案3】:

您可以通过使用随机密钥作为密钥的 AES 计数器运行计数器来生成 128 位“随机”唯一数字。只要使用相同的键,就不会重复任何输出。

static byte[] AESCounter(byte[] key, ulong counter) {
    byte[] InputBlock = new byte[16];
    InputBlock[0] = (byte)(counter & 0xffL);
    InputBlock[1] = (byte)((counter & 0xff00L) >> 8);
    InputBlock[2] = (byte)((counter & 0xff0000L) >> 16);
    InputBlock[3] = (byte)((counter & 0xff000000L) >> 24);
    InputBlock[4] = (byte)((counter & 0xff00000000L) >> 32);
    InputBlock[5] = (byte)((counter & 0xff0000000000L) >> 40);
    InputBlock[6] = (byte)((counter & 0xff000000000000L) >> 48);
    InputBlock[7] = (byte)((counter & 0xff00000000000000L) >> 54);
    using (AesCryptoServiceProvider AES = new AesCryptoServiceProvider())
    {
        AES.Key = key;
        AES.Mode = CipherMode.ECB;
        AES.Padding = PaddingMode.None;
        using (ICryptoTransform Encryptor = AES.CreateEncryptor())
        {
            return Encryptor.TransformFinalBlock(InputBlock, 0, 16);
        }
    }
}

【讨论】:

  • 没有上述解决方案的 cmets,但是看起来很有希望
【解决方案4】:

获取所有用户登录凭据的 md5 并将其与 Guid.NewGuid().ToString() 生成的 guid 连接并在您的 url 中使用它,它应该可以正常工作。

【讨论】:

  • MD5 绝不应用于任何与安全相关的功能。即使在 2013 年也是如此。
【解决方案5】:

在数据库中创建一个带有 linkID 和 Datesent 列的表,在生成链接时发送插入 DateTime.Now 到表中并返回 linkId,将 linkID 设置为 activationLink 上的查询字符串参数。

在加载激活页面时,检索 linkId 并使用它来调用一个存储过程,该存储过程将在将相应的 linkId 作为参数传递时返回日期,当您获取日期时,您可以添加您希望链接到多长时间使用.AddDays()/.AddMonths(这是日期时间的 C# 方法)保持活动状态。然后将您返回的日期与今天的日期进行比较。如果它已经过了几天或几个月的长度,则给出错误消息,否则继续并显示页面内容。

我建议您将页面内容保留在面板中并将其设置为visible = "false",然后仅在日期仍在范围内时才将面板设置为visible="true"

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多