网络协议逆向分析中,加密密钥长度的识别是还原私有通信格式的重要前置步骤。许多逆向系统通过统计密文分组大小、握手消息字段分布或熵值突变点来猜测密钥位数,但这类启发式方法在面对填充随机化、变长IV或混淆流量时容易失准。为了客观衡量识别模块的好坏,我们需要一套可重复的评估流程,用已知答案的样本去检验程序的输出,而不是凭经验目测。

评估基准数据的生成与组织
要在Ruby中评估识别准确率,第一步是准备带标签的基准集。所谓标签,就是每条仿真协议记录对应的真实密钥长度,例如128、192或256位。我们可以写一个Ruby脚本来模拟简易加密传输:用OpenSSL绑定生成随机密钥,对固定明文做AES加密,并把密文长度、前导字节等特征连同真实key_size写入JSON行文件。这样后续任何识别器跑完,都能直接拿预测值和这个字段比对。
组织数据时建议把样本按协议类型、是否含随机填充、IV长度分桶。因为识别器在不同场景下的表现往往差异很大,混在一起算总准确率会掩盖问题。Ruby的Struct或Hash都能清晰表达每条记录,配合CSV或JSON库做持久化也很方便。下面示例展示如何用Ruby生成一百条带真实密钥长度的模拟记录:
require 'openssl'
require 'json'
def gen_sample(key_size)
cipher = OpenSSL::Cipher.new("aes-#{key_size}-cbc")
cipher.encrypt
key = cipher.random_key
iv = cipher.random_iv
cipher.key = key
cipher.iv = iv
ct = cipher.update("hello protocol") + cipher.final
{
key_size: key_size,
iv_len: iv.bytesize,
ct_len: ct.bytesize,
ct_prefix: ct.bytes.first(4).unpack('C*')
}
end
samples = []
[128, 192, 256].each do |ks|
30.times { samples << gen_sample(ks) }
end
File.write('basis.jsonl', samples.map(&:to_json).join("n"))
上面的代码用OpenSSL::Cipher生成三种密钥长度的密文,并保留了IV长度、密文总长度和前四个字节作为特征。真实项目中可加入更多扰动,比如压缩后再加密、插入假握手包等,使基准集更贴近现实流量。只要保证key_size字段是可信真值,评估就有了锚点。
识别结果比对与准确率指标计算
有了基准集,下一步是把待评估的识别器对每条样本的预测密钥长度收集起来,用Ruby做差异统计。最直观的指标是命中率,即预测值等于真实值的样本占比。但当识别器输出的是可能长度区间时,仅看完全命中会过于严苛,此时应引入容差准确率,例如预测与真实差不超过一个分组块(如64位)也算部分正确。
除了整体比例,混淆矩阵能暴露系统性偏差。比如识别器是否总把256位误判为128位,或在某类填充下完全失效。Ruby里可用嵌套Hash构建矩阵,再格式化为文本或CSV。以下脚本读取basis.jsonl与预测文件,输出命中率与简化矩阵:
require 'json'
truth = []
pred = []
File.readlines('basis.jsonl').each_with_index do |line, i|
obj = JSON.parse(line)
truth << obj['key_size']
# 假设预测文件每行一个数字
pred << File.readlines('pred.txt')[i].to_i
end
hit = truth.zip(pred).count { |t, p| t == p }
puts "命中率: #{hit.to_f / truth.size}"
matrix = Hash.new { |h, k| h[k] = Hash.new(0) }
truth.zip(pred).each { |t, p| matrix[t][p] += 1 }
matrix.each do |t, row|
puts "真实#{t} => " + row.map { |p, c| "预测#{p}:#{c}" }.join(', ')
end
这段逻辑把真实值与预测值一一对齐,先算严格命中率,再按真实密钥长度聚合预测分布。若发现某行预测值明显偏移,说明识别器对该长度特征提取有缺陷。实践中还可加入平均绝对误差计算,用Ruby的Enumerable#sum就能快速得出,便于横向比较不同版本识别器的退化情况。
批量回放与评估自动化封装
单次脚本只能解决小样本,真实评估往往要跑成千上万条混合流量,并对比多个识别算法。这时可以把前面的逻辑封装成Ruby类,提供add_sample、load_predictions、report等方法,让评估过程像单元测试一样可重复。同时利用Ruby的并发特性,如Thread或Ractor,把加解密样本生成和比对并行化,缩短回归测试时间。
自动化封装还能固定随机种子,保证每次评估用的基准集一致,使准确率数字具备可比性。我们可以在类里内置几种评分模式:strict、tolerant、weighted,分别对应严格相等、容差内可接受、按误差距离给部分分。这样研究员调整识别阈值后,只需改个参数重跑,就能看到曲线变化。下面给出一个极简封装骨架:
class KeyLenEvaluator
def initialize(mode: :strict)
@mode = mode
@pairs = []
end
def add(truth, pred)
@pairs << [truth, pred]
end
def score
case @mode
when :strict
@pairs.count { |t, p| t == p }.to_f / @pairs.size
when :tolerant
@pairs.count { |t, p| (t - p).abs <= 64 }.to_f / @pairs.size
else
0.0
end
end
end
ev = KeyLenEvaluator.new(mode: :tolerant)
ev.add(128, 128)
ev.add(256, 192)
puts ev.score
这个骨架虽小,却体现了把准确率评估从临时脚本提升为可维护组件的思想。当团队不断迭代网络协议逆向工具时,每次提交都跑一遍该评估器,就能在CI里守住识别准确率底线。结合前面生成的基准集与混淆矩阵输出,Ruby完全能胜任轻量级但严谨的密钥长度识别准确率评估工作。