在C#中基于唯一字符串生成短代码

4
我即将发布一个新的在线服务的测试版。测试版订阅用户将会收到一份独特的“访问码”,以便他们能够注册该服务。
我考虑的是,不储存访问码列表,而是根据用户的电子邮件生成一个访问码,因为电子邮件本身就是唯一的。
我的初步想法是,将电子邮件与一个独特的字符串结合起来,然后使用Base64编码。但是,我想要更短的代码,比如5位数。

@nw:那么,OP应该储存生成的这5个字符。 - Ivan Danilov
我正在尝试避免存储任何东西。如果我能够生成一个代码,然后在注册时使用相同的算法验证代码并发送电子邮件,那将更容易。 - Ben Foster
3个回答

8
如果访问代码本身需要唯一,那么要确保不发生冲突将变得困难。如果您可以容忍两个用户可能偶然共享相同的访问代码,则会更加容易。
采用所提议的电子邮件地址与已知字符串连接的 base-64 编码可能会引入安全漏洞。如果使用电子邮件地址与已知单词连接的 base64 输出,则用户可以轻松地解码访问代码并推导出用于生成代码的算法。
其中一个选项是使用带有已知秘密密钥的电子邮件地址的 SHA-1-HMAC 哈希 (System.Cryptography.HMACSHA1)。哈希的输出为 20 字节序列。然后可以确定性地截断哈希。例如,以下代码 GetCodeForEmail("test@example.org") 给出代码为 'PE2WEG':
// define characters allowed in passcode.  set length so divisible into 256
static char[] ValidChars = {'2','3','4','5','6','7','8','9',
                   'A','B','C','D','E','F','G','H',
                   'J','K','L','M','N','P','Q',
                   'R','S','T','U','V','W','X','Y','Z'}; // len=32

const string hashkey = "password"; //key for HMAC function -- change!
const int codelength = 6; // lenth of passcode

string GetCodeForEmail(string address)
{
    byte[] hash;
    using (HMACSHA1 sha1 = new HMACSHA1(ASCIIEncoding.ASCII.GetBytes(hashkey)))
        hash = sha1.ComputeHash(UTF8Encoding.UTF8.GetBytes(address));
    int startpos = hash[hash.Length -1] % (hash.Length - codelength);
    StringBuilder passbuilder = new StringBuilder();
    for (int i = startpos; i < startpos + codelength; i++)
        passbuilder.Append(ValidChars[hash[i] % ValidChars.Length]);
    return passbuilder.ToString();
}

1
你还把结果字符串搞得一团糟...为什么不只取前面的位呢?所有这些除法运算都是完全不必要的。哈希算法的任务是确保即使输入的一小部分发生变化,整个哈希值也应该不同。通过额外的巧妙处理,你只会给这个想法增加无关紧要的细节,并增加问题的可能性。通常在安全算法中过于聪明是一个坏主意... - Ivan Danilov
2
@drf 从安全角度来看,这个方案可能是可以的。但我更倾向于使用类似 String.Format("{0:X}{1:X}{2:X}", hash[0], hash[1], hash[2]) 这样的结果。这种方式简单得多 :) 你的解决方案能够减少冲突的频率,但即便如此它们仍然微不足道。赞一个,因为它已经是一个可用的实现了。 - Ivan Danilov
@IvanDanilov 我不知道为什么,但当我使用String.Format时,无法保持结果字符串的相同大小。例如,我尝试了类似的字符串“XXXX1”和“XXXX2”。它给了我两个字符串“56EB13”和“438EB”。一个字符串有6个字符,另一个有5个字符。drf的算法对于最终结果效果更好,但我无法判断其他可能存在的问题。 - Eduardo Xavier
2
@EduardoXavier 这是因为哈希字节中有一个小于16,因此只取了一个字符,并且格式“X”不会添加前导零。这就是为什么我说“类似于” - 在浏览器中编写代码总是这样的 :) 尝试使用String.Format("{0:X2}{1:X2}{2:X2}", hash[0], hash[1], hash[2])代替。应该可以解决问题。 - Ivan Danilov
@IvanDanilov 干杯,老兄 ;) - Eduardo Xavier
显示剩余5条评论

2
你可以从他们的电子邮件中创建一个特殊的哈希值,长度小于6个字符,但这并不能真正使其“唯一”,在这样一个小空间内总会出现碰撞。我宁愿使用更长的密钥或在表格中存储预生成的代码。

2
我认为碰撞不会成为这里的问题。看看 Git。通常,5 位 SHA1 哈希码足以区分数千个提交。对于 OP 来说,即使发生偶发冲突,也不是非常关键,至少根据我的理解是这样。对这个想法点赞。 - Ivan Danilov
是的,最有可能的是碰撞,如果有的话,那也很少。虽然我总是很偏执,想到最坏的情况(尽管在计算机世界里这是一件好事)。 - Can Poyrazoğlu

0

所以,听起来你想要做的是为电子邮件创建一个哈希函数,正如@can poyragzoglu所指出的那样。一个非常简单的方法可能看起来像这样:

(伪代码) 对于每个字符c在电子邮件中: 运行总数+= [大质数] * [unicode值]

然后执行running total % large 5 digit number

正如他所指出的那样,除非你有一个很好的哈希函数,否则这将不是唯一的。你很可能会遇到碰撞。不确定是否重要。

对我来说似乎更容易的是,如果你已经知道了有效的电子邮件,只需在注册时检查用户的电子邮件是否在你的有效列表中?为什么还要使用代码呢?

如果你真的想要一个唯一的标识符,最简单的方法可能是使用所谓的GUID。C#本地支持this。你可以将其存储在你的用户表中。虽然它对于用户来说太长了,几乎不可能记住/输入,但如果这是你想要做的事情,它几乎肯定是唯一的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接