我面临的问题是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部分:

让我们看一下变量信息:

java - Apache Shiro HashedCredentialsMatcher生成错误的盐-LMLPHP

全字节数组如下:

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个变量,令牌(提交的密码)和信息(存储的密码信息):

java - Apache Shiro HashedCredentialsMatcher生成错误的盐-LMLPHP

存储的凭证:

java - Apache Shiro HashedCredentialsMatcher生成错误的盐-LMLPHP

证书:

d5fHxI7kYQYtyqo6kwvZFDATIIsZThvFQeDVidpDDEQ


解码前的storedBytes:

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


解码后的storedBytes:

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


提交的凭证:

java - Apache Shiro HashedCredentialsMatcher生成错误的盐-LMLPHP

char []凭证:

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


byte []个字节:

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代码是错误的,所以生成密码哈希/盐或shiro.ini配置时我的代码可能有问题吗?)

最佳答案

ufff,在更多地使用这些功能之后,我找到了解决方案,为什么提交的密码用错误的salt值进行散列

我在方法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将至少达到256bit,迭代次数将达到200k-300k。

解决问题后,我将问题缩小为4种可能的选择:

1)

shiro代码(HashedCredentialsMatcher.java)中存在一个主要错误(至少从我的角度来看是这样),因为使用盐进行密码更改总是会以这种方式失败(请参阅代码块内的描述)。

2)

我或者为草率的密码和加盐的密码使用了错误的CredentialsMatcher,我也不知道该使用哪个密码。

3)

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

4)

我在创建密码哈希时出错(尽管该代码来自官方的Apache Shiro网站Link

从我的角度来看,选项1和4并不是问题,因此它的2或3都会导致此问题,并有必要在HashedCredentialsMatcher.java中添加一些代码。方法:hashProvidedCredentials()

因此,结论是,有人对这个问题有任何想法吗?

关于java - Apache Shiro HashedCredentialsMatcher生成错误的盐,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/34535123/

10-16 20:29