《跟我學Shiro》學習筆記 第四章:編碼/加密

weixin_33860553發表於2018-05-02

前言

密碼儲存應該採用加密/生產密碼摘要儲存。而不是採用明文儲存,這期我們就來學習一下Shiro在編碼與加密方面的功能。

4.1編碼/解碼

Shiro提供了base64和16進位制字串編碼/解碼的API支援,方便一些編碼解碼操作。Shiro內部的一些資料的儲存/表示都使用了base64和16進位制字串。

@Test
public void base64encode(){
    String str = "hello";
    String base64Encoded = Base64.encodeToString(str.getBytes());

    log.info("編碼後:"+base64Encoded);

    String str2 = Base64.decodeToString(base64Encoded);

    log.info("解碼後:"+str2);
}

通過如上方式可以進行base64編碼/解碼操作。

@Test
public void hexEncode(){
    String str = "hello";
    String base64Encoded = Hex.encodeToString(str.getBytes());
    log.info("編碼後:"+base64Encoded);
    String str2 = new String(Hex.decode(base64Encoded.getBytes()));
    log.info("解碼後:"+str2);
}

通過如上方式可以進行16進位制字串編碼/解碼操作。

4.2雜湊演算法

雜湊演算法一般用於生成資料的摘要資訊,是一種不可逆的演算法,一般適合儲存密碼之類的資料,常見的雜湊演算法如MD5、SHA等。一般進行雜湊時最好提供一個salt(鹽),比如加密密碼“admin”,產生的雜湊值是“21232f297a57a5a743894a0e4a801fc3”,可以到一些md5解密網站很容易的通過雜湊值得到密碼“admin”,即如果直接對密碼進行雜湊相對來說破解更容易,此時我們可以加一些只有系統知道的干擾資料,如使用者名稱和ID(即鹽);這樣雜湊的物件是“密碼+使用者名稱+ID”,這樣生成的雜湊值相對來說更難破解。

@Test
public void md5(){
    String str = "hello";
    String salt = "123";
    String md5 = new Md5Hash(str,salt).toString();
    log.info(md5);
}

md5演算法雜湊。

@Test
public void sha256(){
    String str = "hello";
    String salt = "123";
    String sha256 = new Sha256Hash(str,salt).toString();
    log.info(sha256);
}

sha256雜湊演算法。

Shiro還提供了通用的雜湊支援:

@Test
public void messageDigest(){
    String str = "hello";
    String salt = "123";
    //內部使用MessageDigest
    String simpleHash = new SimpleHash("SHA-1", str, salt).toString();
    log.info(simpleHash);
}

通過呼叫SimpleHash時指定雜湊演算法,其內部使用了Java的MessageDigest實現。

為了方便使用,Shiro提供了HashService,預設提供了DefaultHashService實現。

@Test
public void testHashService() {
    //預設演算法SHA-512
    DefaultHashService hashService = new DefaultHashService();
    hashService.setHashAlgorithmName("SHA-512");
    //私鹽,預設無
    hashService.setPrivateSalt(new SimpleByteSource("123"));
    //是否生成公鹽,預設false
    hashService.setGeneratePublicSalt(true);
    //用於生成公鹽。預設就這個
    hashService.setRandomNumberGenerator(new SecureRandomNumberGenerator());
    //生成Hash值的迭代次數
    hashService.setHashIterations(1);

    HashRequest request = new HashRequest.Builder()
    .setAlgorithmName("MD5").setSource(ByteSource.Util.bytes("hello"))
    .setSalt(ByteSource.Util.bytes("123")).setIterations(2).build();
    String hex = hashService.computeHash(request).toHex();
    log.info(hex);
}
  1. 首先建立一個DefaultHashService,預設使用SHA-512演算法;
  2. 可以通過hashAlgorithmName屬性修改演算法;
  3. 可以通過privateSalt設定一個私鹽,其在雜湊時自動與使用者傳入的公鹽混合產生一個新鹽;
  4. 可以通過generatePublicSalt屬性在使用者沒有傳入公鹽的情況下是否生成公鹽;
  5. 可以設定randomNumberGenerator用於生成公鹽;
  6. 可以設定hashIterations屬性來修改預設加密迭代次數;
  7. 需要構建一個HashRequest,傳入演算法、資料、公鹽、迭代次數。

4.3加密/解密

Shiro還提供對稱式加密/解密演算法的支援,如AES、Blowfish等;當前還沒有提供對非對稱加密/解密演算法支援,未來版本可能提供。

AES演算法實現:

@Test
public void aes() {
    AesCipherService aesCipherService = new AesCipherService();
    //設定key長度
    aesCipherService.setKeySize(128);
    //生成key
    Key key = aesCipherService.generateNewKey();
    String text = "hello";
    //加密
    String encrptText = aesCipherService.encrypt(text.getBytes(), key.getEncoded()).toHex();
    log.info(encrptText);
    //解密  
    String text2 = new String(aesCipherService.decrypt(Hex.decode(encrptText), key.getEncoded()).getBytes());

    log.info(text2);
}

4.4PasswordService/CredentialsMatcher

Shiro提供了PasswordService及CredentialsMatcher用於提供加密密碼及驗證密碼服務。

public interface PasswordService {  
    //輸入明文密碼得到密文密碼  
    String encryptPassword(Object plaintextPassword) throws IllegalArgumentException;  
}  
public interface CredentialsMatcher {  
    //匹配使用者輸入的token的憑證(未加密)與系統提供的憑證(已加密)  
    boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info);  
}   

