平时生活中我们会注册很多不同平台账户,但是不同平台对密码的安全校验又不一致 同时如果用同一个密码的话又会导致不安全的因素,这就导致了我们有许多的账号和密码需要记忆。
这时候我就想到了写一个程序来记录这些账号同时将密码加密来提高安全性,这个时候就有人想说 拿一个记事本不是一样的可以记录吗 但是这样一来不就明摆着将账号密码统计起来方面其他人看了,当然了如果你放记事本的设备可以保障安全那也没问题,但是电脑联网嘛总归是有不安全的因素的,所以在想着写一个程序记录一下。

一、效果图:

添加、查看数据
image-1655270795627
查看密码
image-1655270834300

因为是自己使用所以采用的实命令行的输出方式,如果觉得不好看可以改为GUI页面或者搞个前端页面展示也是可以的。

二、具体的实现

密码加密本质也就是字符串加密,程序打算使用java编写为了简洁就直接使用控制台窗口查看数据,加密算法采用的是对称加密因为还想再次查看到数据,但是网上流行的加密貌似都不太安全的样子,而我本身对加密算法了解又不是很深 所以我想到了一个方法:以数量来代替质量

具体的想法就是 定多个加密算法的类,然后随机去使用其中一个类的加密方法,当然这个随机是个伪随机 毕竟还要解密的。
现在思路就明确了 比如我定义了5种加密的方法,取其中的一种取加密,然后保存数据就可以了,其中核心问题就两个:
1、这么多的加密方法怎么动态去调用。
2、怎么做到随机取一个方法。

1、加密方法怎么动态去调用

对于第一个问题我想到了我想到面向对象中的多态性,可以定义一个接口然后让不同的加密类去实现它 重写其中的加密解密方法,这样就可以实现调用接口中的方法最终执行的是子类中的方法,就像

 List list=new ArrayList();
 list.add("");//这里使用的是ArrayList类中的方法

1655117442751
这样的话就会可以实现通过不同的子类来创建类实现对应的方法。
但是 问题又来了,怎么多的之类不可能去一个个写吧,这个时候又想到了java的反射机制,我可以通过类名去创建对应的对象从而来调用,由于我的实现类都放在一个包下,可以获取对应包下所有Class,下面就是具体方法,存放在一个list中,这样我们就完完成了怎么去动态的获取加密类中的方法。

/**
     * 从包package中获取所有的Class
     *
     * @param packageName
     * @return
     */
    public static List<Class<?>> getClasses(String packageName) {

        // 第一个class类的集合
        List<Class<?>> classes = new ArrayList<Class<?>>();
        // 是否循环迭代
        boolean recursive = true;
        // 获取包的名字 并进行替换
        String packageDirName = packageName.replace('.', '/');
        // 定义一个枚举的集合 并进行循环来处理这个目录下的things
        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
            // 循环迭代下去
            while (dirs.hasMoreElements()) {
                // 获取下一个元素
                URL url = dirs.nextElement();
                // 得到协议的名称
                String protocol = url.getProtocol();
                // 如果是以文件的形式保存在服务器上
                if ("file".equals(protocol)) {
                    // 获取包的物理路径
                    String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
                    // 以文件的方式扫描整个包下的文件 并添加到集合中
                    findAndAddClassesInPackageByFile(packageName, filePath, recursive, classes);
                } else if ("jar".equals(protocol)) {
                    // 如果是jar包文件
                    // 定义一个JarFile
                    JarFile jar;
                    try {
                        // 获取jar
                        jar = ((JarURLConnection) url.openConnection()).getJarFile();
                        // 从此jar包 得到一个枚举类
                        Enumeration<JarEntry> entries = jar.entries();
                        // 同样的进行循环迭代
                        while (entries.hasMoreElements()) {
                            // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
                            JarEntry entry = entries.nextElement();
                            String name = entry.getName();
                            // 如果是以/开头的
                            if (name.charAt(0) == '/') {
                                // 获取后面的字符串
                                name = name.substring(1);
                            }
                            // 如果前半部分和定义的包名相同
                            if (name.startsWith(packageDirName)) {
                                int idx = name.lastIndexOf('/');
                                // 如果以"/"结尾 是一个包
                                if (idx != -1) {
                                    // 获取包名 把"/"替换成"."
                                    packageName = name.substring(0, idx).replace('/', '.');
                                }
                                // 如果可以迭代下去 并且是一个包
                                if ((idx != -1) || recursive) {
                                    // 如果是一个.class文件 而且不是目录
                                    if (name.endsWith(".class") && !entry.isDirectory()) {
                                        // 去掉后面的".class" 获取真正的类名
                                        String className = name.substring(packageName.length() + 1, name.length() - 6);
                                        try {
                                            // 添加到classes
                                            classes.add(Class.forName(packageName + '.' + className));
                                        } catch (ClassNotFoundException e) {
                                            // log.error(e.getMessage(), e);
                                        }
                                    }
                                }
                            }
                        }
                    } catch (IOException e) {
                        // log.error(e.getMessage(), e);
                    }
                }
            }
        } catch (IOException e) {
            // log.error(e.getMessage(), e);
        }

        return classes;
    }
    
