前言
密码认证是身份认证中最常用的方法。密码作为证明身份的凭证,一但发生泄露,会对用户的信息安全造成极大的威胁。作为开发人员,在开发用户系统时要充分考虑,明文存储、明文传输是非常不明智的行为,极易造成密码泄露。
这里记录一下自己使用过的一种密码加密和存储的方法。
后端
后端直接管理密码的存储,需要保证密码存储的安全。给密码进行哈希加密是一种常见的做法,这样即使数据库发生泄露,已被哈希加密的密码也无法被直接使用。而为了防止用户设置简单的密码被暴力破解,哈希加密前往往还会向原密码中加入盐值。
这里使用SHA256进行哈希加密,随机生成32~64位的盐值,随加密后的密码一起保存到数据库中。
编写一个加密工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom;
public class EncryptUtil {
public static String Encrypt(String password, String salt){ String str = salt + password; MessageDigest messageDigest; String encodeStr = ""; try { messageDigest = MessageDigest.getInstance("SHA-256"); messageDigest.update(str.getBytes(StandardCharsets.UTF_8)); encodeStr = byteToHex(messageDigest.digest()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return encodeStr; }
public static String getSalt(){ SecureRandom random = new SecureRandom();
int randomInt = random.nextInt(16) + 16; byte[] bytes = new byte[randomInt]; random.nextBytes(bytes);
return byteToHex(bytes); }
private static String byteToHex(byte[] bytes) { StringBuilder stringBuilder = new StringBuilder(); String temp; for (byte aByte : bytes) { temp = Integer.toHexString(aByte & 0xFF); if (temp.length() == 1) { stringBuilder.append("0"); } stringBuilder.append(temp); } return stringBuilder.toString(); } }
|
由此,只需使用String salt = EncryptUtil.getSalt()
即可生成随机盐值,再使用String encryptedPassword = EncryptUtil.Encrypt(password, salt)
即可得到加密后的密码。
将新用户存入数据库
1 2 3 4 5 6 7 8 9 10
| String salt = EncryptUtil.getSalt(); String encryptedPassword = EncryptUtil.Encrypt(password,salt);
User newUser = new User(); newUser.setUserName(userName); newUser.setUserPassword(encryptedPassword); newUser.setSalt(salt); boolean saveResult = userService.save(newUser); ...
|
用户登录校验
结合Mybatis查询数据库。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| QueryWrapper<User> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("user_name", userName); User user = userMapper.selectOne(queryWrapper); if(user == null){ ... }
String encryptedPassword = EncryptUtil.Encrypt(password, user.getSalt()); if(!encryptedPassword.equals(user.getUserPassword())){ ... }
...
|
前端
前端同样对密码进行一次哈希加密,使用crypto-js
包,以网站域名或是用户名或是其他自定义的字符串作为盐值,进行SHA256哈希加密。
1 2 3 4 5 6 7
| import CryptoJS from 'crypto-js';
const handleSubmit = async (values) => { ... values.password = CryptoJS.SHA256(values.userName! + values.password).toString(); ... }
|
前端加密的目的是为了防止请求被破解后原密码泄露,这样用户即使在其他地方也使用了相同的密码,也无法使用泄露的密码登录。前端加盐的目的则是为了让哈希加密更难以破解,即使原密码非常简单,加盐后也使得破解难度大大增加。
值得注意的是,如果请求真的被破解了,那么前端加密后的密码实际上已经相当于用户的登录凭证,可以直接使用加密后的密码发起请求进行登录,所以注册、登录以及各种数据交互的安全性归根到底还是要由https来保证的。