管理系统必备技(4):关于密码加密那些事

898 阅读8分钟

一、前言

最近在做网络安全等级保护的准备,对系统的安全进行了一遍整理,对这方面有了一定的思考,同时想总结下常见的密码加密方式以及自己使用过的加密算法。本篇文章将从密码学的一些基本问题讲起,并介绍自己实际使用的一些加密算法。

二、密码学相关概念

2.1 密码学四大功能、基本模型

四大功能:

  • 机密性
  • 鉴别(发送方和接收方都能鉴别另一方的身份。也就是第三方无法冒充)
  • 报文完整性(内容在运输过程没有被改变)
  • 不可否认性

基本模型:

2.2 密码学算法分类

  • 消息编程:Base64
  • 消息摘要:MD类、SHA类、MAC(消息验证码,在MD类与SHA类上增加了密钥的支持)
  • 对称密码:DES、3DES、AES(对称密码的标准(最新),增加了密钥的长度)
  • 非对称密码:RSA、DH密钥交换
  • 数字签名:RSASignature(基于RSA)、DSASignature(基于DSA)

2.3 密码学五元组

  • 明文
  • 密文
  • 加密算法
  • 解密算法
  • 密钥(安全依赖于密钥)

2.4 JAVA相关密码学的常用类

1、消息编码

  • Base64Encoder(编码)
  • BASE64Decoder(解码)

2、消息摘要 -MessageDigest

3、对称密码

  • KeyGenerator(密钥生成器)
  • SecretKey(存储密钥)
  • Cipher(加解密功能)

4、非对称密码

  • KeyPairGenerator(密钥生成器——公钥与私钥)
  • KeyFactory(密钥工厂)
  • KeyPair(密钥对)
  • PublicKey(公钥)
  • PrivateKey(私钥)
  • Cipher(加解密功能)

5、数字签名

  • Signature

三、实际应用

在清楚上面的一些分类后,来讲讲三个应用

3.1 关于密码传输安全的问题

根据网络安全的要求,应采用密码技术保证重要数据在传输过程中的保密性,包括但不限定于鉴别数据、重要业务数据和重要个人信息等。

因此,需要对前端的密码进行加密。加密的方式是必须后端进行解密后能还原成原文,因为传输的密码进入后端后需要进行密码复杂度校验。因为永远不能相信前端!所以这里的加密方式只能选择对称加密或者消息编码。对于常见的消息摘要则是不可取的,因为例如md5加密后是不可逆的,所以后端无法还原校验这个密码复杂度。

本项目为了简便,采用了消息编码,通过base64进行前端加密,在经过后端解密后完成密码数据在传输过程中的保密性。

前端部分

在vue项目中npm install 'js-base64'

在main.js中引入全局变量

import {Base64} from "js-base64";
Vue.prototype.$base64= Base64

前端只负责加密,前端加密解密的方式如下:

加密:
this.$data.form.password=this.$base64.encode(this.$data.form.password)
解密:
this.$data.form.password=this.$base64.decode(this.$data.form.password)
后端部分

后端base64加密的方式有很多种。这里写了两个工具类,能够将字符串进行加解密。

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

