针对特定属性下的数据加密传输,例如手机号,身份证,地址信息等。

一. 为什么要数据加密

如果我们将用户的手机号码,身份号码以及地址等信息直接进行传递,很容易在传递数据包的时候被截获明文。

较好的结果就是将用户个人信息进行贩卖,较差的结果就是利用这些信息来进行社会工程学的诈骗,例如伪装成银行客服询问相关业务情况,由于能够准确的说出你的身份证以及地址信息,你就会放松警惕。

所以我们最好能够将这些敏感信息进行加密传输或者敏感信息脱敏。

二. 加密措施

2.1 敏感信息脱敏

可以参考正则表达式或者其他方式进行脱敏,目的就是为了隐藏关键的字母或者数字,不能够被其他人得知完整数据。

  • 身份证脱敏

    /**
    * 身份证脱敏
    * @param idNo 原始身份证号信息
    * @return 脱敏后信息
    */
    public String maskIdNo(String idNo){
        // 结果 202210******141024
        return idNo.replaceAll("(\\w{6})\\w*(\\w{6})", "$1******$2");
    }
    
  • 手机号脱敏

    /**
    * 手机号脱敏
    * @param phone 原始手机号信息
    * @return 脱敏后信息
    */
    public String maskIdNo(String phone){
        // 结果 136****1024
        return phone.replaceAll("(\\w{3})\\w*(\\w{4})", "$1****$2");
    }
    

脱敏信息可以有效帮助较少安全隐患,但是也有明显的缺点,即数据接收端也无法准确得知具体信息,比如有一个前端界面确实需要能够回显用户身份证信息,这个时候该种方法就没办法达到要求了。所以我们在这里需要使用对称加密,前后端约定同一种加密手段,并通过约定的手段进行加解密。

2.2 可逆对称加密

这里,我们选择使用AES进行举例,首先我们需要与前端约定好一个解密钥匙,例如使用以下的约定密钥前缀+日月年格式:

/**
* 加密前缀 (与前端约定)
*/
private static String secretKeyPrefix = "abcdefg";

/**
* AES加密
* @param content 明文
* @return 密文
* @throws Exception
*/
public static String aesEncrypt(String content) throws Exception {
	// 构建密钥解密串
    String encryptKey = getDayDecryptKey();
    if (StringUtils.isEmpty(content) || StringUtils.isEmpty(encryptKey)) {
        return null;
    }

    Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
    // 初始化密钥
    cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES"));
	// 使用构造的密钥进行最终加密
    byte[] encryptStr = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
    return Base64.getEncoder().encodeToString(encryptStr);
}

private static String getDayDecryptKey() {
	// 使用约定的密钥前缀以及日月年格式,例如:abcdefg25102022
    String dayDecryptKey = secretKeyPrefix + DateUtil.formatDate(LocalDateTime.now(), DateUtil.Format.DDMMYYYY.value());
    return dayDecryptKey;
}
/**
* 加密前缀 (与前端约定)
*/
private static String secretKeyPrefix = "abcdefg";

/**
* AES解密
* @param encryptStr 密文
* @return 明文
* @throws Exception
*/
public static String aesDecrypt(String encryptStr) {
    try {
        String dayDecryptKey = getDayDecryptKey();
        byte[] encryptByte = Base64.getDecoder().decode(encryptStr);
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(dayDecryptKey.getBytes(), "AES"));
        byte[] decryptBytes = cipher.doFinal(encryptByte);
        return new String(decryptBytes);
    } catch (Exception e) {
        log.error("EncryptUtil aesDecrypt[dayDecryptKey] error.", e.getMessage());
    }
}


private static String getDayDecryptKey() {
	// 使用约定的密钥前缀以及日月年格式,例如:abcdefg25102022
    String dayDecryptKey = secretKeyPrefix + DateUtil.formatDate(LocalDateTime.now(), DateUtil.Format.DDMMYYYY.value());
    return dayDecryptKey;
}

这里我们模拟一个真实的数据加密场景,前端使用加密内容进行传输,后端进行解密后解析处理,定义一个待解密对象包含一个待解密字符串属性:

@Data
public class DecryptContent implements Serializable {

    /**
     * 待解密内容
     */
    private String decryptContent;
}
/**
* 加密信息通信
* @param decryptContent 加密字符串
* @return
*/
@PostMapping("/test")
public void test(@RequestBody DecryptContent decryptContent) {
    if (decryptContent == null) {
        return;
    }else{
        String decryptMsg = validateDecryptContent(decryptContent.getDecryptContent());
        log.info("解密成功,信息={}", decryptMsg);
    }
    
}

/**
* 加密信息校验
* @param decryptContent 加密字符串
* @return
*/
public static String validateDecryptContent(String decryptContent) {
    if (StringUtils.isBlank(decryptContent)) {
        log.error("DecryptContentValidate validateDecryptContent param[decryptContent] is blank.");
        return null;
    }
    String decryptStr = EncryptUtil.aesDecrypt(decryptContent);
    return decryptStr;
}

2.3 mybatis-plus注解加密方法

如果我们使用了mybatis-plus,那么数据脱敏加解密就更方便了。

  1. 第一步,我们在DO类上的@TableName注解上除了标价value属性的表名之外,还需要加上autoResultMap的属性,并设置为true
  2. 第二步,我们在需要标记加密的字段上加上该注解@TableField(typeHandler = EncryptHandler.class),注解后面的Handler即表示需要加密数据的处理方式,观察源码可以发现这个类必须是实现了TypeHandler接口的实现类。如下示例,这里我们已经实现了一个EncryptHandler,继承了BaseTypeHandler,而BaseTypeHandler即是一个继承了TypeReference并实现了TypeHandler的抽象类:
public class EncryptHandler extends BaseTypeHandler {
    public EncryptHandler() {
    }

    public void setNonNullParameter(PreparedStatement preparedStatement, int i, Object o, JdbcType jdbcType) throws SQLException {
        preparedStatement.setString(i, EncryptProxy.encrypt(o.toString()));
    }

    public Object getNullableResult(ResultSet resultSet, String s) throws SQLException {
        String string = resultSet.getString(s);
        return EncryptProxy.decrypt(string);
    }

    public Object getNullableResult(ResultSet resultSet, int i) throws SQLException {
        String string = resultSet.getString(i);
        return EncryptProxy.decrypt(string);
    }

    public Object getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        String string = callableStatement.getString(i);
        return EncryptProxy.decrypt(string);
    }
}

实体类示例:

@TableName(value = "table",autoResultMap = true)
public class Table {

	/** 系统密码 */
    @TableField(typeHandler = EncryptHandler.class)
    private String password;
	
}

更多推荐