1. 程式人生 > >關於 Spring Security 5 預設使用 Password Hash 演算法

關於 Spring Security 5 預設使用 Password Hash 演算法

賬戶密碼儲存的安全性是一個很老的話題,但還是會頻頻發生,一般的做法是 SHA256(userInputpwd+globalsalt+usersalt) 並設定密碼時時要求長度與大小寫組合,一般這樣設計可以滿足絕大部分的安全性需求。更復雜一些的方案有組合演算法簽名(比如:SHA256 + BCRYPT 組合 ) , 兩步認證,Password Hash 等。

在之前整合  spring-security-oauth2 搭建 OAuth2.0 服務,依賴項 Spring Security 5 預設引入了更安全的加/解密機制,如果之前程式使用純文字的方式儲存使用者密碼與 Client 的金鑰或低版本升級到 Spring Security 5 後可能會出現如下錯誤。

Encoded password does not look like BCrypt
java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
    at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:233)
    at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:
196)

Spring Security 5 對 PasswordEncoder 做了相關的重構,提供了 Password Hash 演算法的實現(bCrypt, PBKDF2, SCrypt 等是最常用的幾種密碼 Hash 演算法),將密碼編碼之後的 hash 值和加密方式一起儲存,原先預設配置的 PlainTextPasswordEncoder 明文密碼被移除了(本身明文儲存密碼也是不合適的一種方式,只適用與測試環境)。

@Bean
public static NoOpPasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
}

createDelegatingPasswordEncoder 方法定義了眾多密碼密碼編碼方式的集合,可以通過使用 PasswordEncoderFactories 類建立一個 DelegatingPasswordEncoder 的方式來解決這個問題。

Reverting to NoOpPasswordEncoder is not considered to be secure. You should instead migrate to using DelegatingPasswordEncoder to support secure password encoding. https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#troubleshooting

/**
 * Used for creating {@link PasswordEncoder} instances
 * @author Rob Winch
 * @since 5.0
 */
public class PasswordEncoderFactories {

    /**
     * Creates a {@link DelegatingPasswordEncoder} with default mappings. Additional
     * mappings may be added and the encoding will be updated to conform with best
     * practices. However, due to the nature of {@link DelegatingPasswordEncoder} the
     * updates should not impact users. The mappings current are:
     *
     * <ul>
     * <li>bcrypt - {@link BCryptPasswordEncoder} (Also used for encoding)</li>
     * <li>ldap - {@link LdapShaPasswordEncoder}</li>
     * <li>MD4 - {@link Md4PasswordEncoder}</li>
     * <li>MD5 - {@code new MessageDigestPasswordEncoder("MD5")}</li>
     * <li>noop - {@link NoOpPasswordEncoder}</li>
     * <li>pbkdf2 - {@link Pbkdf2PasswordEncoder}</li>
     * <li>scrypt - {@link SCryptPasswordEncoder}</li>
     * <li>SHA-1 - {@code new MessageDigestPasswordEncoder("SHA-1")}</li>
     * <li>SHA-256 - {@code new MessageDigestPasswordEncoder("SHA-256")}</li>
     * <li>sha256 - {@link StandardPasswordEncoder}</li>
     * </ul>
     *
     * @return the {@link PasswordEncoder} to use
     */
    public static PasswordEncoder createDelegatingPasswordEncoder() {
        String encodingId = "bcrypt";
        Map<String, PasswordEncoder> encoders = new HashMap<>();
        encoders.put(encodingId, new BCryptPasswordEncoder());
        encoders.put("ldap", new LdapShaPasswordEncoder());
        encoders.put("MD4", new Md4PasswordEncoder());
        encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
        encoders.put("noop", NoOpPasswordEncoder.getInstance());
        encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
        encoders.put("scrypt", new SCryptPasswordEncoder());
        encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
        encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
        encoders.put("sha256", new StandardPasswordEncoder());

        return new DelegatingPasswordEncoder(encodingId, encoders);
    }

    private PasswordEncoderFactories() {}
}
Password Encoding

