如何从数据库中储存和使用 shiro 的盐

Posted

技术标签:

【中文标题】如何从数据库中储存和使用 shiro 的盐【英文标题】:How to stock and use a shiro's salt from database 【发布时间】:2012-09-11 20:57:48 【问题描述】:

我在应用程序中使用 shiro 进行身份验证。我使用带盐的哈希密码,并将它们存储在我的数据库中,如下所示:

    private User createUserWithHashedPassword(String inName, String inFirstName, String inLastName, String inPassword)

    ByteSource salt  = randomNumberGenerator.nextBytes(32);

    byte[] byteTabSalt  = salt.getBytes();

    String strSalt = byteArrayToHexString(byteTabSalt);

    String hashedPasswordBase64 = new Sha256Hash(inPassword, salt, 1024).toBase64();

    return new User(inName,inFirstName,inLastName,hashedPasswordBase64,strSalt);

我将盐与字符串一起存储在我的数据库中。现在在我的领域中,我想从数据库中取回我的数据,为此我使用了事务服务。但是我的盐是强的,所以我希望它使用静态方法返回为 ByteSource 类型:

ByteSource byteSourceSalt = Util.bytes(salt); //where the salt is a String

但是当我创建我的 SaltedAuthenticationInfo 时,它不会进行身份验证。

我认为我的问题来自我的转换方法:

private String byteArrayToHexString(byte[] bArray)

        StringBuffer buffer = new StringBuffer();

        for(byte b : bArray) 
            buffer.append(Integer.toHexString(b));
            buffer.append(" ");
        

 return buffer.toString().toUpperCase();    

感谢您的帮助。

【问题讨论】:

【参考方案1】:

正如出色的答案https://***.com/a/20206115/603901 中提到的,Shiro 的 DefaultPasswordService 已经为每个密码生成了唯一的盐。

但是,无需实现自定义 PasswordService 即可将私有盐(有时称为“胡椒”)添加到每个用户的盐。可以在 shiro.ini 中配置私有 salt:

[main]
hashService = org.apache.shiro.crypto.hash.DefaultHashService
hashService.hashIterations = 500000
hashService.hashAlgorithmName = SHA-256
hashService.generatePublicSalt = true
# privateSalt needs to be base64-encoded in shiro.ini but not in the Java code
hashService.privateSalt = myVERYSECRETBase64EncodedSalt
passwordMatcher = org.apache.shiro.authc.credential.PasswordMatcher

passwordService = org.apache.shiro.authc.credential.DefaultPasswordService
passwordService.hashService = $hashService
passwordMatcher.passwordService = $passwordService

用于生成匹配密码哈希的 Java 代码:

DefaultHashService hashService = new DefaultHashService();
hashService.setHashIterations(HASH_ITERATIONS); // 500000
hashService.setHashAlgorithmName(Sha256Hash.ALGORITHM_NAME);
hashService.setPrivateSalt(new SimpleByteSource(PRIVATE_SALT)); // Same salt as in shiro.ini, but NOT base64-encoded.
hashService.setGeneratePublicSalt(true);

DefaultPasswordService passwordService = new DefaultPasswordService();
passwordService.setHashService(hashService);
String encryptedPassword = passwordService.encryptPassword("PasswordForThisUser");

生成的哈希如下所示:

$shiro1$SHA-256$500000$An4HRyqMJlZ58utACtyGDQ==$nKbIY9Nd9vC89G4SjdnDfka49mZiesjWgDsO/4Ly4Qs=

私有盐不存储在数据库中,如果攻击者获得对数据库转储的访问权限,这使得破解密码变得更加困难。

此示例是使用 shiro-1.2.2 创建的

感谢https://github.com/Multifarious/shiro-jdbi-realm/blob/master/src/test/resources/shiro.ini 对shiro.ini 语法的帮助

【讨论】:

如何获取由 shiro 自动生成的公共盐,以便将其存储到 db 中的单独列中【参考方案2】:

你看过 PasswordMatcher / PasswordService 吗?

