spark-sql:自定义UDF函数进行敏感字段加密解密

需求

一些用户数据中包含诸如用户手机号等信息,直接暴露出来的话,是违法的。。。需要对数据进行脱敏,如果单纯的将手机号替换为***号,那么就意味着丢失用户的手机号数据了,因为无法再将***变回手机号。所以需要自定义UDF函数,实现敏感数据的加密解密。

这里实现了两个UDF函数,一个用于加密,一个用于解密。使用Java自带的crypto模块实现AES加密。

在代码中将两个UDF函数中的SecureRandom的Seed写死,这样加密后的数据就是固定的,可以通过解密UDF解析出原始数据。

自定义spark-sql的UDF函数需要实现UDF1、UDF2等方法,后面的数字表示UDF函数的参数个数,但是spark-sql可以使用Hive的函数库,所以直接定义Hive的UDF函数也行。我是实现的Hive的UDF函数

 

高级加密标准(英语:Advanced Encryption Standard,缩写:AES),是一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。

那么为什么原来的DES会被取代呢,,原因就在于其使用56位密钥,比较容易被破解。而AES可以使用128、192、和256位密钥,并且用128位分组加密和解密数据,相对来说安全很多。完善的加密算法在理论上是无法破解的,除非使用穷尽法。使用穷尽法破解密钥长度在128位以上的加密数据是不现实的,仅存在理论上的可能性。统计显示,即使使用目前世界上运算速度最快的计算机,穷尽128位密钥也要花上几十亿年的时间,更不用说去破解采用256位密钥长度的AES算法了。

目前世界上还有组织在研究如何攻破AES这堵坚厚的墙,但是因为破解时间太长,AES得到保障,但是所用的时间不断缩小。随着计算机计算速度的增快,新算法的出现,AES遭到的攻击只会越来越猛烈,不会停止的。

AES现在广泛用于金融财务、在线交易、无线通信、数字存储等领域,经受了最严格的考验,但说不定哪天就会步DES的后尘。)

实现

加密UDF实现代码:

package com.zixuan.hive.udf;

import org.apache.hadoop.hive.ql.exec.UDF;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;