使用 BCryptPasswordEncoder 編碼(預設

//    @Bean
//    public PasswordEncoder passwordEncoder(){
//        return new BCryptPasswordEncoder();
//    }

    @Bean
    PasswordEncoder passwordEncoder(){
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    /**
     * 配置授權的使用者資訊
     * @param authenticationManagerBuilder
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {

//        authenticationManagerBuilder.inMemoryAuthentication()
//                .withUser("irving")
//                .password(passwordEncoder().encode("123456"))
//                .roles("read");

        authenticationManagerBuilder.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());
    }

使用 PasswordEncoderFactories 類提供的預設編碼器,儲存密碼的格式如下所示( {id}encodedPassword ),然後在加密後的密碼前新增 Password Encoder 各自的識別符號

{bcrypt}$2a$10$oenCzSR.yLibYMDwVvuCaeIlSIqsx0TBY1094.jQ3wgPEXzTrA52.
public class TestBCryptPwd {

    @Bean
    PasswordEncoder passwordEncoder(){
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public PasswordEncoder bcryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public PasswordEncoder pbkdf2PasswordEncoder(){
        return new Pbkdf2PasswordEncoder();
    }

    @Bean
    public PasswordEncoder scryptPasswordEncoder(){
        return new SCryptPasswordEncoder();
    }

    @Test
    public void testPasswordEncoder() {
        String pwd = passwordEncoder().encode("123456");
        String bcryptPassword = bcryptPasswordEncoder().encode("123456");
        String pbkdf2Password = pbkdf2PasswordEncoder().encode("123456");
        String scryptPassword = scryptPasswordEncoder().encode("123456");
        System.out.println(pwd  +"\n"+bcryptPassword +"\n"+pbkdf2Password+"\n"+scryptPassword);

        /*
            {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG 1
            {noop}password 2
            {pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc 3
            {scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=  4
            {sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0 5
        */
    }
}
Password Matching
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder;
String result = encoder.encode("123456");
assertTrue(encoder.matches("123456", result));
Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder();
String result = encoder.encode("123456");
assertTrue(encoder.matches("123456", result));
SCryptPasswordEncoder encoder = new SCryptPasswordEncoder();
String result = encoder.encode("123456");
assertTrue(encoder.matches("123456", result));

示列

    /**
     * 修改使用者密碼
     *
     * @param oldPwd
     * @param newPwd
     * @param userName
     * @return
     */
    @Override
    public Users modifyPwd(String oldPwd, String newPwd, String userName) {
        Users user = this.userRepository.findByUsername(userName);
        //驗證使用者是否存在
        if (user == null) {
            throw new UserFriendlyException("使用者不存在!");
        }
        //驗證原密碼是否正確
        if (!passwordEncoder.matches(oldPwd, user.getPassword())) {
            throw new UserFriendlyException("原密碼不正確!");
        }
        //修改密碼
        user.setPassword(passwordEncoder.encode(newPwd));
        return this.userRepository.save(user);
    }

應該使用哪一種Password Hash?[引用]

PBKDF2、BCRYPT、SCRYPT 曾經是最常用的三種密碼Hash演算法,至於哪種演算法最好,多年以來密碼學家們並無定論。但可以確定的是,這三種演算法都不完美,各有缺點。其中PBKDF2因為計算過程需要記憶體少所以可被GPU/ASIC加速,BCRYPT不支援記憶體佔用調整且容易被FPGA加速,而SCRYPT不支援單獨調整記憶體或計算時間佔用且可能被ASIC加速並有被旁路攻擊的可能。

2013年NIST(美國國家標準與技術研究院)邀請了一些密碼學家一起,舉辦了密碼hash演算法大賽(Password Hashing Competition),意在尋找一種標準的用來加密密碼的hash演算法,並藉此在業界宣傳加密儲存使用者密碼的重要性。大賽列出了參賽演算法可能面臨的攻擊手段:

  • [X] 加密演算法破解(原值還原、雜湊碰撞等,即應滿足Cryptographic Hash的第2、3、4條特性);
  • [X] 查詢表/彩虹表攻擊;
  • [X] CPU優化攻擊;
  • [X] GPU、FPGA、ASIC等專用硬體攻擊;
  • [X] 旁路攻擊;

最終在2015年7月,Argon2演算法贏得了這項競賽,被NIST認定為最好的密碼hash演算法。不過因為演算法過新,目前還沒聽說哪家大公司在用Argon2做密碼加密。

相關推薦

關於 Spring Security 5 預設使用 Password Hash 演算法

賬戶密碼儲存的安全性是一個很老的話題,但還是會頻頻發生,一般的做法是 SHA256(userInputpwd+globalsalt+usersalt) 並設定密碼時時要求長度與大小寫組合,一般這樣設計可以滿足絕大部分的安全性需求。更復雜一些的方案有組合演算法簽名(比如:SHA256 + BCRYPT 組合 )

Spring Security 5中的預設密碼編碼器

1.概述 在Spring Security 4中,可以使用記憶體中身份驗證以純文字格式儲存密碼。 對版本5中的密碼管理過程進行了重大改進,為密碼編碼和解碼引入了更安全的預設機制。這意味著如果您的Spring應用程式以純文字格式儲存密碼,升級到Spring Security 5可能會導致問題。 在這個簡短的教程

spring security 5.0 密碼未加密報錯

bean opp work 加密方式 user enc zhang dmi 之前 使用spring security5.0後,配置文件中直接寫普通的密碼如:123456,會報錯: java.lang.IllegalArgumentException: There is no

Spring Security 5.0的DelegatingPasswordEncoder詳解

