【问题标题】:How can I retrieve a salt from LDAP?如何从 LDAP 中检索盐?
【发布时间】:2013-07-17 23:15:12
【问题描述】:

我工作的组织使用 PPolicy(一个 OpenLDAP 模块)自动加盐和散列密码。不幸的是,我无法访问运行 OpenLDAP 服务器的机器,所以我无法查看配置文件。不过从我看到的情况来看,几乎所有内容似乎都是使用默认设置进行设置的。

我希望能够检索特定用户的盐。如果我查看用户的属性,userPassword 是 SSHA 密码。对于该特定用户,我没有看到任何关于盐的信息。我最终查看了 LDAP 架构,但也没有看到任何关于盐的信息。

如果您要猜测每个用户的盐存储在哪里,会在哪里?我知道这很模糊,可能信息不多,但是我在 OpenLDAP 文档中找不到任何地方来解释唯一盐的确切存储位置。也许以前配置过 OpenLDAP 服务器的人会知道默认位置在哪里。

谢谢。

【问题讨论】:

  • 您必须得出结论,肯定没有每个用户的盐值?
  • 每个用户密码确实有一个唯一的盐,但我在任何地方都看不到它 - 不在该用户的正常属性下,不在隐藏属性下,甚至在架构中。

标签: sha1 salt openldap ssha


【解决方案1】:

使用 SSHA,通常将盐附加到 SHA1 散列,然后整个内容是 Base64 编码的(我从未见过不以这种方式执行 SSHA 的 LDAP)。您应该能够通过查看 userPassword 属性来判断这一点。如果它是 28 个字符长,最后有一个 =,它只是散列。

如果 Base64 值的长度为 32 个字符或更大,则它同时包含哈希值和盐值。 Base64 解码值并去掉前 20 个字节,这是 SHA1 哈希。剩下的字节是盐。

例子:

                     Base64 encoded hash with salt
userPassword: {SSHA}MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0

Base64 decoded value
     SHA1 Hash      Salt
--------------------++++
123456789012345678901234

编辑:经过仔细检查,有时似乎支持可变长度的盐。更正了编码说明以解决此问题。

【讨论】:

  • 感谢您的回复。我了解密码是如何加盐的,但是盐存储在 LDAP 中的什么位置?换句话说,当我执行 ldap_bind 时,LDAP 需要对其进行加盐和散列,以便可以将其与记录的加盐和散列密码进行比较。我的问题是它在哪里查找用于散列存储密码的原始盐?
  • 我不是在谈论密码是如何加盐的,原始加盐实际上是与散列一起存储的,然后在 LDAP 需要尝试验证用户密码时被剥离。生成 userPassword 值的算法是Base64Encode(SHA1(salt+password)+salt)。所以在那个 Base64 值里面你有哈希和盐。
  • 啊,我明白了!非常感谢,我会将此标记为答案!我确实有一个后续问题 - 如果数据库中的哈希值被泄露,攻击者不能轻松地剥离盐并使用它来帮助他们破解密码吗?
  • 是的,但这并不是盐的真正用途。使用盐有助于防止攻击者使用彩虹表一次性破解所有密码,或者同时破解多个相同的密码。如果您使用盐,攻击者必须使用自己的盐分别破解每个用户的密码。
  • 其实我认为是Base64Encode(SHA1(password+salt)+salt)(得到SHA1-digested的连接在盐之前有密码(来自tools.ietf.org/id/…))。
【解决方案2】:

Syon 的帖子对我帮助很大,谢谢!我认为对于在这个主题上苦苦挣扎的其他人来说,工作测试会是一个很好的补充;)。

public class SshaPasswordVerifyTest {
    private final static int SIZE_SHA1_HASH = 20;

    @Test
    public void itShouldVerifyPassword() throws Exception{
        String password = "YouNeverGuess!";
        String encodedPasswordWithSSHA = "{SSHA}M6HeeJAbwUCzuLwXbq00Fc3n3XcxFI8KjQkqeg==";
        Assert.assertEquals(encodedPasswordWithSSHA, getSshaDigestFor(password, getSalt(encodedPasswordWithSSHA)));
    }

    // The salt is the remaining part after the SHA1_hash
    private byte[] getSalt(String encodedPasswordWithSSHA){
        byte[] data = Base64.getMimeDecoder().decode(encodedPasswordWithSSHA.substring(6));
        return Arrays.copyOfRange(data, SIZE_SHA1_HASH, data.length);
    }

    private String getSshaDigestFor(String password, byte[] salt) throws Exception{
        // create a SHA1 digest of the password + salt
        MessageDigest crypt = MessageDigest.getInstance("SHA-1");
        crypt.reset();
        crypt.update(password.getBytes(Charset.forName("UTF-8")));
        crypt.update(salt);
        byte[] hash = crypt.digest();

        // concatenate the hash with the salt
        byte[] hashPlusSalt = new byte[hash.length + salt.length];
        System.arraycopy(hash, 0, hashPlusSalt, 0, hash.length);
        System.arraycopy(salt, 0, hashPlusSalt, hash.length, salt.length);

        // prepend the SSHA tag + base64 encode the result
        return "{SSHA}" + Base64.getEncoder().encodeToString(hashPlusSalt);
    }
}

【讨论】:

    【解决方案3】:

    在 PHP 中,这会将纯文本密码(通常由用户输入)与给定的 ssha 哈希(通常存储在您的数据库中)进行比较:

    private function checkSshaPassword($encrypted_password, $password)
    {
        //  get hash and salt from encrypted_password
        $base_64_hash_with_salt = substr($encrypted_password, 6);
        $hash_with_salt = base64_decode($base_64_hash_with_salt);
        $hash = substr($hash_with_salt, 0, 20);
        $salt = substr($hash_with_salt, 20);
    
        //  hash given password
        $hash_given = sha1($password . $salt, true);
    
        return ($hash == $hash_given);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-01-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-25
      • 2011-05-22
      相关资源
      最近更新 更多