public class EncryptionUDF extends UDF {
    Cipher cipher;
    //编码规则,可以任意指定,但是加密与解密的规则必须一致,否则无法解密
    String encodeRules = "default";
    {
        try {
            //1.构造密钥生成器,指定为AES算法,不区分大小写
            KeyGenerator keygen = KeyGenerator.getInstance("AES");
            //2.根据ecnodeRules规则初始化密钥生成器
            //生成一个128位的随机源,根据传入的字节数组
            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG") ;
            secureRandom.setSeed(encodeRules.getBytes());
            keygen.init(128, secureRandom);
            //3.产生原始对称密钥
            SecretKey original_key = keygen.generateKey();
            //4.获得原始对称密钥的字节数组
            byte[] raw = original_key.getEncoded();
            //5.根据字节数组生成AES密钥
            SecretKey key = new SecretKeySpec(raw, "AES");
            //6.根据指定算法AES自成密码器
            cipher = Cipher.getInstance("AES");
            //7.初始化密码器,第一个参数为加密(Encrypt_mode)或者解密解密(Decrypt_mode)操作,第二个参数为使用的KEY
            cipher.init(Cipher.ENCRYPT_MODE, key);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public String evaluate(String str){
        try {
            //将待加密字符串转换成字节数组
            byte [] byte_encode=str.getBytes("utf-8");
            //进行加密
            byte [] byte_AES=cipher.doFinal(byte_encode);
            return new String(new BASE64Encoder().encode(byte_AES));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
/**
    public static void main(String[] args) {
        EncryptionUDF encryptionUDF = new EncryptionUDF();
        System.out.println(encryptionUDF.evaluate("MCWABKVKBD"));
    }
 */
}

解密UDF实现代码:

package com.zixuan.hive.udf;

import org.apache.hadoop.hive.ql.exec.UDF;
import sun.misc.BASE64Decoder;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;

public class DecryptUDF extends UDF {
    Cipher cipher;
    //编码规则,可以任意指定,但是加密与解密的规则必须一致,否则无法解密
    String encodeRules = "default";
    {
        try {
            //1.构造密钥生成器,指定为AES算法,不区分大小写
            KeyGenerator keygen=KeyGenerator.getInstance("AES");
            //2.根据ecnodeRules规则初始化密钥生成器
            //生成一个128位的随机源,根据传入的字节数组
            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG") ;
            secureRandom.setSeed(encodeRules.getBytes());
            keygen.init(128, secureRandom);
            //3.产生原始对称密钥
            SecretKey original_key=keygen.generateKey();
            //4.获得原始对称密钥的字节数组
            byte [] raw=original_key.getEncoded();
            //5.根据字节数组生成AES密钥
            SecretKey key=new SecretKeySpec(raw, "AES");
            //6.根据指定算法AES自成密码器
            cipher=Cipher.getInstance("AES");
            //7.初始化密码器,第一个参数为加密(Encrypt_mode)或者解密(Decrypt_mode)操作,第二个参数为使用的KEY
            cipher.init(Cipher.DECRYPT_MODE, key);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public String evaluate(String str){
        try {
            //将待解密字符串转换成字节数组
            byte [] byte_content= new BASE64Decoder().decodeBuffer(str);
            //进行解密
            byte [] byte_decode=cipher.doFinal(byte_content);
            return  new String(byte_decode,"utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
/**
    public static void main(String[] args) {
        DecryptUDF decryptUDF = new DecryptUDF();
        System.out.println(decryptUDF.evaluate("dZlVbUSx/TLCeddjpPatBw=="));
    }
 */
}

测试

创建临时方法

添加jar包

add jar /root/udf-1.0-SNAPSHOT.jar;

创建临时方法

--加密
create temporary function encryption as 'com.zixuan.hive.udf.EncryptionUDF';

--解密
create temporary function decryptUDF as 'com.zixuan.hive.udf.DecryptUDF';

进行测试

原数据:

select ChannelMemoID from mdm_channel_memo where ChannelMemoID='MCWABKVKBD';

MCWABKVKBD

加密后:

select encryption(ChannelMemoID) from ods.mdm_channel_memo where ChannelMemoID='MCWABKVKBD';

dZlVbUSx/TLCeddjpPatBw==

对加密后的数据进行解密:

select decryptUDF('dZlVbUSx/TLCeddjpPatBw==');

MCWABKVKBD

遇到的坑

原先在window上测试通过,打包到linux实际测试时,解密总是报错,经排查发现在window上对一条数据加密的结果总是一致的,而到了linux上每次加密得出的密文都不同。

原先的代码是这样的:


            KeyGenerator keygen=KeyGenerator.getInstance("AES");


            //2.根据ecnodeRules规则初始化密钥生成器
            //生成一个128位的随机源,根据传入的字节数组
            keygen.init(128, new SecureRandom(encodeRules.getBytes())); //本行为出错代码
           


            SecretKey original_key=keygen.generateKey();
            
            byte [] raw=original_key.getEncoded();
           
            SecretKey key=new SecretKeySpec(raw, "AES");
              
            Cipher cipher=Cipher.getInstance("AES");
              
            cipher.init(Cipher.ENCRYPT_MODE, key);
          
            byte [] byte_encode=content.getBytes("utf-8");
           
            byte [] byte_AES=cipher.doFinal(byte_encode);

原因:SecureRandom 实现完全随操作系统本身的內部状态,除非调用方在调用 getInstance 方法,然后调用 setSeed 方法;该实现在 windows 上每次生成的 key 都相同,但是在 solaris 或部分 linux 系统上则不同。关于SecureRandom类的详细介绍,见 http://yangzb.iteye.com/blog/325264

也就是说,在window上new SecureRandom(encodeRules.getBytes()))每次得到的结果都是相同的,而在Linux上则是不同的。将出错的那行代码修改为以下代码,问题解决:

            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG") ;
            secureRandom.setSeed(encodeRules.getBytes());
            keygen.init(128, secureRandom);

 

你可能感兴趣的:(spark)