大数据传输中的二进制加密方案
大数据传输中的二进制加密方案
在大数据时代,数据安全已成为数据治理的重要组成部分。本文将探讨大数据传输中的二进制加密方案,包括数据加密的基本概念、常见加密算法以及实际应用场景。
数据加密基础
数据加密是通过某种算法将明文数据转换为密文数据的一种技术。加密后的密文即使被非法获取,也无法直接读取其中的信息,只有通过正确的解密密钥才能还原成原始的明文数据。
数据加密的过程通常包括两个主要环节:加密和解密。加密过程使用加密算法将原始的明文数据通过特定的加密密钥转化为密文。常见的加密算法包括对称加密和非对称加密。
解密是将密文数据恢复成明文的过程,解密过程使用解密算法和密钥。如果加密算法是对称的,则加密和解密使用相同的密钥;如果是非对称的,则使用一对密钥,其中一个密钥用于加密,另一个密钥用于解密。
对称加密算法
对称加密是最为常见的数据加密方式,指加密和解密使用相同的密钥。由于加密和解密使用相同的密钥,因此对称加密速度较快,适合大规模数据的加密。但其缺点也很明显,即密钥管理问题。一旦密钥被泄露,所有加密的数据都可能受到威胁。
常见的对称加密算法:
- AES(Advanced Encryption Standard):高级加密标准,是目前最广泛使用的对称加密算法,广泛应用于SSL/TLS等数据传输协议中。
- DES(Data Encryption Standard):数据加密标准,虽然曾经是主流的加密算法,但由于其密钥长度较短(56位),已经逐渐被淘汰。
- 3DES(Triple DES):三重数据加密标准,在DES的基础上进行三次加密,安全性更强,但效率较低。
非对称加密算法
非对称加密算法使用一对密钥,其中一个用于加密(公钥),另一个用于解密(私钥)。虽然公钥可以公开,但只有私钥持有者才能解密加密数据。因此,非对称加密在解决密钥分发和密钥管理问题上具有天然优势。
常见的非对称加密算法:
- RSA(Rivest-Shamir-Adleman):一种广泛使用的非对称加密算法,通常用于电子邮件加密、数字签名等场景。其安全性基于大整数分解的难度。
- ECC(Elliptic Curve Cryptography):椭圆曲线密码学,提供与RSA相同的安全性,但使用较短的密钥长度,效率更高。
哈希算法
哈希算法是一种单向加密算法,用于生成固定长度的输出(哈希值),即使输入数据的长度发生变化。哈希算法通常用于验证数据完整性,广泛应用于数字签名和密码存储中。哈希算法不涉及解密过程,因此它不适用于保护数据机密性,但可以有效防止数据篡改。
常见的哈希算法:
- MD5(Message Digest Algorithm 5):广泛用于数据完整性校验,但由于其碰撞问题,已经不再推荐用于安全应用。
- SHA(Secure Hash Algorithm):SHA-256和SHA-512是目前应用最广泛的哈希算法,主要用于数字签名、区块链等领域。
二进制加密实现
在加解密的过程中,数据是以二进制的形式存在的。就拿对称加密AES来说,将要加密的字符串转换成二进制的字节数组,使用密钥加密之后再以字节数组的形式返回。
public static String encrypt(String data, String key) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encryptedData = cipher.doFinal(data.getBytes());
return Base64.getEncoder().encodeToString(encryptedData);
}
但是对于加密后的二进制数据,用户是无法直接看到的,所以我们通过一些转换将二进制转换成可见字符,例如常见的base64、二进制转换成10进制/16进制等,这样我们就可以看到加密数据。
在解密过程中,是将加密后的二进制字节数组在解密成明文数据的字节数组。
public static String decrypt(String encryptedData, String key) throws Exception {
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decodedData = Base64.getDecoder().decode(encryptedData);
byte[] decryptedData = cipher.doFinal(decodedData);
return new String(decryptedData);
}
然后我们通过构造String还原明文数据即可。我们对上面AES加解密的方法进行测试:
public static void main(String[] args) throws Exception {
String key = "1234567890123456";
String data = "Hello, aqi!";
String encryptedData = encrypt(data, key);
System.out.println("Encrypted: " + encryptedData);
String decryptedData = decrypt(encryptedData, key);
System.out.println("Decrypted: " + decryptedData);
}
运行输出:
为什么需要Base64编码
那么,为什么加密后的二进制数据不用new String的形式,而是要转换成base64、16进制的形式呢?
因为String是将1个字节转换成ASCII码,而ASCII的最大值是127(第一位为符号位),也就是0111 1111,但是在加密的过程中,如果这个字节最高位是1,就会被转换为负数。因为在计算机中负数是以补码的形式存储的(取反加1)。
例如1111 1111,取反为加1为0000 0001,所以为-1。代码如下:
byte b = (byte)255;
System.out.println(b);
System.out.println(b & 0XFF);
运行结果:
而最后的b & 0XFF为什么可以输出255??0XFF是十六进制,转换成二进制是1111 1111,不应该也是-1吗?
(byte)255 变成 -1 是因为Java 的 byte 类型是有符号的,而0xFF 是一个十六进制数,它本身表示的是 255,当作为无符号整数处理,直接解释为 255。所以b & 0XFF的规则是这样的:
b的-1转换成int类型,其会被表示为11111111 11111111 11111111 11111111,而在 & 过程中,OxFF要扩展成32位,因为是无符号数,所以使用零扩展,即00000000 00000000 00000000 11111111。根据 & 的规则,最后 b & 0XFF 结果为00000000 00000000 00000000 11111111,即255。
上面这个就是个扩展只是,加密后的二进制数据不用new String归根结底的原因就是无法保证字节在127之内,结果就是乱码,如图:
数据传输安全方案
在数据传输过程中,数据加密可以确保数据的安全性,避免敏感信息被第三方窃取、篡改或伪造。例如HTTPS、VPN等。在大数据中的传输和存储过程中,可以通过上面提到的加密算法进行加解密。但是也需要考虑很多的问题:
- 密钥管理:加密技术的核心在于密钥的安全性,密钥的丢失、泄露或管理不当都会导致加密数据的安全风险。
- 性能问题:加密和解密操作会增加数据传输的时间和计算资源消耗,尤其在大量数据需要加密传输时,可能导致性能下降。
密钥管理更多的体现在离线数据治理的场景中,而在数据传输的应用场景中,对性能(实时性)的要求会更高。所以在大数据中,如何在数据加密和实时性之间获得平衡也成为一个问题。所以现在更偏向与在传输中只对双方身份认证,而不对数据本加密,数据安全就放在数据治理的工作中。
无需加密算法的数据传输安全方案
那么不用加密算法可以实现数据传输的安全性吗??抛开内网的环境下,谈谈我在数据传输中遇到的一个实时业务场景,白天大概16Gbytes/s +的流量。就是就是将字段、类型与字节数对应。听我细讲:
假如一条数据有3个字段,然后网络传输的过程中使用的是byte,最简单的方式就是使用getBytes将这条数据转换成字节,假如被人给获取了这条数据,使用new String就简单的破解了。
- 规范指定
那么,如果数据传输方和接收方制定一个规范,将每个字段字段与类型、字节数对应起来,例如:
其中string类型的处理比较复杂,因为不同长度的字符串字节数不一样,所以这里标了一个“变长”,通常情况下,可以在name前面增加一个name_length来name长度,也可以使用TLV编码格式。
像age字段注意我们使用的是unsigned无符号类型,这样就能用2个字节全部16个bit来表示数据内容,最后timestamp是long类型,除此之外还有4、5个字节的unsigned int以及double等类型。
- 传输编码
这里不讨论name的变长问题,这里就默认为3。先看传输方根据规范,如何将三个字段转换为字节数组。
上图样例代码只是其中的实现方式之一,旨在将每个字段按照类型处理成byte后,以网络序放入到了result字节数组中。
不难发现,这一条数据一共用了13个字节,如果使用getBytes会占用多少个字节呢??
18个字节,其中还不包含两个分隔符。所以这种传输方式也能减少带宽占用。
- 接收解码
接收方收到数据后,根据规范解析每个字段。
其中,name使用new String,age是将2个字节转换成short,timestamp是将8个字节转换成Long。我们可以查看解码使用的封装方法:
public static int byteToUnsignedShort(byte[] b, int off) {
short s = 0;
short s0 = (short) (b[(1 + off)] & 0xFF);
short s1 = (short) (b[off] & 0xFF);
s1 = (short) (s1 << 8);
s = (short) (s0 | s1);
return s & 0xFFFF;
}
public static long byteToLong(byte[] b, int off) {
long s = 0L;
long s0 = b[(7 + off)] & 0xFF;
long s1 = b[(6 + off)] & 0xFF;
long s2 = b[(5 + off)] & 0xFF;
long s3 = b[(4 + off)] & 0xFF;
long s4 = b[(3 + off)] & 0xFF;
long s5 = b[(2 + off)] & 0xFF;
long s6 = b[(1 + off)] & 0xFF;
long s7 = b[off] & 0xFF;
s1 <<= 8;
s2 <<= 16;
s3 <<= 24;
s4 <<= 32;
s5 <<= 40;
s6 <<= 48;
s7 <<= 56;
s = s0 | s1 | s2 | s3 | s4 | s5 | s6 | s7;
return s;
}
如果,在传输过程中,字节数组result被获取,没有规范的话使用new String是无法正确解析数据的。
如图,部分数据就会乱码,原因上面也讲过了。
- 编码格式扩展
对于上面这种字段解析,是属于普通字段的解析,按顺序排列且所有字节都用来表示数据本身,我们称之为V(value)。除此之外,还有LV和TLV格式,T表示Tag,每个字段都有一个编号且不是顺序的,L表示Length,表示字段长度的枚举值,V才是数据本身。
而且V、LV、TLV的缺省值回填也不一样,具体的还是要看规范制定,合理使用可以在一定程度上节约带宽。
总结
本篇文章刚开始是想讲常见的加密算法,然后就想起来了我遇到的这个应用场景,最后就以文字的形式分享了出来。加密方案并不是一成不变,适合自己的实际业务才是最重要的。