【问题标题】:Apache Shiro HashedCredentialsMatcher generates wrong saltApache Shiro HashedCredentialsMatcher 生成错误的盐
【发布时间】:2016-04-04 18:12:15
【问题描述】:

我面临的问题是 Shiro 在转换字节时表现出一些奇怪的行为 数组到盐。

我开始将流程中涉及的所有类实现到我的应用程序中,它们是:

  • org.apache.shiro.realm.AuthenticatingRealm
  • org.apache.shiro.authc.credential.HashedCredentialsMatcher

在创建用户时,用户密码使用生成的盐进行哈希处理,然后存储在我的数据库中:

import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;

RandomNumberGenerator rng = new SecureRandomNumberGenerator();
Object salt = rng.nextBytes();
String hashedPasswordBase64 = new Sha256Hash(password, salt, 1024).toBase64();

shiro.ini 看起来像这样:

# SALTED JDBC REALM

saltedJdbcRealm=com.mycompany.ssp.SaltedJdbcRealm

dataSource = org.postgresql.ds.PGSimpleDataSource
dataSource.databaseName = Self-Service-Portal
dataSource.serverName = localhost
dataSource.portNumber = 5432
dataSource.user = postgres
dataSource.password = admin

saltedJdbcRealm.dataSource = $dataSource
saltedJdbcRealm.authenticationQuery = SELECT umgmt_users.password, umgmt_users.salt FROM umgmt_users WHERE umgmt_users.user = ?

sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher

# base64 encoding, not hex in this example:
sha256Matcher.storedCredentialsHexEncoded = false
sha256Matcher.hashIterations = 1024

saltedJdbcRealm.credentialsMatcher = $sha256Matcher

################################################################################
# SECURITY MANAGER #

securityManager.realms = $saltedJdbcRealm
strategy = org.apache.shiro.authc.pam.FirstSuccessfulStrategy
securityManager.authenticator.authenticationStrategy = $strategy

################################################################################

我的自定义 saltedJdbcRealm 只是覆盖了 doGetAuthenticationInfo。这段代码来自这个博客->

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    //identify account to log to
    UsernamePasswordToken userPassToken = (UsernamePasswordToken) token;
    String username = userPassToken.getUsername();

    if (username == null) {
        log.debug("Username is null.");
        return null;
    }

    // read password hash and salt from db 
    PasswdSalt passwdSalt = getPasswordForUser(username);

    if (passwdSalt == null) {
        log.debug("No account found for user [" + username + "]");
        return null;
    }

    // return salted credentials
    SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, passwdSalt.password, getName());
    info.setCredentialsSalt(new SimpleByteSource(passwdSalt.salt));

    return info;
}

return info 之后的调试如下:

  • AuthenticatingRealm.java:方法:assertCredentialsMatch()
  • HashedCredentialsMatcher.java:方法:doCredentialsMatch()
  • HashedCredentialsMatcher.java:方法:hashProvidedCredentials()

寻找错误我最终在这里找到了 org.apache.shiro.authc.credential.HashedCredentialsMatcher.java:

protected Object hashProvidedCredentials(AuthenticationToken token, AuthenticationInfo info) {
    Object salt = null;
    if (info instanceof SaltedAuthenticationInfo) {

        // STOP HERE AND SEE BELOW PART 1!!!

        salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();

        // STOP HERE AND SEE BELOW PART 2!!!

    } else {
        //retain 1.0 backwards compatibility:
        if (isHashSalted()) {
            salt = getSalt(token);
        }
    }
    return hashProvidedCredentials(token.getCredentials(), salt, getHashIterations());
}

第 1 部分:

让我们看一下变量信息:

完整的字节数组如下:

57 109 102 43 65 87 118 88 70 76 105 82 116 104 113 108 116 100 101 108 79 119 61 61

正确表示我数据库中的盐:

9mf+AWvXFLiRthqltdelOw==

代码中的下一步是从 info 变量中提取 Salt 并将其存储在 Object 类型的变量 salt 中。

第 2 部分:

查看这一行之后的变量 salt:

salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();

执行我得到这个结果:

OW1mK0FXdlhGTGlSdGhxbHRkZWxPdz09

编辑:

我做了另一个例子,向您展示了 2 种方法:1)对提交的密码进行哈希处理 2)从数据库中获取密码以进行比较并且它们不是 一样的:

我从 2 个变量开始,token(提交的密码)和 info(存储的密码信息):

存储的凭据:

凭证:

d5fHxI7kYQYtyqo6kwvZFDATIIsZThvFQeDVidpDDEQ

解码前的存储字节数:

100 53 102 72 120 73 55 107 89 81 89 116 121 113 111 54 107 119 118 90 70 68 65 84 73 73 115 90 84 104 118 70 81 101 68 86 105 100 112 68 68 69 81 61

解码后的存储字节数:

119 -105 -57 -60 -114 -28 97 6 45 -54 -86 58 -109 11 -39 20 48 19 32 -117 25 78 27 -59 65 -32 -43 -119 -38 67 12 68

哈希:

7797c7c48ee461062dcaaa3a930bd9143013208b194e1bc541e0d589da430c44

提交的凭据:

char[] 凭据:

[0] = 1
[1] = 2
[2] = 3

字节[]字节:

