2021年你还不会Shiro?----4.使用MD5+盐+hash散列进行密码加密

一.前言

上一篇文章里介绍了使用自定义的Realm来实现数据的获取,不过,数据的获取依然不是来源于真实的数据库或者是nosql,只是包装了一个方法,假装从这个方法里获取了数据库中的用户信息,然后我们返回了一个SimpleAccount,这个对象携带了用户名与密码,当时我们是明文返回的密码。这样做很显然是不安全的,一旦数据来源被攻破,所有的用户信息都会被泄露。所以这里我们介绍下,常用的密码加密策略。

二.常用加密策略

我们在日常的开发中可能都是用过不同的加密算法,那么这些算法到底有哪些优势呢,他们的特点又是什么呢?这里简单介绍下几种常用的加密算法。

1.MD5

  1. 什么是MD5
    MD5与我们常用的SM2等算法有很大区别,通常我们使用的加密算法都是可以将密文破解的,比如基于对称算法与非对称算法实现的加密方式,但MD5则不行,MD5的典型特点就是不可逆,也就是说我们加密的信息就不能再反向解密出来了,那么可能有人会说网络上有很多MD5在线解密的网站,注意这些并不是真的解密了MD5加密后的密文,而是使用穷举法,对密文进行一一匹配,来实现的破解,这种暴力破解其实很好规避,不过这样牵扯出了MD5的另一个特点,那就是固定的明文多次加密的结果都是一样的使用MD5算法生成的始终是一个16进制的32位(256bit)的字符串
  2. MD5的应用场景
    ①应用于加密场景,比如登录中的密码加密。
    ②应用于签名,用来判定两个文件是否是同一个文件,同一个文件使用MD5加密后的密文肯定是一致的,这样可以防止文件被篡改。

2.AES

  1. 什么是AES
    AES是一种常用的加密算法,该算法是一种对称加密算法,何为对称加密算法呢,就是加密与解密都需要同一个秘钥,我们常用的AES有AES128,AES192,AES256,这些是什么意思呢,这三种AES其实就是指不同的秘钥的长度,三种AES对应的秘钥长度分别是128位,192位,256位,换算成字节就是16,24,32。比如这样一个秘钥:String str= “1234567812345678”,那么使用这个秘钥的AES自然就是,用的AES128了。

  2. AES的使用场景
    在实际的项目中通常前后端数据的交互都是采用AES加密,这样就可以规避敏感信息的泄露,不过值得说的是通常都不是仅仅对AES进行简单的加密,加密完以后还会对密文进行Base64加密,然后才会进行交互,这样就保证了信息传输的安全性,不过没有百分百的安全,只要是密文能被解出来,就会存在破解的风险,风险都是相对的,我们能做的就是相对安全。

三.使用MD5+盐+hash散列实现登录

我们已经都知道了,使用MD5加密,我们依然可以使用穷举法来实现暴力破解,这样就需要用户设置密码时不能使用简单的密码,简单的密码很容易就会被穷举到,但是用户在使用时是没有这样的意识的,所以就需要我们为用户的密码加点“盐”,让密码变得更咸一点,使密码不会这么容易被破解出来。所谓的这个盐呢,其实就是为密码拼接一串我们随机生成的字符串。此外我们还可以为MD5+盐生成的16进制的值进行hash散列,这样就会让密码更加的安全,即使数据库被攻破,盐与密码都被获取了,散列次数一样还是不会被发现,当然了这样情况也是极少碰到的。

1.如何使用Shiro的MD5,对数据进行加密

那么如何使用Shiro提供的MD5呢,我们看下下面的代码就明白了了

public class Test {
    
    
    public static void main(String[] args) {
    
    
        Md5Hash md5Hash = new Md5Hash("123");
        System.out.println(md5Hash.toHex());
        Md5Hash md5Hash1 = new Md5Hash("123","12345&8");
        System.out.println(md5Hash1.toHex());
        Md5Hash md5Hash2 = new Md5Hash("123","12345&8",1024);
        System.out.println(md5Hash2.toHex());

    }
}

上面的程序输出结果如下所示:

202cb962ac59075b964b07152d234b70
19d11f5191d12b1c224a7d535e1cb650
cc24303e7e1827dbb9742d1f4efeb0ec

Process finished with exit code 0

根据上面的例子,我们可以很形象的看出哈,第一个例子是只使用了MD5进行加密的数据,第二步部分是加上了“盐”的MD5加密,第三部分则是在第二部分的基础上对值进行散列,其实根据这样思路我们可以对密码进行很多种加密,比如对盐进行散列,对盐也进行MD5加密,或者对盐使用Base64,或者是AES等,都可以,我们可以组合出很多的加密,不过其实是没有必要的,没有绝对的安全,但是一般我们使用MD5+“盐”+hash散列就已经可以了。

2.使用MD5+盐+hash散列实现登录

下面我们就来看下使用Shiro实现的登录场景中是如何使用这种加密的方式呢,前面的文章已经介绍过,在密码验证的地方有个密码匹配器,默认的密码匹配器就是做equals比较密码的,既然我们不想使用equals直接比较,那么就需要更换这个密码匹配器,那么我们怎么更换这个密码匹配器呢?

1.密码匹配器在哪里

