troysps 2020-06-13
哈希算法(Hash)又称摘要算法(Digest),它的作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要。
哈希算法最重要的特点就是:
- 相同的输入一定得到相同的输出;
- 不同的输入大概率得到不同的输出。
- 哈希算法的目的就是为了验证原始数据是否被篡改。
我们来简单实现一个用于用户注册和登录最基本的功能。
在登录中,要检查是否存在某个用户信息,每个用户信息都是唯一的,所以可以借助Set的特性来操作用户信息的存放。
在注册中,要检查用户名是否已经被注册,而每个用户名也是唯一的,所以在这里也利用Set来操作用户名的存放。
当然,也可以用Map来存放用户名和用户密码,K存放用户名,对应的V存放密码。但是为了让用户名和密码的关联度尽可能的小一些,所以利用两个Set来分别存放用户名和用户信息。
由于Set是无序的,所以当黑客获取到这两个数据文件的时候也很难将用户名对应到相应的用户信息。
这里的用户信息指的是将用户名和密码混合后的信息,例如某个用户的信息是"admin",密码是"password",那么可以将这两个字段混合来达到增长信息量的目的。
当然,为了让安全性更高,可以利用特定的排列组合将两个字符串混合,比如可以将两个字符串拆解成字符数组,按照数组下标的奇偶数来排列两个字符串。
例如"admin"的长度小于"password",因此以"admin"为基准,‘a‘为起始,"admin"占奇数位,"passw"占偶数位,剩余字符连接在生成字段后,即"apdamsisnword",就像把用户名插入到了密码中。
还有一种方法是对每个生成的用户信息添加随机字符,这个方法被称为“加盐”。
例如,用户名和密码依然是"admin"和"password",我们设置一个随机salt = "aRandomSalt",然后将这个salt加入到用户名和密码之中,比如"admin" + salt + "password",salt + "admin" + "password"或是其他更复杂的组合。
后续的代码中,简单的将用户名和密码连接在了一起,即"adminpassword"
package service; import java.math.BigInteger; import java.security.MessageDigest; import java.util.HashSet; import java.util.Set; import dao.Dao; // 储存相关配置文件 import dao.UserInfoDao; // 用于将用户信息存盘 import entity.UserInfo; // 用户信息实体类,其中的两个类成员是userName和userPassword,即用户名和密码 public class UserInfoService extends Dao { private String userInfoPath; // 用户信息保存的文件路径 private String userNamePath; // 用户名保存的文件路径 private UserInfoDao dao = new UserInfoDao(); public UserInfoService() { super(); userInfoPath = super.getResource().getString("userInfo"); userNamePath = super.getResource().getString("userName"); } /** * 用户登录。若用户信息存在,则登录成功;若用户信息不存在,则登录失败 * * @param userInfo * @return 提示信息 */ public String userSignIn(UserInfo userInfo) { Set<String> userInfoSet = null; String tips; userInfoSet = dao.readInfo(userInfoPath); // 从相关文件中读取用户信息 if (userInfoSet == null) { // 若尚无用户注册,则new HashSet<String>(),避免NullPointerException userInfoSet = new HashSet<String>(); } if (userInfoSet.contains(getUserInfoHashCode(userInfo))) { // 判断是否含有相关用户信息 tips = "登录成功!"; } else { tips = "登录失败!请检查用户名或密码"; } return tips; } /** * 用户注册。若用户名不存在,则注册成功;若用户名存在,则注册失败 * * @param userInfo * @return 提示信息 */ public String userSignUp(UserInfo userInfo) { Set<String> userInfoSet = null; Set<String> userNameSet = null; String tips; userInfoSet = dao.readInfo(userInfoPath); // 从相关文件中读取用户信息 userNameSet = dao.readInfo(userNamePath); // 从相关文件中读取用户名 if (userInfoSet == null) { // 若尚无用户注册,则new HashSet<String>(),避免NullPointerException userInfoSet = new HashSet<String>(); } if (userNameSet == null) { // 若尚无用户注册,则new HashSet<String>(),避免NullPointerException userNameSet = new HashSet<String>(); } if (userNameSet.add(userInfo.getUserName())) { // 判断用户名是否已注册 userInfoSet.add(getUserInfoHashCode(userInfo)); // 若用户名未注册,则将用户信息添加至Set中 dao.saveInfo(userInfoSet, userInfoPath); // 保存用户信息到相关文件 dao.saveInfo(userNameSet, userNamePath); // 保存用户名到相关文件 tips = "注册成功!"; } else { tips = "注册失败!用户已存在"; } return tips; } /** * 以预设算法SHA-1加密用户名和密码,以预设基数36位保存 * * @param userInfo * @return 加密后的用户信息 */ public String getUserInfoHashCode(UserInfo userInfo) { return getUserInfoHashCode(userInfo, "SHA-1", 36); // 用SHA-1算法生成用户信息密钥,进制为36进制 } /** * 以指定算法algorithm加密用户名和密码,以指定基数radix长度保存 * * @param userInfo * @param algorithm * @param radix * @return 加密后的用户信息 */ public String getUserInfoHashCode(UserInfo userInfo, String algorithm, int radix) { try { MessageDigest md = MessageDigest.getInstance(algorithm); // 用指定算法algorithm创建一个MessageDigest实例 md.update((userInfo.getUserName() + userInfo.getUserPassword()).getBytes("UTF-8")); // 将用户名和密码合并,调用update()输入数据 byte[] res = md.digest(); // 将摘要存放在byte[]中 return new BigInteger(1, res).toString(radix); // 返回一个指定进制基数为radix的字符串 } catch (Exception e) { e.printStackTrace(); return ""; // 若异常则返回空字符串 } } }