【问题标题】:Computing Shannon entropy of a HTTP header using Python. How to do it?使用 Python 计算 HTTP 标头的香农熵。怎么做?
【发布时间】:2026-02-08 14:05:01
【问题描述】:

香农熵是:

\r\n\r\n is the end of a HTPP header:

不完整的 HTTP 标头:

我有一个 PCAP 格式 (dump.pcap) 的网络转储,我正在尝试使用 Python 在标头中计算带有 \r\n\r\n 和没有 \r\n\r\n 的 HTTP 协议中的数据包数量的熵,并比较它们。我使用以下方法读取数据包:

import pyshark

pkts = pyshark.FileCapture('dump.pcap')

我认为香农公式中的Ti 是我的转储文件的数据。

dump.pcap:https://uploadfiles.io/y5c7k

我已经计算了 IP 号码的熵:

import numpy as np
import collections

sample_ips = [
    "131.084.001.031",
    "131.084.001.031",
    "131.284.001.031",
    "131.284.001.031",
    "131.284.001.000",
]

C = collections.Counter(sample_ips)
counts = np.array(list(C.values()),dtype=float)
#counts  = np.array(C.values(),dtype=float)
prob    = counts/counts.sum()
shannon_entropy = (-prob*np.log2(prob)).sum()
print (shannon_entropy)

有什么想法吗?是否可以计算带有\r\n\r\n而头部没有\r\n\r\n的HTTP协议中数据包数量的熵,或者这是一个无稽之谈?

