zoukankan      html  css  js  c++  java
  • 关于 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  +"
    "+bcryptPassword +"
    "+pbkdf2Password+"
    "+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做密码加密。

    REFER:
    https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#core-services-password-encoding
    https://stackoverflow.com/questions/49582971/encoded-password-does-not-look-like-bcrypt
    https://www.baeldung.com/spring-security-5-default-password-encoder
    https://www.cnkirito.moe/spring-security-6/
    https://stackoverflow.com/questions/6832445/how-can-bcrypt-have-built-in-salts
    https://www.cnblogs.com/xinzhao/p/6035847.html
    http://www.cnblogs.com/cnblogsfans/p/5112167.html
    https://github.com/KaiZhang890/store-password-safely

  • 相关阅读:
    Yii2 高级模板不使用Apache配置目录,将前后台入口移到根目录
    物理路径,相对路径,绝对路径以及根目录
    其他ip无法访问Yii的gii,配置ip就可以
    move_uploaded_file() 函数
    DetailView内匿名函数不可用
    instanceof 用于确定一个 PHP 变量是否属于某一类 class 的实例 , 返回true或者false
    php 判断变量函数
    [HNOI2008] GT考试
    [Bzoj1006][HNOI2008]神奇的国度
    [BZOJ 1005] 明明的烦恼
  • 原文地址:https://www.cnblogs.com/Irving/p/9579025.html
Copyright © 2011-2022 走看看