这已经内置了所有的编码/解码/比较逻辑。要使用它:

在数据库中存储密码:

PasswordService service = new DefaultPasswordService(); // or use injection or shiro.ini to populate this

private User createUserWithHashedPassword(String inName, String inFirstName, String inLastName, String inPassword)

  String hashedPasswordBase64 = service.encryptPassword(inPassword);

  return new User(inName,inFirstName,inLastName,hashedPasswordBase64,strSalt);

然后您可以简单地使用 PasswordMatcher 作为您领域中的匹配器。

realm.setCredentialsMatcher(new PasswordMatcher());

或在 shiro.ini 中:

matcher = org.apache.shiro.authc.credential.PasswordMatcher
realm.credentialsMatcher = $matcher

【讨论】:

好的,谢谢您的回答。我会试试这个。但我不明白我现在必须在哪里使用我的盐,以及密码和盐之间的链接在哪里。【参考方案3】:

DefaultPasswordService 实现会自动为每个 encryptPassword 调用添加随机盐。该“公共”盐将存储在您从“encryptPassword”收到的“hashedPasswordBase64”中。

因为“公共”盐是为每个散列密码单独生成的,所以不能“简单地”生成彩虹表并一次暴力破解所有散列密码。对于每个散列密码,由于唯一的“公共”盐,攻击者必须生成自己的唯一彩虹表。到目前为止,您不需要在数据库中添加额外的盐。

为了使您存储的散列密码更加安全,您还可以添加一个“私人”盐,该盐应该存储在其他任何地方 - 只要不在数据库中。通过使用“私有”salt,您可以保护散列密码免受暴力彩虹表攻击,因为攻击者不知道“私有”salt,也无法从数据库条目中获取“私有”salt。

这是一个非常基本的示例,如何创建使用作为常量字符串提供的“私有”salt 并用作 CredentialsMatcher 的 PasswordService:

public class MyPrivateSaltingPasswortService extends DefaultPasswordService

   public MyPrivateSaltingPasswortService()
   
      super();
      HashService service = getHashService();
      if (service instanceof DefaultHashService)
      
         ((DefaultHashService) service).setPrivateSalt(
             new SimpleByteSource("MySuperSecretPrivateSalt"));
      
   

然后你可以在 shiro.ini 中使用你自己的实现:

[main]
saltedService = com.mycompany.MyPrivateSaltingPasswortService
matcher = org.apache.shiro.authc.credential.PasswordMatcher
matcher.passwordService = $saltedService
realm.credentialsMatcher = $matcher

此示例是使用 shiro-1.2.2 创建的

【讨论】:

您能否解释一下:“DefaultPasswordService 实现会自动为每个 encryptPassword 调用添加一个随机盐。那个“公共”盐将存储在您从“encryptPassword”收到的“hashedPasswordBase64”中。”?我不明白这个公共盐必须保存在单独的列(不是密码列)中吗? 到目前为止,您不需要在数据库中添加额外的盐。 - 你是什么意思? 你好Pasha,生成的token包含几个不同的信息块。其中一个块是公共盐,因此 Shiro 可以重新散列并再次测试用户输入的密码。只要您“按原样”存储生成的令牌,您就不需要存储公共盐 - 您甚至不知道它,因为 Shiro 会处理它。请查看 Mikael Falkvidd 的答案 - 有一个生成令牌的示例,还有一个定义私有盐的选项。 谢谢。我明白了。【参考方案4】:

为了节省盐分,我改变了我的类型。现在我使用的是 byte[] 而不是 String。

ByteSource salt  = randomNumberGenerator.nextBytes(32);

byte[] byteTabSalt  = salt.getBytes();

我将 byteTabSalt 存储在我的数据库中。

【讨论】:

以上是关于如何从数据库中储存和使用 shiro 的盐的主要内容,如果未能解决你的问题,请参考以下文章

将 Shiro 的 PasswordMatcher 与自定义领域一起使用

如何使用 Shiro 的 Salted AuthenticationInfo?

生成具有恒定轨道的盐

day02

day02

JAVA学习笔记——