一些用户数据中包含诸如用户手机号等信息,直接暴露出来的话,是违法的。。。需要对数据进行脱敏,如果单纯的将手机号替换为***号,那么就意味着丢失用户的手机号数据了,因为无法再将***变回手机号。所以需要自定义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的后尘。)
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"));
}
*/
}
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);