【问题标题】:Are there known issues with the C function crypt()?C 函数 crypt() 是否存在已知问题?
【发布时间】:2020-01-23 21:34:22
【问题描述】:

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

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

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

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

在某个特定情况下不会。假设他们的密码是“asdf”。如果他们输入带有任何随机尾随字符的“asdf”,例如“asdffffff”或“asdf339sfd”,它仍然接受密码。它似乎忽略了“asdf”之后的所有内容。

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

【问题讨论】:

  • 你介意分享minimal reproducible example吗?你的描述听起来不对。
  • 我怀疑你的 input 方法,而不是 crypt() (顺便说一下,它的工作方式与你想象的不同 - 所以你最好阅读它的 @987654322 @)。
  • @Downvoters:这实际上有足够的信息来提供完整的答案,我现在正在写一个。
  • 您确定您的密码被截断为 4 个字符吗?可能是八个字符吗?你看,八比四更容易解释。
  • @zwol 好吧,这不是否决这个的唯一原因。另一个原因是没有阅读函数规范并检查错误。三分之一的人对 cme​​ts 没有反应。

标签: c encryption passwords crypt


【解决方案1】:

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

$2b$07$fQuDK3TaQP4sw6IX6iVcTw

$2b$07$ 部分告诉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("$2b$", 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,我在上面链接到了它的文档,而另一些有a different version,同名,做同样的工作,但采用不同的参数。是时候清理你的 Autoconf 技能了! )


现在你都知道了,我可以解释为什么

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

似乎可以工作,但密码被截断。您的用户帐户名称可能至少以两个字母数字字符开头,对吗?比如"Ma[ya]",还是"zw[ol]"?不幸的是,任何两个字母数字字符都构成一个有效的设置字符串……它选择了科学界已知的最古老和最不安全的密码散列算法之一,descrypt。 (它在 1970 年代中期发明时非常好。如今,it can be cracked by brute force 不管密码是什么。)该算法的许多问题之一是它将所有密码截断为八个字符。 asdfasdfhjkl 散列到不同的事物,但 asdfhjklasdfhjkl1234 散列到相同的事物。

解决此问题的方法是使用crypt_gensalt 或等效项来选择现代算法。所有现代算法都接受任意长的密码。

【讨论】:

  • “它应该是一个设置字符串。” -- 嗯,很确定它应该是一个两个字符的盐。
  • @MarcoBonelli:在不同的库/平台中有不同的crypt 例程。并且两个字符的盐不会很强。
  • @MarcoBonelli 如果你的crypt 只支持解密,是的,但是你需要在有人暴力破解你的整个影子文件之前升级它:)
  • 问题是,“crypt from unistd.h”和“crypt from libcrypt”很可能是同一个函数。链接器不关心哪个头文件中有extern char *crypt(const char *, const char *);,无论你是从-lcrypt 还是隐式-lc 或其他地方获得实现,它现在可能支持的不仅仅是DES。
  • @zwol 感谢您抽出宝贵时间回答我的问题。我正在查看一个经过高度修改的库存 ROM 多用户地牢代码,并且自己没有编写 crypt 部分 - 它是由代码库的原始创建者编写的,所以我认为它是正确编写的 - 猜不!我将使用您的建议进行更新。谢谢。
猜你喜欢
  • 2017-10-18
  • 2010-10-22
  • 1970-01-01
  • 1970-01-01
  • 2013-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多