C 函数 crypt() 是否存在已知问题?

Are there known issues with the C function crypt()?

我使用 crypt() 为我的一个项目加密密码。当用户选择密码时,它是这样加密的:

password = crypt(<password chosen>, <user's account name>)

问题出在用户使用密码登录时。如果他们键入的内容与他们的密码不匹配,则应输入以下内容,如果检查:

if (strcmp(crypt(<what user types in as password>, <user's account name>), <user's encrypted password>)) {
   //...
}

在某一种情况下不会。假设他们的密码是 'asdf'。如果他们输入带有任何随机尾随字符的 'asdf',例如 'asdffffff' 或 'asdf339sfd',它仍会接受密码。 'asdf'.

之后似乎忽略了一切

这是 crypt 的已知问题吗?还有其他加密密码的方法吗?

crypt 的第二个参数不应是用户的帐户名。它应该是一个设置字符串。设置字符串如下所示:

b$fQuDK3TaQP4sw6IX6iVcTw

b$部分告诉crypt使用哪种密码散列算法,后面的随机字符串就是salt。每个用户的盐必须不同,但它不应该与用户的帐户名有任何关联。从技术上讲,它不一定是随机的,但对于每个用户来说它都是不同的,并且每次用户更改密码时它都需要更改,因此最佳做法是使用从加密 PRNG 中提取的长字符串。

当您验证以前登录过的用户时,您使用存储的散列密码作为设置字符串:

char *new_hash = crypt("password typed in", "stored hash");
if (new_hash && !strcmp(new_hash, "stored hash")) {
    // user has successfully logged in
}

这是有效的,因为存储的散列密码总是以最初用于创建它的设置字符串开头,并且 crypt 被编码为仅查看设置字符串部分。

(还要注意空检查;crypt 的某些实现可能会失败并通过返回空指针来报告失败。)

当您创建新帐户或更改密码时,您必须生成新的设置字符串。如果您有函数 crypt_gensalt,请使用:

char *new_setting = crypt_gensalt("b$", 0, 0, 0);
if (new_setting) {
    char *new_hash = crypt("user's new password", new_setting);
    // ...
} else {
    // halt and catch fire
}

如果您没有 crypt_gensalt,很遗憾,您必须自己实施。 (更糟糕的是,一些 Unix 有 crypt_gensalt,我在上面链接了它的文档,而其他 Unix 有 a different version,具有相同的名称,做同样的工作,但采用不同的参数。是时候尘埃落定了你的 Autoconf 技能!)


现在你知道了所有这些,我可以解释为什么

password = crypt("password chosen", "user's account name");

似乎有效,但截断了密码。您的用户帐户名可能至少以两个字母数字字符开头,对吗?比如,"Ma[ya]""zw[ol]"?不幸的是,任何两个字母数字字符都会构成一个有效的设置字符串……select 是科学界已知的最古老和最不安全的密码哈希算法之一,descrypt. (It was pretty good when it was invented ... in the mid-1970s. Nowadays, it can be cracked by brute force 无论密码是什么。)一个该算法的许多问题之一是它将所有密码截断为八个字符。 asdfasdfhjkl 散列到不同的东西,但 asdfhjklasdfhjkl1234 散列到相同的东西。

解决这个问题的方法是使用 crypt_gensalt 或等效于 select 的现代算法。所有现代算法都接受任意长的密码。