高效数据处理:使用 Tango 库进行压缩和加密





5.00/5 (1投票)
.NET 库,用于内存中的 GZip 和 LZMA 压缩,并结合强大的基于 BlakeB 的流密码引擎
引言
本项目介绍了一个自包含的 .NET 库 (DLL),用于内存数据(作为字节数组和 MemoryStream
)的压缩和加密。当然,处理后的输出可以保存到硬盘,但本项目侧重于内存数据的有效处理方式。
压缩
压缩的显而易见的选择是 .NET 内置的 GZip 功能以及 7zip SDK C# 实现提供的 LZM 算法,后者是迄今为止最有效的压缩函数。
由于项目的目的只是内存转换,因此它不提供带有特定头结构的标准 7Zip 文件。该库调用 GZip 和 LZM 压缩算法,利用并行编程来利用多核 CPU 来解压缩/压缩内存数据。
该库可用的压缩选项来自以下
public enum CompressionAlgo {
GZip_All,
GZip1,
GZip2,
GZip4,
GZip8,
LZMA_All,
LZMA1,
LZMA2,
LZMA4,
LZMA8,
None
}
其中 1、2、4、8 代表压缩过程中要使用的处理器数量,而 Gzip_All
和 LZMA_All
则代表所有可用机器处理器(通常等于 8,因此 LZMA_All
和 LZMA8
含义相同)。
提供的功能的主要且唯一目的是内存操作。因此,保存到硬盘的单线程 LZMA1
压缩结果(字节数组)会形成一个可以被 7zip 应用程序打开的正确 7z 文件,但多线程选项无法生成格式正确的 7z 文件。
加密
对于加密部分,该库引入了基于 BlakeB 压缩函数的快速流密码,该函数由 Samuel Neves、Christian Winnerlein 和 Uli Riehm 实现。
BlakeB
压缩函数是生成器核心,该生成器会创建唯一对应于用户输入的密钥、盐和分布值的输出字节数组(填充)。
生成的填充以以下方式用于加密/解密
- 加密:明文(字节数组 p)异或填充 => 密文(字节数组 x)
- 解密:密文(x)异或填充 => 明文(p)
由于 encrypt
和 decrypt
函数都只是用填充对字节进行异或操作,因此密码引擎包含单个函数 Crypt
,该函数涵盖了这两种情况,为我们提供了流密码实现。
生成器(程序中的 BGen
类)和流密码(程序中的 Streamer
类)都以并行模式工作,利用多核处理器。
例如,如果机器有 8 个处理器,生成器将生成 8 个并行数组并将它们粘合到单个输出(填充)中。不用说,这些片段保证是唯一的且不重复的。
同样,Streamer
会将输入流分成 8 个部分,将它们与 8 个对应的填充部分进行异或操作,并生成汇总结果。
让我们对 BlakeB
功能进行一些修改。
核心压缩函数由如下块组成
v0 = v0 + v4 + m0;
v12 = v12 ^ v0;
v12 = ((v12 >> 32) | (v12 << (32)));
v8 = v8 + v12;
v4 = v4 ^ v8;
v4 = ((v4 >> 24) | (v4 << (40)));
v0 = v0 + v4 + m1;
v12 = v12 ^ v0;
v12 = ((v12 >> 16) | (v12 << (48)));
v8 = v8 + v12;
v4 = v4 ^ v8;
v4 = ((v4 >> 63) | (v4 << (1)));
v1 = v1 + v5 + m2;
v13 = v13 ^ v1;
v13 = ((v13 >> 32) | (v13 << (32)));
v9 = v9 + v13;
v5 = v5 ^ v9;
v5 = ((v5 >> 24) | (v5 << (40)));
v1 = v1 + v5 + m3;
v13 = v13 ^ v1;
v13 = ((v13 >> 16) | (v13 << (48)));
v9 = v9 + v13;
v5 = v5 ^ v9;
v5 = ((v5 >> 63) | (v5 << (1)));
...
带有重复的移位常量 32、24、16 和 63。每个常量在 12 轮处理中出现 96 次,总共出现 384 次。
为了增加攻击者的难度,当前实现最初会基于用户密码(密钥)、盐和分布输入生成字节数组 S[384]。这使得压缩函数具有以下模式
int i = 0;
v0 = v0 + v4 + m0;
v12 = v12 ^ v0;
v12 = RR(v12, S[i++]);
v8 = v8 + v12;
v4 = v4 ^ v8;
v4 = RR(v4, S[i++]);
v0 = v0 + v4 + m1;
v12 = v12 ^ v0;
v12 = RR(v12, S[i++]);
v8 = v8 + v12;
v4 = v4 ^ v8;
v4 = RR(v4, S[i++]);
...
其中 RR
是右旋转函数
private ulong RR(ulong x, byte n) {
return (x >> n) | (x << (-n & 63));
}
因此,压缩函数利用了一组 384 个唯一的移位数字(字节),这些数字取决于用户输入的密钥、盐和分布,从而消除了计算中的任意常量。
生成器中唯一的常量是程序员定义的 ulong
s
private const ulong IV0 = 9111111111111111111;
private const ulong IV1 = 8222222222222222222;
private const ulong IV2 = 7333333333333333333;
private const ulong IV3 = 6444444444444444444;
private const ulong IV4 = 5555555555555555555;
private const ulong IV5 = 4666666666666666666;
private const ulong IV6 = 3777777777777777777;
private const ulong IV7 = 2888888888888888888;
private const ulong IV8 = 1999999999999999999;
private const ulong IV9 = 1111111111111111111;
private const ulong IV10 = 2222222222222222222;
private const ulong IV11 = 3333333333333333333;
private const ulong IV12 = 4444444444444444444;
private const ulong IV13 = 5555555555555555555;
private const ulong IV14 = 6666666666666666666;
private const ulong IV15 = 7777777777777777777;
private const ulong FNL0 = 123456789;
private const ulong FNL1 = 987654321;
在您的实现中设置唯一的常量,并享受可靠而灵活的个人流密码,其中没有任何外部预定义项。
用户提供的盐和分布输入会影响生成器的初始状态,从而增加了结果的分散性。
Tango 库
Tango 库的 Tang
类是一个单一的入口点,提供仅压缩、仅加密或两者兼有的接口。
以下是构造函数使用示例。
压缩
Tang tang1 = new Tang(CompressionAlgo.GZip1); // use GZip on 1 processor
Tang tang2 = new Tang(CompressionAlgo.LZMA8); // use LZMA on 8 processors
加密
Tang tang3 = new Tang(“key”, “salt”, “distr”, 1); // use 1 processor for encryption
Tang tang4 = new Tang(key, salt, distr); // use all processors for encryption.
// key,salt and distr are byte arrays
加密与压缩
Tang tang5 = new Tang(CompressionAlgo.GZip8, “key”,
“salt”, “distr”, 4); // GZip8 compression, 4 processors for encryption
Tang tang6 = new Tang
(CompressionAlgo.LZMA1, “key”, “salt”, “distr”); // LZMA1 compression,
// all processors for encryption
实际应用
Tang tang1 = new Tang(CompressionAlgo.GZip1);
byte[] plain = ...
byte[] compressed = tang1.Zip(plain);
byte[] uncompressed = tang1.Unzip(compressed);
Tang tang 2 = new Tang(“key”, “salt”, “distr”);
byte[] encrypted= tang2.Crypt(plain);
// The reset is needed when using the same tang object for the
// paired encrypt/decrypt and tangle/untangle actions.
tang2.Reset();
byte[] decrypted = tang2.Crypt (encrypted);
Tang tang3 = new Tang(CompressionAlgo.LZMA1, “key”, “salt”, “distr”);
byte[] tangled = tang3.Tangle(plain); // Tangle is Crypt(Zip(plain))
tang3.Reset();
byte[] untangled = tang3.Untangle(tangled); // Untangle is Unzip(Crypt(tangled))
Tango 函数也支持使用 MemoryStream
输入参数而不是字节数组。
不要忘记将 try
/catch
块放在 Unzip
和 Untangle
操作周围。
生成
初始化后,tang 对象可以生成给定长度的字节数组,该数组对于提供的密钥、盐和分布是唯一的
Tang tang = new Tang(“key”, “salt”, “distr”);
byte[] hash = tang.Generate(int length);
测量
PC:i7 2GHz 64 位 8 核
Tangle()
来源:9,653,618 B(fb2 文件 - 允许高压缩)
时间 | 压缩率 | ||
GZip1 | 2,463,446 B | 1259 毫秒 | 25.52% |
GZip8 | 2,472,430 B | 303 毫秒 | 25.61% |
LZMA1 | 1,777,219 B | 30995 毫秒 | 18.41% |
LZMA8 | 1,914,649 B | 6124 毫秒 | 19.83% |
仅压缩
来源:9653618 B
时间 | 压缩率 | ||
GZip1 | 2,463,446 B | 1169 毫秒 | 3.7% 25.52% |
GZip8 | 2,472,430 B | 293 毫秒 | 0.9% 25.61% |
LZMA1 | 1,777,219 B | 31377 毫秒 | 100% 18.41% |
LZMA8 | 1,914,649 B | 6131 毫秒 | 19.5% 19.83% |
如预期,最有效的压缩算法是 LZMA1,但其时间成本最高。
GZip8 比 GZip1 快 4 倍,且在压缩方面几乎没有劣势。
仅加密
来源:9653618 B
时间 | ||
1 核 | 903 毫秒 | 100% |
2 核 | 457 毫秒 | 50.6% |
4 核 | 295 毫秒 | 32.7% |
8 核 | 219 毫秒 | 24.3% |
生成
10,000,000 字节在 250 毫秒内生成
初始化速度
Tang tang = new Tang (CompressionAlgo.LZMA8, "key", "salt", "distr");
Tang
的构造时间约为 0.08 毫秒,与应用的 CompressionAlgo
无关。
轻松掌握 Tango 的纠缠!
历史
- 2019 年 1 月 1 日:初始版本