Skip to main content

string_analyze/
entropy.rs

1//! 文本与字节序列的熵值 (Entropy) 计算模块。
2//!
3//! 本模块提供基于信息论的多种熵计算方法(基础香农熵、差分熵、二元语法熵),
4//! 并提供了一个融合这三种特征的复合熵评分系统,
5//! 专门用于评估文本的随机程度,以辅助过滤无效匹配并精准定位高随机性的密钥、Token 或混淆代码。
6
7use std::cell::RefCell;
8
9/// 计算给定频率分布的香农熵 (Shannon Entropy) 核心算法。
10///
11/// 内部公式基于 `H = -Σ (p_i * log2(p_i))`,其中 `p_i = count / total`。
12/// 为了优化计算性能,对公式进行了代数变形:
13/// `H = log2(total) - (Σ (count * log2(count)) / total)`
14/// 这样可以避免在循环内部进行除法运算。
15#[inline(always)]
16fn calc_entropy<'a, I>(counts: I, total_grams: f64) -> f64
17where
18    I: Iterator<Item = &'a u32>,
19{
20    let sum_c_log2_c: f64 = counts
21        .filter(|&&c| c > 0)
22        .map(|&c| {
23            let c_f64 = c as f64;
24            c_f64 * c_f64.log2()
25        })
26        .sum();
27    total_grams.log2() - (sum_c_log2_c / total_grams)
28}
29
30/// 计算标准的单字节香农熵 (1-gram Entropy)。
31///
32/// 评估输入序列中各个字节(0-255)出现的均匀程度。
33/// 返回值区间通常为 `[0.0, 8.0]` (因为 2^8 = 256)。
34///
35/// # 参数
36/// - `bytes`: 待计算的字节切片。
37pub fn entropy(bytes: &[u8]) -> f64 {
38    if bytes.is_empty() {
39        return 0.0;
40    }
41
42    let mut counts = [0u32; 256];
43    for &b in bytes {
44        counts[b as usize] += 1;
45    }
46    calc_entropy(counts.iter(), bytes.len() as f64)
47}
48
49/// 计算差分熵 (Delta Entropy)。
50///
51/// 评估相邻字节之间**差值**的分布均匀度。
52///
53/// # 作用
54/// 标准熵无法识破规律递增或递减的序列(如 `"12345678"` 或 `"abcdefgh"`),
55/// 这些序列在单字节熵中表现为高熵,但在差分熵计算中,由于差值总是固定值(如 `1`),
56/// 其差分熵会极其接近 `0.0`,从而有效过滤掉这批伪随机字符串。
57pub fn delta_entropy(bytes: &[u8]) -> f64 {
58    let len = bytes.len();
59
60    if len < 2 {
61        return 0.0;
62    }
63
64    let mut counts = [0u32; 256];
65    for window in bytes.windows(2) {
66        let delta = window[1].wrapping_sub(window[0]);
67        counts[delta as usize] += 1;
68    }
69
70    calc_entropy(counts.iter(), (len - 1) as f64)
71}
72
73/// 计算二元语法熵 (2-gram / Bigram Entropy)。
74///
75/// 评估相邻两个字节组合(0x0000 - 0xFFFF,共 65536 种可能)的分布情况。
76/// 理论最大值为 `16.0` (2^16)。用于评估序列中字符组合的复杂度和随机性。
77///
78/// # 性能优化
79/// 因为需要 `65536` 大小的数组,频繁的堆内存分配 (`Vec::new`) 会严重拖慢性能。
80/// 此处使用了 `thread_local!` 缓存复用的方式,使得并发扫描时零内存分配。
81pub fn gram_entropy2(bytes: &[u8]) -> f64 {
82    thread_local! {
83        // 使用 (counts, visited_indices) 的组合来实现 O(K) 的快速清零 (K 为独立 2-gram 数量)
84        static GRAM_BUFFER: RefCell<(Vec<u32>, Vec<u16>)> = RefCell::new((vec![0; 65536], Vec::with_capacity(4096)));
85    }
86    let len = bytes.len();
87
88    if len < 2 {
89        return 0.0;
90    }
91
92    GRAM_BUFFER.with(|buf| {
93        let (counts, visited) = &mut *buf.borrow_mut();
94        let total_grams = (len - 1) as f64;
95
96        for w in bytes.windows(2) {
97            let idx = ((w[0] as usize) << 8) | (w[1] as usize);
98            if counts[idx] == 0 {
99                visited.push(idx as u16);
100            }
101            counts[idx] += 1;
102        }
103
104        let mut sum_c_log2_c = 0.0;
105        for &idx in visited.iter() {
106            let u_idx = idx as usize;
107            let c = counts[u_idx];
108            counts[u_idx] = 0; // 顺手清理数组,为下一次调用做准备
109
110            let c_f64 = c as f64;
111            sum_c_log2_c += c_f64 * c_f64.log2();
112        }
113
114        visited.clear();
115
116        total_grams.log2() - (sum_c_log2_c / total_grams)
117    })
118}
119
120/// 计算综合复合熵 (Composite Entropy) 评分。
121///
122/// 融合了标准熵、差分熵和二元语法熵,输出一个归一化到 `[0.0, 10.0]` 的综合评分。
123/// 分数越高,表示序列越趋近于真正的强随机(如高质量哈希、密码、加密密钥)。
124///
125/// # 算法逻辑
126/// 1. 提取三种熵并归一化到 `[0.0, 1.0]` 的区间。
127/// 2. 对 2-gram 熵 (`n3`) 应用一个平滑多项式 `y = -4x^5 + 15x^4 - 20x^3 + 10x^2`,
128///    这是一种基于 Smoothstep 思想的映射,旨在压制处于中低水平的 2-gram 影响。
129/// 3. 采用**带有负指数 (`P = -1.6`) 的加权幂平均法** (Weighted Power Mean) 融合三者。
130///    负指数的特性是具有**“木桶效应 / 惩罚低分”**:只要三种熵中有任意一个明显偏低(例如具有规律性递增),
131///    整体的分数就会被大幅拉低。
132pub fn composite_entropy(bytes: &[u8]) -> f64 {
133    let len = bytes.len();
134    if len < 4 {
135        return 0.0;
136    }
137
138    let e1 = entropy(bytes);
139    let e2 = delta_entropy(bytes);
140    let e3 = gram_entropy2(bytes);
141
142    const EPS: f64 = 1e-6;
143    let n1 = (e1 / 8.0).clamp(EPS, 1.0);
144    let n2 = (e2 / 8.0).clamp(EPS, 1.0);
145
146    let n3_raw = (e3 / 16.0).clamp(EPS, 1.0);
147
148    // 对 2-gram 应用多项式非线性映射 y = -4x^5 + 15x^4 - 20x^3 + 10x^2
149    let n3 = {
150        let x = n3_raw;
151        (x * x * (10.0 + x * (-20.0 + x * (15.0 - 4.0 * x)))).clamp(EPS, 1.0)
152    };
153
154    // 权重配置
155    const W1: f64 = 0.6; // 基础熵占主导
156    const W2: f64 = 0.3; // 差分熵
157    const W3: f64 = 0.1; // 2-gram 熵
158    const P: f64 = -1.6; // 幂指数,负值强调惩罚偏低的特征值
159
160    // 计算加权幂平均
161    let mean_pow = W1 * n1.powf(P) + W2 * n2.powf(P) + W3 * n3.powf(P);
162    let combined = mean_pow.powf(1.0 / P);
163
164    (combined * 10.0).clamp(0.0, 10.0)
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170    use rand::{RngExt, random, rng};
171
172    #[test]
173    fn test_entropy() {
174        let input = &[
175            "unittests src/lib.rs (target/debug/deps/string_analyze-551ebe6d3e6cc1ac)",
176            "0123456789ABCDEF0123456789abcdef0123456789ABCDEF0123456789abcdef",
177            "96ef6443c6effd748c7202c04f0577f92761b821db19fdfea2a5c2364b439209",
178            "horizontal-scroll-mode",
179            "112233445566778899aabbccddeeffxxyyzz112233445566778899aabbccddeeffxxyyzz",
180            "aGVsbG8=",
181            "MDEyMzQ1Njc4OUFNREV5TXpRMU5qYzRPVUZDUTBSRlJqQXhNak09QkNERUYwMTIz",
182            "mTyqm7wjODkrNLcWl0eqO8K8gc1BPk1GNLgUpI==",
183            "12~3",
184            "1[82~3",
185            "Bitcoin/IPFS",
186            " ",
187            "~呜嗷~~嗷嗷呜~嗷呜喵呜喵呜喵~嗷~~呜~喵喵呜嗷喵嗷呜嗷呜嗷~喵~呜呜呜喵嗷呜喵喵喵呜喵喵",
188            "~呜嗷嗷嗷嗷呜呜呜呜嗷呜嗷呜呜喵呜喵嗷喵嗷喵呜~呜~嗷~嗷~呜嗷呜嗷嗷嗷嗷嗷呜呜呜呜嗷呜嗷呜呜喵呜喵嗷喵嗷喵呜~呜~嗷~嗷~呜嗷呜嗷嗷嗷嗷嗷呜呜呜呜嗷呜嗷呜呜喵呜喵嗷喵嗷喵呜~呜~嗷~嗷~呜嗷呜嗷嗷嗷嗷嗷呜呜呜呜嗷呜嗷呜呜喵呜喵嗷喵嗷喵呜~呜~嗷~嗷~呜嗷呜嗷嗷嗷嗷嗷呜呜呜呜嗷呜嗷呜呜喵呜喵嗷喵嗷喵呜~呜~嗷~嗷~呜嗷呜嗷嗷嗷嗷嗷呜呜呜呜嗷呜嗷呜呜喵呜喵嗷喵嗷喵呜~呜~嗷~嗷~呜嗷呜嗷嗷嗷嗷嗷呜呜呜呜嗷呜嗷呜呜喵呜喵嗷喵",
189            "~~~~~~~~~~~!!!!!!!!!!!!!!###############",
190            "ccVoejdMVQnfrzpgIunpQchW6WXcCnuX7leuRhzY3Ripc26KwOtCqxBRcjmHbN9PiaesCgHTi9iF",
191            r#"M_2cL`Eqq:;|z_hiRTvhW@RLyq&d+UPXf6$ZarHI+9AiM[ZcQ"8egZ4m3Ur>J*AJE>kitQ@5Exo!C3$z.c[E#{*Rq5D"#,
192            "豫章故郡,洪都新府。星分翼軫,地接衡廬。襟三江而帶五湖,控蠻荊而引甌越。物華天寶,龍光射牛斗之墟;人傑地靈,徐孺下陳蕃之榻。雄州霧列,俊彩星馳。臺隍枕夷夏之交,賓主盡東南之美。都督閻公之雅望,棨戟遙臨;宇文新州之懿範,襜帷暫駐。十旬休假,勝友如雲。千里逢迎,高朋滿座。騰蛟起鳳,孟學士之詞宗;紫電青霜,王將軍之武庫。家君作宰,路出名區。童子何知?躬逢勝餞。",
193        ];
194
195        for s in input {
196            println!(
197                "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
198            );
199            println!(
200                "[entropy          ] [{:<5.3}]\t{s:.128}",
201                entropy(s.as_bytes())
202            );
203            println!(
204                "[delta_entropy    ] [{:<5.3}]\t{s:.128}",
205                delta_entropy(s.as_bytes())
206            );
207            println!(
208                "[gram_entropy2    ] [{:<5.3}]\t{s:.128}",
209                gram_entropy2(s.as_bytes())
210            );
211            println!(
212                "[composite_entropy] [{:<5.3}]\t{s:.128}",
213                composite_entropy(s.as_bytes())
214            );
215        }
216        let input = &mut vec![
217            b"\xd6\x23\x15\x9e\xbd\xe2\x90\xf9\xaa\xa0\x2e\xa0\x80\x9a\xa6\xf3\xcd\x31\xaa\x0d\x04\x6f\x51\x9c\xf1\x34\xcd\xef\x41\x29\xa4\x28\x44\x0c\xf7\x2d\xbb\x3d\x69\xf2\x03\xff\x9d\x54\x95\x25\x2d\x83\xd2\x80\x8d\x44\xef\xef\xf5\x6a\xf8\xc3\x61\x99\x9c\xe9\x2c\xec\x23\x3b\xea\xf3\x43\x1f\x57\xbd\x45\xce\x0e\x96\x4c\xb7\xbf\x28\x08\x16\x77\x53\x83\x59\x16\xd3\xac".as_slice()
218        ];
219        let x256 = random::<[u8; 256]>();
220        input.push(x256.as_slice());
221        let x512 = random::<[u8; 512]>();
222        input.push(x512.as_slice());
223
224        let x1024 = random::<[u8; 1024]>();
225        input.push(x1024.as_slice());
226
227        let mut x4096 = vec![0u8; 4096];
228        rng().fill(&mut x4096);
229        input.push(x4096.as_slice());
230
231        for b in input {
232            println!(
233                "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
234            );
235            println!(
236                "[entropy          ] [{:<5.3}]\t{:.128}",
237                entropy(b),
238                format!("{b:?}")
239            );
240            println!(
241                "[delta_entropy    ] [{:<5.3}]\t{:.128}",
242                delta_entropy(b),
243                format!("{b:?}")
244            );
245            println!(
246                "[gram_entropy2    ] [{:<5.3}]\t{:.128}",
247                gram_entropy2(b),
248                format!("{b:?}")
249            );
250            println!(
251                "[composite_entropy] [{:<5.3}]\t{:.128}",
252                composite_entropy(b),
253                format!("{b:?}")
254            );
255        }
256    }
257}