//具体可以产考:https://blog.csdn.net/weixin_42290901/article/details/102961320?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-102961320-blog-114051070.pc_relevant_aa&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-102961320-blog-114051070.pc_relevant_aa&utm_relevant_index=2

2、随机取一个方法

现在加密的类都放在一个集合里面了,接下来就是考虑怎么确认使用里面的哪一个这时候我想到了
HashMap方法中add方法的底层实现(HashMap底层实现原理),其中当数据新增时计算hash值来确定存在数组的哪个下标中,简单来说就是创建一个长度为5的数组,这个数组的下标不就是0–4嘛,这个时候我通过一个唯一标识的计算得到一个处在这个下标范围的数字,就拿这个数字去获取数组中对应下标的数字不就可以做到随机取了。
下面开始实现:
首先将装有加密类的集合长度获取到,然后将一个字段作为唯一标识(在存储数据的时候定义好,一旦使用这个后就无法在修改了)

    /**
     * 根据key的hash值 再总方法数量中 确认使用哪一个
     * @param key 唯一标识
     * @param total 总的加密算法数量
     * @return
     */
    public int encryptionMethod(Object key,int total){
        int h;
        return (key == null) ? 0 : total& ((h = key.hashCode()) ^ (h >>> 16));
    }

这样两个问题就解决了,定义一个类来做加密解密的过程

@Service
public class recodingService {
    private static int quantity=0;//有多少个加密的类
    private static ArrayList<encryptor> encryptorList=new ArrayList<>();//加密类的Class
    private final ClassUtil classUtil =new ClassUtil();//获取文件下包的工具类
    private final choice choice =new choice();//计算hash值得工具类

    /**
     * 根据 标题 加密密码
     * @param title
     * @param cipher
     * @return
     */
    public String encryptedContent(String title,String cipher){
        int coordinate = this.coordinate(title);
        encryptor encryptor = encryptorList.get(coordinate);
        String inuninini= encryptor.encryption(cipher);
        return inuninini;
    }

    /**
     * 根据 标题 解密密码
     * @param title
     * @param cipher
     * @return
     */
    public String DdecryptContent(String title,String cipher){
        int coordinate = this.coordinate(title);
        encryptor encryptor = encryptorList.get(coordinate);
        String decrypt=encryptor.decrypt(cipher);
        return decrypt;
    }

    /**
     * 根据标题获取使用加密方式坐标
     * @return
     */
    public int coordinate(String title){
        int i = choice.encryptionMethod(title, quantity);
        return i;
    }

    public recodingService() {
        List<Class<?>> clsList = classUtil.getClasses("com.hui.cipher.recoding.algorithm.DefaultUse");
        quantity=(clsList.size()-1);//获取默认加密方法有多少种 获取下标
        try {
        for (Class<?> aClass : clsList) {
            encryptor encryptor=(encryptor) aClass.newInstance();
            encryptorList.add(encryptor);
          
        }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

到此加密的类计算完成了,在需要的地方将这个recodingService类注入进去就可以使用加密解密的方法了。
这个项目中为了使用简单所以数据库使用的是H2,然后数据操作层使用的是jap+mybatis-plus,前者用来在项目启动时创建表,后者这是对数据库进行操作,项目启动后就会在当期文件夹创建一个db文件,其中存的是数据库文件,当然这些路径都是可以在配置文件中修改的。

项目代码可以查看:账号密码加密保存