地理 The 不同的 fault wpa 存在 版本 直接 tex 本文參考自Spring Security 5.0.4.RELEASE 的官方文檔,結合源碼介紹了 DelegatingPasswordEncoder,對其工作過程進行分析並解決其中遇到的問題。包括 Ther

spring security 5之後密碼加密策略

給大家推薦個靠譜的公眾號程式設計師探索之路,大家一起加油 1.StandardPasswordEncoder, MessageDigestPasswordEncoder, StandardPasswordEncoder 不再推薦使用, 全加上了@Deprecated ,在引用的時候能明顯的看

spring security 5.x 入門及分析

Java Web專案的許可權管理框架,目前有兩個比較成熟且使用較多的框架,Shiro 和 Spring Security ,Shiro 比 Spring Security更加輕量級,但是需要手動配置的東西較多,Spring Security 和 Spring 整合更好,甚至直

spring security 5.x 使用及分析(二:自定義配置—初階)

二、自定義配置(初階): 自定義的配置,就要修改一些預設配置的資訊,從那開始入手呢? 1、第一步:建立Spring Security 的Java配置,改配置建立一個名為springSecurityFilterChain的servlet過濾器,它負責應用程式的安

The best Course to Learn Spring Security 5 for Experienced Java Developers

If you are a Java Spring developer and working with Spring Security then you may be familiar with the "Learn Spring Security" course by Eugen Paraschiv of

Spring Security Web 5.1.2 原始碼解析 -- DefaultWebSecurityExpressionHandler 預設Web安全表示式處理器

概述 DefaultWebSecurityExpressionHandler是Spring Security Web用於Web安全表示式處理器(handler)。它會基於一組預設配置,和當時的環境,對指定的Web安全表示式求值。 DefaultWebSecurityExpress

Spring Security(二十三):6.5 The Default AccessDecisionManager(預設接入策略管理)

This section assumes you have some knowledge of the underlying architecture for access-control within Spring Security. If you don’t you can skip it and com

Spring Security教程(10)---- 自定義登入成功後的處理程式及修改預設驗證地址

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Spring Security Web 5.1.2 原始碼解析 -- HttpSessionSecurityContextRepository

Spring Security Web提供的類HttpSessionSecurityContextRepository是一個SecurityContextRepository介面的實現,用於在HttpSession中儲存安全上下文(security context),這樣屬於同一個Htt

Spring Security 4.x -> 5.x 踩坑記錄

1. AuthenticationManager無法自動注入 在實現AbstractAuthenticationProcessingFilter重寫以使用者名稱、密碼認證時,需要顯示注入AuthenticationManager,不然會報如下錯誤: Caus

Spring Security Config 5.1.2 原始碼解析 -- 工具類 AutowireBeanFactoryObjectPostProcessor

概述 Spring Security的配置機制會使用到很多物件,比如WebSecurity,ProviderManager,各個安全Filter等。這些物件的建立並不是通過bean定義的形式被容器發現和註冊進入容器的。而是由Spring Security配置機制使用Java new操

Spring Security Web 5.1.2 原始碼解析 -- 安全相關Filter清單

名稱 簡介 WebAsyncManagerIntegrationFilter TODO SecurityContextPersistenceFilter

Spring Security Web 5.1.2 原始碼解析 -- SessionManagementFilter

概述 該過濾器會檢測從當前請求處理開始到目前為止的過程中是否發生了使用者登入認證行為(比如這是一個使用者名稱/密碼錶單提交的請求處理過程),如果檢測到這一情況,執行相應的session認證策略(一個SessionAuthenticationStrategy),然後繼續繼續請求的處理。

Spring Security Web 5.1.2 原始碼解析 -- AnonymousAuthenticationFilter

概述 此過濾器過濾請求,檢測SecurityContextHolder中是否存在Authentication物件,如果不存在,說明使用者尚未登入,此時為其提供一個匿名Authentication物件:AnonymousAuthentication。 注意:在整個請求處理的

Spring Security Web 5.1.2 原始碼解析 -- RequestCacheAwareFilter

概述 Spring Security Web對請求提供了快取機制,如果某個請求被快取,它的提取和使用是交給RequestCacheAwareFilter完成的。 系統在啟動時,Spring Security Web會首先嚐試從容器中獲取一個RequestCache bean,獲取

Spring Security Web 5.1.2 原始碼解析 -- HttpSessionRequestCache

概述 Spring Security Web認證機制(通常指表單登入)中登入成功後頁面需要跳轉到原來客戶請求的URL。該過程中首先需要將原來的客戶請求快取下來,然後登入成功後將快取的請求從快取中提取出來。 針對該需求,Spring Security Web 提供了在http se

Spring Security Core 5.1.2 原始碼解析 -- PasswordEncoderFactories

概述 PasswordEncoderFactories是Spring Security建立DelegatingPasswordEncoder物件的工廠類。該工廠所建立的DelegatingPasswordEncoder預設使用bcrypt用於加密,並且能夠用於匹配以下幾種密碼型別 :