//base解密
    public static String base64DeCode(String s) {
        byte[] b = null;
        String result = null;
        if (s != null) {
            BASE64Decoder decoder = new BASE64Decoder();
            try {
                b = decoder.decodeBuffer(s);
                result = new String(b);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }
    //base加密
    public static String base64EnCode(String s) {
        byte[] b = s.getBytes();
        String result = null;
        if (s != null) {
            BASE64Encoder encoder = new BASE64Encoder();
            try {
                result = encoder.encodeBuffer(b);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }
//加密密码
loginVO.setPassword(ValidateSafeUtil.base64EnCode(loginDTO.getPassword()));
//解密密码
loginDTO.setPassword(ValidateSafeUtil.base64DeCode(loginDTO.getPassword()));

3.2 关于常见的MD5加密用法

3.2.1 首先写个工具类
import org.apache.commons.codec.digest.DigestUtils;
 
import java.util.UUID;
 
public class Md5UUIDSaltUtil {
    public static String uuid = UUID.randomUUID().toString().replace("-", "");
    //创建md5对象
 
    public static String createMd5Code(String code) {
        return DigestUtils.md5Hex(code);
    }
 
    //进行密码校验
 
    public static boolean checkPassword(String userCode, String dbCode) {
        if (dbCode.equals(createMd5Code(userCode))) {
            return true;
        } else {
            return false;
        }
    }
 
    public static String getUUID() {
        return uuid;
    }
 
    public static String getSalt() {
        String salt = uuid.substring(0, 5);
        return salt;
    }

}
3.2.2 用户注册时的密码+盐加密

当然用户需要把当前加密的盐值放在user字段中,对比的时候,取出这个盐值和密码再次加密即可。

 @Override
    //添加
    public void add(User user) {
        String id = UUID.randomUUID().toString().replace("-","");
        user.setId(id);
        user.setCreateDate(new Date());
        user.setLastUpdateDate(new Date());
        //加盐,并将加过盐的密码 和盐重新存入数据库中
        String salt = Md5UUIDSaltUtil.getSalt();
        String password = Md5UUIDSaltUtil.createMd5Code(user.getPassword()+salt);
        user.setPassword(password);
        user.setSalt(salt);
 
        userDAO.insert(user);
    }
3.2.3 用户登录时的密码校验
 //获取该用户的盐
String salt = user.getSalt();
//将用户输入的密码加上盐 转换为密文
String md5Code = Md5UUIDSaltUtil.createMd5Code(password+salt);
//判断该密码是否与数据库中的密码一致
if(user.getPassword().equals(md5Code)){
     message = "登录成功";
}else{
      message = "密码不正确";
}

3.3 关于一次国密加密的应用

3.3.1 普通加密方式

1、使用对称加密算法来保存,比如3DES、AES等算法,使用这种方式加密是可以通过解密来还原除原始密码的,当然前提条件是需要获取到密钥。不过如果保护密钥不被泄露是件很麻烦的事情,所以这种方式并不是很好的方式。

2、使用MD5等单向HASH算法保护密码,使用这些算法后,无法通过计算还原出原始密码,而且实现比较简单,因此这种方式被光伏应用,但随着彩虹表技术的兴起,可以建立彩虹表进行查表破解,目前这种方式已经很不安全了。

3、特殊的单向HASH算法,由于单向HASH算法在保护密码方面不再安全,于是有些公司在单向HASH算法基础上进行了加盐,多次HASH等扩展,这些方式可以在一定程度上增加破解难度,对于加了“固定盐”的HASH算法,需要保护“盐”不能泄露,这就跟对称加密一样,一旦“盐”泄露,根据“盐”重新建立彩虹表可以进行破解。

3.3.2 国密方式

国产密码算法(国密算法)是指国家密码局认定的国产商用密码算法,在金融领域目前主要使用公开的SM2,SM3,SM4三类算法,分别是非对称算法、哈希算法和对称算法。随着近年来国家有关机关和监管机构站在国家安全和长远战略的角度提出了推动国密算法应用实施、加强行业安全可控的要求。摆脱对国外技术和产品的过渡依赖,建设行业网络安全环境的迫切需求,以及国家密码管理局于2011年发布了《关于做好公钥密码算法升级工作的通知》,要求“自2011年3月1日起,在建和拟建公钥密码基础设施电子认证系统和密钥管理系统应使用SM2算法,自2011年7月1日起,投入运行并使用公钥密码的信息系统,应使用SM2算法。”

SM2算法:SM2椭圆曲线公钥密码算法是我国自主设计的公钥密码算法,包括SM2-1椭圆曲线数字签名算法,SM2-2椭圆曲线密钥交换协议,SM2-3椭圆曲线公钥加密算法,分别用于实现数字签名密钥协商和数据加密等功能。SM2算法与RSA算法不同的是,SM2算法是基于椭圆曲线上点群离散对数难题,相对于RSA算法,256位的SM2密码强度已经比2048位的RSA密码强度要高

3.3.3 对称加密和非对称加密

这两个在加密和解密过程以及加密解密速度、传输的安全性上都有所不同,具体介绍如下:

1、加密和解密过程不同

对称加密过程和解密过程使用的是同一个密钥,加密过程相当于原文+密钥可以传输密文,同时解密过程用密文和密钥可以推导出原文。但非对称加密是采用了两个密钥,即公钥和私钥,公钥进行原文加密,私钥将密文进行解密得到原文。

2、加密解密速度不同

这点是有体会的,加密解密的速度比较快,适合数据比较长时的使用。非对称加密和解密花费的时间长、速度相对较慢,只适合对少量数据的使用。

3、传输的安全性不同

对称加密的过程中无法确保密钥被安全传递,密文在传输过程中是可能被第三方截获的,如果原文密码也被第三方截获,则传输的密码信息将被第三方破获,安全性较低。

非对称加密算法中私钥是基于不同的算法生成不同的随机数,私钥通过一定的加密算法推导出公钥,但私钥到公钥的推导过程是单向的,也就是说公钥无法反推导出私钥。所以安全性较高。

3.3.4 SM2的应用

基于上述的分析,我采用了SM2国密加密算法,它是非对称加密,安全等级要高。 SM2的原理不用去分析,毕竟是国密级别的,算法复杂度不需要我们去考虑。采用Hutool的SMUtil可以轻松集成SM2算法,我们通过自定义密钥来加密或解密,将生成的公钥和私钥进行base64编码后和加密后的密码三个内容存入到user表中。如果用户登录的时候,我们会根据用户id找到公钥和私钥进行base64解密后还原加密后的密码进行对比。当然中间的加密方式是有很多种的,可以进行多层加密,安全系数也就更高,更复杂。

1、引入jar包

<dependency>
  <groupId>org.bouncycastle</groupId>
  <artifactId>bcprov-jdk15to18</artifactId>
  <version>1.66</version>
</dependency>

(如果出现engin引擎缺少错误,还需要补全jar包)

<dependency>
   <groupId>org.bouncycastle</groupId>
   <artifactId>bcprov-jdk15on</artifactId>
   <version>1.66</version>
</dependency>

2、自定义密钥加密

KeyPair pair = SecureUtil.generateKeyPair("SM2");
//SM2加密
byte[] privateKey = pair.getPrivate().getEncoded();
byte[] publicKey = pair.getPublic().getEncoded();
SM2 sm2 = SmUtil.sm2(privateKey, publicKey);
subUser1.setPrivateKey(Base64.encode(privateKey));
subUser1.setPublicKey(Base64.encode(publicKey));
String encry=sm2.encryptBcd(subEditUserDTO.getPassword(),KeyType.PublicKey);
subUser1.setPassword(encry);

3、获取密文解密

byte[] privateKey=Base64.decode(subUser.getPrivateKey());
byte[] publicKey= Base64.decode(subUser.getPublicKey());
SM2 sm2=SmUtil.sm2(privateKey,publicKey);
String decryptStr=StrUtil.utf8Str(sm2.decryptFromBcd(subUser.getPassword(),KeyType.PrivateKey));
if(decryptStr.equals(loginDTO.getPassword())){
    sameUserNames.add(subUser);
}

四、小结

本篇文章介绍了自己对密码加密的相关理解,密码加密的方式有很多种,自己也并没有一一用过,因此,肯定存在一些疏漏,但是很多东西一通百通,明白了这些内容对其他加密也肯定能快速掌握。