本文主要讲前端传输时的密码加密及后端收到密文后解密的过程,避免明文传输密码,文中内容都比较浅显,加密原理部分需要查阅其它资料。至于前端加密是否有意义,见仁见智。

环境

前端用的vue,纯JS也可以,引入crypto-js库,后端用java

加密方法、MODE、PADDING的选择

这几个参数之间相互有关联,且前后端加密、解密时的参数必须一致。

有的参数前端有A选项,但是后端没有对应的选项,反之也存在后端有B选项,但前端没有的,选择的时候只能选前后端都有的。

具体可以从下面两篇文档中看到:

三个参数前后端共有的选项分别是:

  • 加密方法:AESDESTriple DES(java对应DESede)、RC4
  • mode:CBCCFBCTROFBECB
  • padding:NoPaddingIso10126Pkcs7 (java对应PKCS5Padding)

选择的话,每一个方法、MODE、padding的实现都有点复杂,上面的java参考文档里有写具体的技术性文档链接,这里只说使用,不说原理。

  • 加密方法通常选AES,参考:FIPS 197
  • mode: crypto-js默认是CBC,参考:FIPS 81
  • padding:如果上面选的是AES/CBC,那么padding可以选Iso10126或者Pkcs7
    • NoPadding不能用,因为AES只支持128bit、192bit、256bit几种大小的分块(block),取决于key的位数,除非要加密的密文正好是上面这个块的大小,否则就不能解密,这显然是不合理的。
    • 前端的Pkcs7和java的PKCS5Padding是对应的,参考wiki上的解释,两者基本等价:

    PKCS#5 padding is identical to PKCS#7 padding, except that it has only been defined for block ciphers that use a 64-bit (8-byte) block size. In practice the two can be used interchangeably.

实现工具类

下面的例子采用AES/CBC/Pkcs7这几项,java端是AES/CBC/PKCS5Padding

密钥key和初始向量iv都设置成固定值,前后端一致即可。

key用于AES方法加密,iv用于CBCMODE,iv的大小要跟block一样,在这里就是128bit,跟key一样

前端引入crypto-jsnpm install crypto-js

前端

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
//aesutils.js
import CryptoJs from 'crypto-js'

//把key、iv设置成固定值,前后端的值要一致
let key = CryptoJs.enc.Utf8.parse("onlinemenukey123");
let iv = CryptoJs.enc.Utf8.parse("onlinemenuiv1234");

/**
 *
 * @param word 原始文本
 * @returns {*} Base64的文本
 * @constructor
 */
export function Encrypt(word) {
    let srcs = CryptoJs.enc.Utf8.parse(word);
    var encrypted = CryptoJs.AES.encrypt(srcs, key, {
        iv: iv,
        mode: CryptoJs.mode.CBC,
        padding: CryptoJs.pad.Pkcs7
    });
    return CryptoJs.enc.Base64.stringify(encrypted.ciphertext);
}

后端

涉及到Stringbyte[]转换时,最好指定编码类型,可以避免

 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
package com.lyuww.bgmanager.Utils;

import org.apache.tomcat.util.codec.binary.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;

public class AESUtils {
    /**
     * 解密前端发送的AES加密处理后的密码文本
     * @param text 前端发送的加密后的密文
     * @param key 跟前端设置的key一致,只能是128bit、192bit、256bit
     * @param iv 初始向量,跟前端设置的iv一致
     * @return
     */

    private static final String key="onlinemenukey123";
    private static final String iv="onlinemenuiv1234";


    public static String DecoderAES(String text){
        try{
            byte[] encrypted=new Base64().decode(text);//把base64编码的文本转换成byte数组

            Cipher cipher=Cipher.getInstance("AES/CBC/PKCS5Padding ");
            SecretKeySpec secretKey=new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8),"AES");
            IvParameterSpec ivParameter=new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));

            cipher.init(Cipher.DECRYPT_MODE,secretKey,ivParameter);

            byte[] decrypted=cipher.doFinal(encrypted);
            String oriringalString=new String(decrypted,StandardCharsets.UTF_8);
            return oriringalString;
        }catch (Exception e){
            //解密失败时的操作
            return e.toString();
        }
    }
}

应用

主要步骤:

前端

  • 引入工具类:

    import {Decrypt, Encrypt} from "@/utils/aesutils";

  • axios等方法传输数据,比如密码,在传输前加工具类加密即可

    Encrypt(this.$data.formData.password)

后端

  • 引入工具类

    import xxx.AESUtils;

  • 用工具类解密还原密码,然后进行后续的数据库端密码操作即可

    AESUtils.DecoderAES(user.getPassword()))