Shiro預設提供了PasswordService實現DefaultPasswordService;CredentialsMatcher實現PasswordMatcher及HashedCredentialsMatcher(更強大)。

DefaultPasswordService配合PasswordMatcher實現簡單的密碼加密與驗證服務

  1. 定義Realm

    public class MyRealm extends AuthorizingRealm {
    
        private PasswordService passwordService;
    
        public void setPasswordService(PasswordService passwordService){
            this.passwordService = passwordService;
        }
    
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            return null;
        }
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            return new SimpleAuthenticationInfo("zhang",passwordService.encryptPassword("123"),getName());
        }
    }
    

    為了方便,直接注入一個passwordService來加密密碼,實際使用時需要在Service層使用passwordService加密密碼並存到資料庫。

  2. ini配置(shiro-passwordservice.ini)

    [main]
    passwordService=org.apache.shiro.authc.credential.DefaultPasswordService
    hashService=org.apache.shiro.crypto.hash.DefaultHashService
    passwordService.hashService=$hashService
    hashFormat=org.apache.shiro.crypto.hash.format.Shiro1CryptFormat
    passwordService.hashFormat=$hashFormat
    hashFormatFactory=org.apache.shiro.crypto.hash.format.DefaultHashFormatFactory
    passwordService.hashFormatFactory=$hashFormatFactory
    
    passwordMatcher=org.apache.shiro.authc.credential.PasswordMatcher
    passwordMatcher.passwordService=$passwordService
    
    myRealm=com.zhaojun.shiro.chapter4.realm.MyRealm
    myRealm.passwordService=$passwordService
    myRealm.credentialsMatcher=$passwordMatcher
    securityManager.realms=$myRealm
    
    • passwordService使用DefaultPasswordService,如果有必要也可以自定義;
    • hashService定義雜湊密碼使用的HashService,預設使用DefaultHashService(預設SHA-256演算法);
    • hashFormat用於對雜湊出的值進行格式化,預設使用Shiro1CryptFormat,另外提供了Base64Format和HexFormat,對於有salt的密碼請自定義實現ParsableHashFormat然後把salt格式化到雜湊值中;
    • hashFormatFactory用於根據雜湊值得到雜湊的密碼和salt;因為如果使用如SHA演算法,那麼會生成一個salt,此salt需要儲存到雜湊後的值中以便之後與傳入的密碼比較時使用;預設使用DefaultHashFormatFactory;
    • passwordMatcher使用PasswordMatcher,其是一個CredentialsMatcher實現;
    • 將credentialsMatcher賦值給myRealm,myRealm間接繼承了AuthenticatingRealm,其在呼叫getAuthenticationInfo方法獲取到AuthenticationInfo資訊後,會使用credentialsMatcher來驗證憑據是否匹配,如果不匹配將丟擲IncorrectCredentialsException異常。

    HashedCredentialsMatcher實現密碼驗證服務

    Shiro提供了CredentialsMatcher的雜湊實現HashedCredentialsMatcher,和之前的PasswordMatcher不同的是,它只用於密碼驗證,且可以提供自己的鹽,而不是隨機生成鹽,且生成密碼雜湊值的演算法需要自己寫,因為能提供自己的鹽。

    1. 生成密碼雜湊值

      此處我們使用MD5演算法,“密碼+鹽(使用者名稱+隨機數)”的方式生成雜湊值:

      String algorithmName = "md5";  
      String username = "liu";  
      String password = "123";  
      String salt1 = username;  
      String salt2 = new SecureRandomNumberGenerator().nextBytes().toHex();  
      int hashIterations = 2;  
        
      SimpleHash hash = new SimpleHash(algorithmName, password, salt1 + salt2, hashIterations);  
      String encodedPassword = hash.toHex();   
      

    如果要寫使用者模組,需要在新增使用者/重置密碼時使用如上演算法儲存密碼,將生成的密碼及salt2存入資料庫(因為我們的雜湊演算法是:md5(md5(密碼+username+salt2)))。

    1. 生成Realm

      @Override
          protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
              //使用者名稱及salt1
              String username = "liu";
              //加密後的密碼
              String password = "202cb962ac59075b964b07152d234b70";
              String salt2 = "202cb962ac59075b964b07152d234b70";
              //鹽是使用者名稱+隨機數
              SimpleAuthenticationInfo ai =
                      new SimpleAuthenticationInfo(username, password, getName());
              ai.setCredentialsSalt(ByteSource.Util.bytes(username+salt2));
              return ai;
          }
      

      如果使用JdbcRealm,需要修改獲取使用者資訊(包括鹽)的sql:“select password, password_salt from users where username = ?”,而我們的鹽是由username+password_salt組成,所以需要通過如下ini配置修改:

      jdbcRealm.saltStyle=COLUMN  
      jdbcRealm.authenticationQuery=select password, concat(username,password_salt) from users where username = ?  
      jdbcRealm.credentialsMatcher=$credentialsMatcher 
      

      1、saltStyle表示使用密碼+鹽的機制,authenticationQuery第一列是密碼,第二列是鹽;

      2、通過authenticationQuery指定密碼及鹽查詢SQL;


張開濤的部落格:http://jinnianshilongnian.iteye.com/category/305053

我的部落格:https://zhaojun0193.github.io

本文程式碼地址:https://github.com/zhaojun0193/shiro-example

相關文章