转储的几行:

 30 2017/246 11:20:00.304515    192.168.1.18    192.168.1.216   HTTP    339 GET / HTTP/1.1 


    GET / HTTP/1.1
    Host: 192.168.1.216
    accept-language: en-US,en;q=0.5
    accept-encoding: gzip, deflate
    accept: */*
    user-agent: Mozilla/5.0 (X11; Linux i686; rv:45.0) Gecko/20100101 Firefox/45.0
    Connection: keep-alive
    content-type: application/x-www-form-urlencoded; charset=UTF-8

【问题讨论】:

  • 这是一个无稽之谈。正如您在 IP 地址计算中所展示的,香农的熵是根据 字符串 计算的,而不是数据包的数量。你在寻找字符串“\r\n\r\n”的熵吗?这是用this handy online calculator 计算的
  • @ScouserInTrousers:我认为我可以计算带有“\r\n\r\n”和没有“\r\n\r\n”的数据包的频率。这是没有意义的吗?感谢您的帮助!
  • 当然,你可以计算频率没问题。我对pyshark 不熟悉,但如果你能捕获HTTP 请求,你可以只做一个正则表达式并有两个计数器:count_withcount_without。如果字符串中有“\r\n\r\n”,则递增count_with;否则递增count_without
  • @ScouserInTrousers:好主意,但问题是怎么做?我不会写剧本!你能帮帮我吗?
  • 你不太明白你在做什么。公式中的Ti是看到某个字符的概率。而且您无法计算数据包数量的熵。但是您可以计算每个特定页面的熵。首先,您必须找到每个符号的概率。为此,您需要估计这些概率。这意味着下载尽可能多的页面(几千就足够了,以很好地表示统计数据),并在所有数据上运行您的代码。

标签: python python-2.7 python-3.x entropy


【解决方案1】:

虽然我不明白你为什么要这样做,但我不同意其他人认为这是荒谬的。

例如,您可以拿起一枚硬币并将其翻转并测量其熵。假设你翻转 1000 次,得到 500 个正面和 500 个反面。这是每个结果的 0.5 频率,或者统计学家正式称之为“事件”的频率。

现在,由于两个 Ti 相等 (0.5),并且 0.5 的对数底数 2 是 -1,硬币的熵是 -2 *(0.5 * -1) = -1(负 2 是前面的减号并识别添加两个相同的东西与乘以 2 相同。

如果硬币正面出现的次数是反面的 127 倍怎么办?尾部现在以 1/128 的概率出现,其对数底数 2 为 -7。因此,将 -7 乘以 1/128(大约)得出大约 1/32 的贡献。正面的概率非常接近 1。但是 1 的对数底数为 2(或任何底数)为零。因此,该术语大致为零。因此,那枚硬币的熵大约是 -1/32,记住减号(如果我在脑海中这样做的话)。

所以你的诀窍是收集大量随机消息,并将它们计数到两个桶中。然后按照上面的方法进行计算。

如果您询问如何进行计数,并且计算机上有这些,您可以使用 grep(unix 上的正则表达式工具)之类的工具或其他系统上的类似实用程序。它会为您排序。

【讨论】:

  • 谢谢!我的目标是在拒绝服务攻击期间比较熵。我想实现两个计数器的想法:count_with 和 count_without。如果字符串中有“\r\n\r\n”,则增加count_with;否则增加count_without。我不知道如何提取“\r\n\r\n”进行计数!
  • 我认为你的想法比其他人的想法更有价值。不知道它是否会起作用,但熵是对实际“随机性”的一个非常有力的测试。
【解决方案2】:

提醒:熵的公式是

H(S)=-sum[ P(Xi) * log2 P(Xi) ],其中

S是你要计算它的熵的内容,

Xi 是文档中的i-th 字符,并且

P(Xi) 是在内容中看到字符Xi 的概率。

这里的第一个问题是正确估计P(Xi)。要正确执行此操作,您需要下载尽可能多的多样化页面。至少100个,几千个会更好。这很重要,因为您需要有一个能够很好地代表您的域的真实页面。

现在,您必须从数据包中重建 HTTP 层。这在现实生活中并不是一件容易的事,因为有些页面会被拆分成多个数据包,它们的到达顺序可能与您预期的不一样,有些数据包可能会丢失并重新传输。我建议您阅读this 博客,以掌握主题。

另外,我建议您分别计算 HTTP 请求的标头和正文的熵。这是因为我希望标题和正文中的字符分布不同。

现在,当您可以访问所需内容时,您只需计算每个字符的频率即可。类似于以下内容(doc_collection 可能是一个列表,其中包含您从 PCAP 中提取的所有 HTTP 标头的内容。):

def estimate_probabilities(doc_collection):
    freq = Counter()
    for doc in doc_collection:
        freq.update(Counter(doc))
    total = 1.0*sum(freq.values())
    P = { k : freq[k]/total for k in freq.keys() }
    return P

既然你有了字符的概率,计算熵就很简单了:

import numpy as np
def entropy(s, P):
    epsilon = 1e-8
    sum = 0
    for k,v in Counter(s).iteritems():
        sum -= v*P[k]*np.log2(P[k] + epsilon) 
    return sum

如果您愿意,您甚至可以使用map 加快速度:

import numpy as np
def entropy(s, P):
    epsilon = 1e-8
    return -sum(map(lambda a: a[1] * P[a[0]] * np.log2(P[a[0]] + epsilon), Counter(s).items()))

如果符号的概率接近于零,则需要epsilon 以防止对数变为负无穷大。

现在,如果您想计算不包括某些字符(在您的情况下为“\r”和“\n”)的熵,只需将它们的概率归零,例如P['\n'] = 0 这将从计数中删除所有这些字符。

--更新回答评论:

如果你想根据子串的存在对熵求和,你的程序将如下所示:

....
P = estimate_probabilities(all_HTTP_headers_list)

....
count_with, count_without = 0, 0
H = entropy(s, P)
if '\r\n\r\n' in s:
    count_with += H
else:
    count_without += H

all_HTTP_headers_list 是您拥有的所有标头的串联,s 是特定标头。

-- update2:如何读取 HTTP 标头

pyshark 不是处理数据包的最佳解决方案,因为它会丢弃有效负载,但只获取标头即可。

pkts = pyshark.FileCapture('dump.pcap')

headers = []
for pk in pkts:
    if pk.highest_layer == 'HTTP':
        raw = pk.tcp.payload.split(':')
        headers.append( ''.join([ chr(int(ch, 16)) for ch in raw ]) )

在这里,您检查您的数据包是否确实具有 HTTP 层,获取其有效负载(从 TCP 层以 ':' 分隔的字符串),然后进行一些字符串操作,最后从 PCAP 接收所有 HTTP 标头作为列表。

【讨论】:

  • 谢谢。我的目标是在拒绝服务攻击期间比较熵。我想实现两个计数器的想法:count_with 和 count_without。如果字符串中有“\r\n\r\n”,则增加count_with;否则增加count_without。我不知道如何提取“\r\n\r\n”进行计数!
  • 您不需要提取子字符串,只需检查它是否匹配。查看更新的答案
  • 我理解了这个想法,但我无法将它与读取 PCAP 文件的脚本部分结合起来......我需要读取 PCAP 文件以检查它是否匹配。我只知道如何读取PCAP文件:import pyshark pkts = pyshark.FileCapture('dump.pcap')
  • 对不起我的无知。我迷路了!