50 69 81 77 57 55 80 53 53 112 89 52 122 69 78 54 57 98 53 56 82 65 61 61

这是2EQM97P55pY4zEN69b58RA==,这是数据库中的内容

cachedBase64:

MkVRTTk3UDU1cFk0ekVONjliNThSQT09

返回值是这个哈希:

af9a7ef0ea9fa4d93eae1ca5d16c03c516f4822ec3e9017f14f694175848a6ab

由于 2 个哈希值不相同,我明白为什么我的应用程序告诉我密码错误但我使用上面的代码(第一个代码块)使用密码 123 创建了这个用户

编辑结束

那么有谁知道为什么哈希计算没有为相同的密码给出相同的哈希值???或者我可能做错了什么(我怀疑 shiro 代码是错误的,所以在生成密码 hash/salt 或 shiro.ini 配置时我的代码可能有问题?)

【问题讨论】:

  • looking at the variable salt I get this result: 这是什么意思?变量salt,它应该是调试屏幕截图中#944 的SimpleByteSource。不是这样吗? SimpleAuthenticationInfo 的代码在这里:shiro.apache.org/static/1.2.1/apidocs/src-html/org/apache/shiro/…
  • 我的意思是这条线:salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt(); 在 Stop Part1 和 Part 2 部分之间。该变量被分配在那里,但值错误。我更新了上面的部分以便更好地理解
  • 但变量salt 是什么?它应该与您在Part 1 中看到的SimpleByteSource 相同。
  • 好吧,官方的“shiro-code”将其设置为对象类型Object salt = null;,但我会尝试检查这是否可能是原因
  • 它被声明为Object,但在运行时它是什么?

标签: java shiro


【解决方案1】:

ufff,在对这些函数进行了更多尝试之后,我找到了为什么提交的密码使用错误的盐值散列的解决方案

我在方法hashProvidedCredentials里面加了3行

org.apache.shiro.authc.credential.HashedCredentialsMatcher.java

protected Object hashProvidedCredentials(AuthenticationToken token, AuthenticationInfo info) {
    Object salt = null;
    if (info instanceof SaltedAuthenticationInfo) {
        salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();
        
        // Get base64 Decoder
        java.util.Base64.Decoder Decoder = java.util.Base64.getDecoder();
        // decode salt from database
        byte[] encodedJava8 = null;
        encodedJava8 = Decoder.decode(((SaltedAuthenticationInfo) info).getCredentialsSalt().getBytes());
        // save decoded salt value in previous salt Object
        salt = ByteSource.Util.bytes(encodedJava8);
        
        // The 3 steps above are nessecary because the Object salt is of type 
        // SimpleByteSource and:
        // - it holds a byte[] which holds the salt in its correct form
        // - it also holds a cachedBase64 encoded version of this byte[]
        //   (which is of course not the actual salt)

        // The Problem is that the next method call below that hashes the
        // submitted password uses the cachedBase64 value to hash the
        // passwort and not the byte[] which represents the actual salt

        // Therefor it is nessecary to:
        // - create SimpleByteSource salt with the value from the database
        // - decode the byte[] so that the cachedBase64 represents the actual salt
        // - store the decoded version of the byte[] in the SimpleByteSource variable salt
    } else {
        //retain 1.0 backwards compatibility:
        if (isHashSalted()) {
            salt = getSalt(token);
        }
    }
    return hashProvidedCredentials(token.getCredentials(), salt, getHashIterations());
}

现在用户在登录时提交的密码的哈希方式与以这种方式生成时的方式相同:

RandomNumberGenerator rng = new SecureRandomNumberGenerator();
Object salt = rng.nextBytes();
//Now hash the plain-text password with the random salt and multiple
//iterations and then Base64-encode the value (requires less space than Hex):
String hashedPasswordBase64 = new Sha256Hash(password, salt, 1024).toBase64();

注意:这不是密码散列的最终版本。 Salt 将至少为 256 位,迭代次数将在 200k-300k 左右。

问题解决后,我将问题缩小到 4 个可能的选项:

1)

shiro 代码 (HashedCredentialsMatcher.java) 中有一个重大错误(至少从我的角度来看是这样),因为使用盐的密码验证总是会以这种方式失败(请参阅我在代码块中的描述)。

2)

我要么使用了错误的 CredentialsMatcher 作为 hased & salted 密码,而且我不知道应该改用哪个。

3)

我在自定义领域中的doGetAuthenticationInfo 方法的实现有一个错误。对于我的自定义领域,我使用了本教程: Apache Shiro Part 2 - Realms, Database and PGP Certificates

4)

我在创建密码哈希时犯了一个错误(尽管该代码来自 Apache Shiro 官方网站Link

从我的角度来看,选项 1 和 4 不是问题,因此导致此问题的选项 2 或 3 使得向 HashedCredentialsMatcher.java 方法添加一些代码变得必要:hashProvidedCredentials()

所以总结一下,有没有人对这个问题有任何想法只是为了得到澄清??

【讨论】:

    猜你喜欢
    • 2015-10-24
    • 2015-07-22
    • 1970-01-01
    • 2016-07-15
    • 2014-04-07
    • 2013-06-15
    • 2017-04-14
    • 1970-01-01
    • 2012-05-07
    相关资源
    最近更新 更多