海口顶尖网站建设青岛网络优化哪家专业
目录
前言
一、登录认证密码加密
二、bcrypt加密密码不一样,匹配原理
1.程序运行现象
2.原理解释
三、密码防重放
总结
前言
密码重放攻击:请求被攻击者获取,并重新发送给认证服务器,从而达到认证通过的目的
一、登录认证密码加密
Spring Security 中内置的加密算法BCrypt
,号称最安全的加密算法,除了加盐来抵御rainbow table 攻击之外,bcrypt的一个非常重要的特征就是自适应性,可以保证加密的速度在一个特定的范围内,即使计算机的运算能力非常高,可以通过增加迭代次数的方式,使得加密速度变慢,从而可以抵御暴力搜索攻击。Bcrypt可以简单理解为它内部自己实现了随机加盐处理。使用Bcrypt,每次加密后的密文是不一样的
public interface PasswordEncoder {String encode(CharSequence rawPassword);boolean matches(CharSequence rawPassword, String encodedPassword);default boolean upgradeEncoding(String encodedPassword) {return false;}
}
encode:方法接受的参数是原始密码字符串,返回值是经过加密之后的hash值,hash值是不能被逆向解密的。这个方法通常在为系统添加用户,或者用户注册的时候使用。
matches:方法是用来校验用户输入密码rawPassword,和加密后的hash值encodedPassword是否匹配。如果能够匹配返回true,表示用户输入的密码rawPassword是正确的,反之返回fasle。也就是说虽然这个hash值不能被逆向解密,但是可以判断是否和原始密码匹配。这个方法通常在用户登录的时候进行用户输入密码的正确性校验。
upgradeEncoding:设计的用意是,判断当前的密码是否需要升级。也就是是否需要重新加密?需要的话返回true,不需要的话返回fasle。默认实现是返回false。
二、bcrypt加密密码不一样,匹配原理
1.程序运行现象
public static void main(String[] args) {String passworld = "12345";PasswordEncoder passwordEncoder = new CustomBCryptPasswordEncoder();String encodePasswordOne = passwordEncoder.encode(passworld);System.out.println("encodePasswordOne:"+encodePasswordOne);String encodePasswordTow = passwordEncoder.encode(passworld);System.out.println("encodePasswordTow:"+encodePasswordTow);boolean matchesOne = passwordEncoder.matches(passworld,encodePasswordOne);System.out.println("matchesOne:"+matchesOne);boolean matchesTow = passwordEncoder.matches(passworld,encodePasswordTow);System.out.println("matchesTow:"+matchesTow);}
2.原理解释
a.Bcrypt就是一款加密工具,可以比较方便地实现数据的加密工作。你也可以简单理解为它内部自己实现了随机加盐处理。例如,我们使用MD5加密,每次加密后的密文其实都是一样的,这样就方便了MD5通过大数据的方式进行破解。Bcrypt生成的密文是60位的。而MD5的是32位的。使用BCrypt 主要是能实现每次加密的值都是不一样的
b.源码分析
1) 加密方法,获取了随机盐放入BCrypt.hashpw加密方法中
public String encode(CharSequence rawPassword) {if (rawPassword == null) {throw new IllegalArgumentException("rawPassword cannot be null");}String salt = getSalt();return BCrypt.hashpw(rawPassword.toString(), salt);}private String getSalt() {if (this.random != null) {return BCrypt.gensalt(this.version.getVersion(), this.strength, this.random);}return BCrypt.gensalt(this.version.getVersion(), this.strength);}
2) 匹配方法matches->checkpw->hashpw,hashpw方法把加密后的秘钥放入了随机盐的为止,这个方法里面从密文里面获取了加密时的随机盐real_salt = salt.substring(off + 3, off + 25);从而对新的密码用这个随机盐再加密,进行匹配
public boolean matches(CharSequence rawPassword, String encodedPassword) {if (rawPassword == null) {throw new IllegalArgumentException("rawPassword cannot be null");}if (encodedPassword == null || encodedPassword.length() == 0) {this.logger.warn("Empty encoded password");return false;}if (!this.BCRYPT_PATTERN.matcher(encodedPassword).matches()) {this.logger.warn("Encoded password does not look like BCrypt");return false;}return BCrypt.checkpw(rawPassword.toString(), encodedPassword);}public static boolean checkpw(String plaintext, String hashed) {return equalsNoEarlyReturn(hashed, hashpw(plaintext, hashed));}
public static String hashpw(byte passwordb[], String salt) {BCrypt B;String real_salt;byte saltb[], hashed[];char minor = (char) 0;int rounds, off;StringBuilder rs = new StringBuilder();if (salt == null) {throw new IllegalArgumentException("salt cannot be null");}int saltLength = salt.length();if (saltLength < 28) {throw new IllegalArgumentException("Invalid salt");}if (salt.charAt(0) != '$' || salt.charAt(1) != '2') {throw new IllegalArgumentException("Invalid salt version");}if (salt.charAt(2) == '$') {off = 3;}else {minor = salt.charAt(2);if ((minor != 'a' && minor != 'x' && minor != 'y' && minor != 'b') || salt.charAt(3) != '$') {throw new IllegalArgumentException("Invalid salt revision");}off = 4;}// Extract number of roundsif (salt.charAt(off + 2) > '$') {throw new IllegalArgumentException("Missing salt rounds");}if (off == 4 && saltLength < 29) {throw new IllegalArgumentException("Invalid salt");}rounds = Integer.parseInt(salt.substring(off, off + 2));real_salt = salt.substring(off + 3, off + 25);saltb = decode_base64(real_salt, BCRYPT_SALT_LEN);if (minor >= 'a') {passwordb = Arrays.copyOf(passwordb, passwordb.length + 1);}B = new BCrypt();hashed = B.crypt_raw(passwordb, saltb, rounds, minor == 'x', minor == 'a' ? 0x10000 : 0);rs.append("$2");if (minor >= 'a') {rs.append(minor);}rs.append("$");if (rounds < 10) {rs.append("0");}rs.append(rounds);rs.append("$");encode_base64(saltb, saltb.length, rs);encode_base64(hashed, bf_crypt_ciphertext.length * 4 - 1, rs);return rs.toString();}
三、密码防重放
1.鉴于每次加密密码都不一样,所以每次前端传过来的密码只能使用一次,我们用redis的setnx功能来实现密码是否被二次使用
/*** 检查密码重放*/protected void checkReplayPassword(String ursername, String password) {String md5Pass = EncryptionUtils.MD5.encrypt(password);boolean absent = loginRecordService.savePassIfAbsent(ursername, md5Pass,securityProperties.getLogin().getPassReplayExpire(), TimeUnit.DAYS);if (absent) {throw new AuthenticationServiceException(LoginExceptions.DUPLICATE_PASSWORD.value());}}
总结
密码防重放攻击方案较多,上述只是其中一种简单的防重放方案,是建立在每次加密密码不一样的基础之上