前面我们已经说过密码的校验是在这里完成的,如下图

在这里插入图片描述
该方法的第一行就是密码匹配器了,密码匹配器是该类的一个属性,所以我们想要更换密码匹配器就为这个属性set一个我们想要的密码匹配器就可以实现更换。

2.匹配匹配器在哪里换

我们已经知道密码匹配器在哪里了,那我们想要更换难道是在这个密码匹配器所属的类中更换吗,当然不是我们写的,真正的更换地方应该使我们写的自定义realm中去进行更换,我们通常会为安全管理器设置realm,在设置realm之前我们需要更换密码匹配器,还需要告诉这个realm我们使用哪种加密算法。那么我们对应的代码就应该是下面这个样子了

public class TestAuthenticator2 {
    
    
    public static void main(String[] args) throws Exception{
    
    
        //定义安全管理器
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        //定义一个支持MD5+盐+hash散列的密码匹配器
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //告诉密码匹配器密文是哪种加密方式
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        //告诉密码匹配器,密码被散列的次数,这个一般定义为1024的倍数,默认一次
        hashedCredentialsMatcher.setHashIterations(1024);
        //定义自己的realm
        SecondRealm secondRealm = new SecondRealm();
        //为realm设置密码匹配器
        secondRealm.setCredentialsMatcher(hashedCredentialsMatcher);
        //为安全管理器设置realm
        defaultSecurityManager.setRealm(secondRealm);
        //模拟用户登录场景
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhaoyun","123");
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        try {
    
    
            Subject subject = SecurityUtils.getSubject();
            subject.login(usernamePasswordToken);
            System.out.println("登录成功");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

既然我们已经告诉了安全管理器,我数据库存储的是MD5+盐+hash散列实现的密文,那么我们的realm再像以前那么写就不正确了。

3.自定义realm

在我们不对密码进行加密时我们模拟的realm获取的都是明文的用户名与密码返回给了密码匹配器,那么我们想使用MD5+盐+hash散列的方式去实现密码加密,以前的信息返回肯定会报错,那我们该如何写呢,请看下面的代码

public class SecondRealm extends AuthorizingRealm {
    
    
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
    
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    
    
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)authenticationToken;
        String userName = usernamePasswordToken.getUsername();
        //获取数据库中的用户信息
//        SimpleAccount simpleAccount = new SimpleAccount("zhaoyun","202cb962ac59075b964b07152d234b70","df",this.getName());
        SimpleAuthenticationInfo simpleAuthenticationInfo = getSimpleInfo();
        //验证用户名与token用户名是否相同
        if(simpleAuthenticationInfo.getPrincipals().asList().contains(userName)){
    
    
            return simpleAuthenticationInfo;
        }else{
    
    
            return  null;
        }
    }

    private SimpleAuthenticationInfo getSimpleInfo(){
    
    
        Md5Hash md5Hash = new Md5Hash("123");
        Md5Hash md5Hash1 = new Md5Hash("123","12345&8");
        Md5Hash md5Hash2 = new Md5Hash("123","12345&8",1024);

        return new SimpleAuthenticationInfo("zhaoyun",md5Hash2, ByteSource.Util.bytes("12345&8"),this.getName());
    }
}

有一点要说下在上一个例子中我们实现自定义realm,是去继承AuthenticatingRealm,这个例子里我们是去继承的AuthorizingRealm,这两个Realm一个是做认证的,一个是授权的(第一个认证),而且用作授权的AuthorizingRealm是AhthenticatingRealm的子类,所以我们可以直接继承AuthorizingRealm也是正确的,就像上方的代码,继承了这个类,就要求我们重写认证和授权两块的代码了。授权这部分还未涉及到,我们暂时先不说,还继续看认证的部分。
根据这个方法SimpleAuthenticationInfo(),我们可以看到里面写了三个Md5Hash的对象,第一个是只用了Md5,第二个使用了随机盐,随机盐即使一组随机的字符串,第三个则是使用了MD5+盐+散列1024次生产的密文,
细心的同学可能会发现哈,我们在为realm设置密码匹配器时并没有告诉ream随机盐是多少,只告诉了密码匹配器密文是何种加密方式,需要散列多少次。

4.密码匹配器怎么知道随机盐是多少的

其实很简单,我们在完成用户名验证时,就会返回一个账户信息也就是SimpleAuthenticatingInfo该类的对象,这个对象包含了密码,已经盐,在到达密码匹配器时,会根据是否传了盐来去动态获取盐的值,而不需要我们在用户登录时直接携带者盐,况且用户登录携带着盐也是极不安全的,所以我们并不需要为密码匹配器设置盐。

5.使用MD5+盐+hash散列成功登录

运行上方的代码我们可以得到结果如下:

登录成功

Process finished with exit code 0

感兴趣的大佬可以多试试,这里就不罗列其他的几种密码使用场景了。

四.总结

这篇文章主要介绍了MD5在使用Shiro登录时的的应用场景,我们使用MD5+盐+hash散列的方式,可以很大程度上保证密码的安全性,在日常的开发中其实,密码加你也是这种,就比如我前阵子在做的项目,前后端交互在对参数进行加密时就是使用AES+Base64进行双重加密的。

猜你喜欢

转载自blog.csdn.net/m0_46897923/article/details/114906453