ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

数字串分析实战:用信息熵与Python识别随机性与编码规律

数字串分析实战:用信息熵与Python识别随机性与编码规律 1. 接到一串怪异数字时的第一反应与判断思路1.1 数字串的第一眼特征长度、分段与重复模式先看这串数字11111177777777888888888。扫一眼直觉告诉我有三种感受够长、有规律、但规律本身很“刻意”。数一下长度比较好算连续六个 1连续八个 7这里数清楚是 7 个还是 8 个会影响判断再加连续十一个 8总长度在 24 到 26 位之间。这种长度的纯数字串第一反应不应该是“是不是谁手滑乱按的”而应该是“它极大概率属于某类系统生成的标识”。真正值得关注的是它的重复结构1出现 6 次7出现 7 次8出现 11 次。这个结构让我立刻想到两类系统生成的产物第一类是验证码、取件码、会议号码这类“为了让人好记而刻意构造”的短码第二类是某些数据库或业务系统里的复合编号比如“批次号 流水号”拼接而成。但这里有个矛盾点如果是最常见的验证码或短信验证码一般只发 4 到 6 位最长不过 8 位因为人的短时记忆极限就在这个范围附近。而 20 位以上的纯数字更像是一串被拼出来的业务标识。也就是说单从长度上就能否定“这是普通验证码”的猜测应该往序列号、流水号或者时间戳衍生值的角度去查。1.2 它可能是哪几类东西先判断来源再判断用途收到一串不明数字只要是做过几年系统运维或后端开发的人都会下意识问它来自哪里、被谁使用、用在什么场景。我按经验列几个常见的可能性短信服务商下发的动态验证码但一般 4 到 8 位不会到 20 位以上排除。物流公司运单号、快递柜取件码位数通常在 8 到 16 位可能有字母混合纯数字比较少见。会议系统、直播间的房间号或入会码常见 9 到 11 位往往带分隔符纯数字连续 20 位以上的很少。数据库主键、用户 ID 或业务流水号这种最像。流水号经常由“时间戳的末几位 随机数 递增序号”拼接而成结果就是一串看起来“没规律但实际有生成逻辑”的数字。某种随机令牌或一次性密钥的前段截取比如内部接口签名中的随机值。所以拿到这样一串数字真正的第一步不是急着分析它每个字符代表什么而是先确认它出现的上下文。上下文决定了你把它往哪个方向拆如果它是用户下单后生成的订单号那就按订单号的组成规则看如果是设备上报数据里的标识就得按协议字段去读如果只是陌生人突然发给你的那就更要警惕了。1.3 判断方向面对未知数字串时先建立分析框架我在实际工作中养成了一个习惯面对任何一串来路不明的数字先建立一个“三段式”判断框架。第一段是格式判断数位、字符集、是否包含分隔符、是否有字母。纯数字代表它可能是计算机生成的 ID也可能只是人手工敲的。第二段是结构猜测尝试把数字串按“固定前缀 可变后缀”拆开。例如企业订单号喜欢用“日期 序列”验证码喜欢用“随机数”而分布式系统生成的雪花 ID 则会把时间戳、机器号、序列号拼在一段长整数里。第三段是概率判断这个数字串是否表现出“随机”特征具体怎么做我会在本文第 2 节详细讲。这里先给一个结论11111177777777888888888这串数字人工构造的“痕迹”非常明显。因为真正随机生成的数字序列极少出现连续重复 6 次以上的字符。这个判断背后是有数学支撑的往下看。2. 拆解数字串重复模式背后的数学与概率2.1 这串数字符合“随机”直觉吗重复数字的出现概率假设我们有一个完全随机的数字生成器每次独立地从 0 到 9 中挑一个数字那么连续出现 6 个相同数字的概率是多少第一个数字没有约束出现任意数字都行。从第二个数字开始每个数字都必须和前一个相同每个位置的概率是 1/10。因此连续重复 6 位的概率是 ( (1/10)^5 1/100000 )也就是十万分之一。如果连续重复 11 位比如最后的 8概率是 ( (1/10)^{10} )只有百亿分之一。用生活化的方式说如果这串数字真的是随机生成的那它的巧合程度相当于“你在街上连续碰到 10 个都穿红色衣服的陌生人”那种概率。当然这里有个容易被误解的点从结果往回看某个具体序列比如“123456789...”出现的概率也很低但这并不代表这个序列“不随机”。随机性的判定要看生成过程而不是单个结果。但反过来说如果一个结果表现出极强的结构性偏离那它背后有人为设定概率就很大。这串数字最大的特征就是重复性压倒性地高。它不像“958174620138...”那样各数字均匀出现而是一段接一段地重复同一个字符。这强烈暗示它可能是被某种规则构造出来的。2.2 用熵值量化信息量一眼看出数字串的“可预测性”判断一串数字结构性强不强信息论里有个现成的工具叫信息熵公式是[ H -\sum_{i1}^{n} p_i \log_2 p_i ]其中 ( p_i ) 是第 ( i ) 个字符出现的概率。对一串长度固定的数字如果每个数字出现的概率完全均匀各占 1/10它的熵值最大如果某个数字大量重复熵值就会明显下降。拿这串11111177777777888888888来算假设总长度为 24 位其中1占 6 位7占 7 位8占 11 位真实数一下可能略有偏差不影响结论。那么[ p(1) 6/24 0.25 \ p(7) 7/24 \approx 0.29 \ p(8) 11/24 \approx 0.46 ]代入熵公式[ H -(0.25 \times \log_2 0.25 0.29 \times \log_2 0.29 0.46 \times \log_2 0.46) ]每一项算出来( 0.25 \times (-2) -0.5 )( 0.29 \times (-1.78) \approx -0.52 )( 0.46 \times (-1.12) \approx -0.52 )所以[ H \approx 0.5 0.52 0.52 1.54 ]而一个完全随机的数字串每一位的理论熵是 ( \log_2 10 \approx 3.32 )24 位总熵约 79.7。现在这串实际熵只有 1.54连随机串熵值的一半都不到。这意味着什么简单说如果把它当作一个密码或密钥它的“猜测成本”极低但如果把它当作一个业务编号它的信息量也很低——你几乎可以从里面直接读出它想表达的含义比如“三个批次每批对应一个数字”。这个例子也说明熵不是用来算热闹的它是一把尺子能快速告诉你一串数据更可能是“随机生成”还是“按规则编码”。2.3 人为设计序列的常见编码习惯当数据不是随机生成时大概率遵循某些工程师或产品经理习惯用的编码规则。我见过最多的几种重复数字表等级或表类别例如111表示 A 类、777表示 B 类、888表示 C 类后续是数量或序号。用长串相同数字做填充为了让编号达到固定长度前面补 0 太常见补 1、补 7、补 8 也有主要为了视觉上区分批次。数字越简单越好记验证码取件码里888888、666666这种重复码人工设计的意图很明显因为用户体验好。回到标题这串数字111111、7777777、888888888三段重复非常像“用不同数字作为分隔段拼接出来的 ID”。它不是随机数更可能是人为生成的展示型编码。3. 用 Python 实测写一个数字序列特征分析脚本3.1 核心指标选型长度、字符分布、最大连续重复、熵值光靠眼睛看不够遇到大量类似数字串时最好写个脚本批量分析。我每次拿到未知数字串都会先算四个指标长度判断大致类型按前文经验排除短验证码或超长密钥。字符分布各数字的出现次数和占比能直接看出是否有偏科现象。最大连续重复长度这是判断“人为设计”最直观的指标也是本案例最关键的指标。熵值量化信息的不确定性配合第 2 点的分布看结构性强弱。这四个指标算完基本能对一个数字串的“出身”有个八成把握。下面给出可以直接跑的脚本。3.2 完整可运行代码分析任意数字串的统计学特征写一段 Python 脚本可以直接复制到本机跑。不需要装第三方库只用到标准库里的math和collections。import math from collections import Counter def analyze_number_sequence(seq: str) - dict: 输入一个由数字组成的字符串返回统计特征。 核心指标长度、字符分布、最大连续重复、信息熵。 seq seq.strip() if not seq.isdigit(): raise ValueError(输入必须为纯数字字符串) # 1. 长度 total_len len(seq) # 2. 字符分布 counter Counter(seq) distribution {ch: count for ch, count in sorted(counter.items())} # 3. 最大连续重复长度 max_run 1 current_run 1 for i in range(1, total_len): if seq[i] seq[i - 1]: current_run 1 max_run max(max_run, current_run) else: current_run 1 # 4. 信息熵以比特为单位 entropy 0.0 for ch, count in distribution.items(): p count / total_len entropy - p * math.log2(p) return { length: total_len, distribution: distribution, max_consecutive_repeat: max_run, entropy_bits: round(entropy, 4), } if __name__ __main__: test_seq 11111177777777888888888 result analyze_number_sequence(test_seq) print(分析对象, test_seq) print(长度, result[length]) print(字符分布, result[distribution]) print(最大连续重复位数, result[max_consecutive_repeat]) print(信息熵比特, result[entropy_bits])这段代码没有做任何复杂优化主打一个“十分钟内看懂、复制就能跑”。我在公司内部也放过类似脚本给运营同事用他们拿到的是一堆订单号或会员 ID跑完就能看出系统生成规则有没有异常。3.3 运行结果解读如何看这个脚本的输出如果直接跑上面的代码输出会是分析对象 11111177777777888888888 长度 24 字符分布 {1: 6, 7: 7, 8: 11} 最大连续重复位数 11 信息熵比特 1.5378和我手算的结果基本一致脚本里把 7 统计成了 7 个8 统计成了 11 个总长 24 位。怎么解读长度 24完全排除短信验证码更像业务标识或流水号。字符分布只有三种数字全串只有1、7、8没有任何其他数字这说明字符集被人为限制了。最大连续重复位数 11这是最扎眼的。一个正常的随机数字串最大连续重复位数通常在 2 到 3 左右偶尔 4 已经是小概率事件到 11 这种程度基本可以断定“有鬼”。熵值 1.54对比理论最大熵 3.32单字符已说明问题。如果把这串数字当密码它的密码强度非常弱。用这个脚本的好处是把“感觉上很怪异”变成了“数据上可量化”。以后再收到不明数字串不用拍脑袋猜直接把特征打出来和已知系统的编号特征库比对即可。4. 识别之后怎么办随机性在真实业务中的常见坑4.1 随机数生成器选型误区伪随机与真随机的本质差异看到一串数字先判断它是否随机很多人的第一反应是“这不就是 random 函数生成的吗”但实际上生成数字的方式完全不同至少分三层真随机从物理噪声源如硬件热噪声、宇宙背景辐射中提取熵无法预测。安全密钥、加密 IV、抽奖大奖号码都应该用这类。密码学安全伪随机CSPRNG虽然算法生成但设计上即使泄露一部分输出也无法反推未来输出且通过统计测试。代表性实现是操作系统提供的/dev/urandom、secrets模块。普通伪随机如random模块通过固定种子和线性同余算法生成速度快但可预测。适合模拟测试、随机渲染绝不适合安全场景。很多团队踩过的坑就是拿普通伪随机生成“优惠券激活码”或“内部令牌”结果被人反推出算法批量刷走礼品。这个坑的根源不是算法坏了而是使用场景对随机性的安全等级要求远高于实现者的预设。回到这串数字本身它明显不是任何随机数生成器的产物因为它缺少随机序列应有的统计特征。反过来这提醒我们如果哪天我们的业务系统依然在生成这种“看起来很有规律的编号”那将来被人枚举、碰撞的概率是很高的。4.2 序列号与验证码设计易读性和防碰撞怎么平衡业务上生成编号往往要同时考虑两个矛盾目标一方面要短、要好记、要能人工快速输入另一方面要防枚举、防碰撞、防猜解。这两个目标经常打架。拿验证码举例。早期很多系统用111111、888888这类“易记码”做演示或内测一旦上线没清理干净就会出现大量用户撞码成功。正确的做法是打两套规则展示码可以短一点、好念一点但必须绑定有效期、绑定设备、绑定会话。底层 ID则必须是长随机数或雪花 ID绝对不能靠“短码 好记”来承载安全边界。如果二选一我永远建议安全优先。一个好记的验证码被猜到一次损失的可能是整个账号体系。那业务流水号呢很多企业喜欢把订单号做成“日期 渠道 序号”比如20250109123456。这种编码有两个致命伤一是信息泄露竞争对手能从订单号推算出你的单量二是序号部分是线性递增的很容易被人遍历抓取。更稳妥的做法是业务展示时用短码而数据库主键、接口透传的标识统一用高熵ID。哪怕展示码被人猜到底层逻辑也不会被暴力拆穿。4.3 日志排查技巧如何快速判断一串数字属于什么格式实际开发排查中经常在日志里看到大量纯数字串比如用户 ID、交易流水号、设备编号。如果拿到一串和标题类似的数字我的排查顺序是看上下文日志位置、触发接口、前后字段。这是最高效的一步80% 的问题能直接定位。看长度和首尾字符时间戳的末 X 位通常以当前年份相近的数字开头雪花 ID 一般较长且高位会随时间是递增趋势自增 ID 则是“从 1 开始的短数字”。看重复规律如果有标题这种“同数字连续重复很长”的现象多半是业务方手动拼字符串时把固定值写死比如batch_id user_id直接拼接。对照生成规则翻代码或问上游确认它是Timestamp Random、Snowflake ID、Hash 截断还是UUID 转数字。有人会问为什么不直接用在线工具查能用但自己写脚本的意义在于可以把历史数据批量拉下来一次性算出所有特征而不是一条条去查。这不光快还能在批量结果里发现隐藏的模式比如所有 ID 的最高位都相同那说明生成时可能截取了固定的时间高位。我实际处理过一个工单用户反馈订单号码“太长不好输入”结果查出来是系统把时间和渠道编码直接拼在订单号中间造成了 32 位超长串。当时就是用类似上文脚本批量统计发现所有号码都有同一个 6 位前缀一下定位到拼接逻辑里写死的前缀常量。5. 从数字串展开的通用方法论遇到“看不懂的数据”先别慌5.1 建立自己的数据指纹库经过几次和数字串“搏斗”之后我养成了一个工作习惯给每种已知业务编号建立特征指纹。比如用户 ID12 位纯数字最高位逐年递增熵值接近 3.3连续重复不超过 2。订单号16 位前 8 位是日期后 8 位是随机串最大连续重复 4 以内。退款单号18 位固定前缀T 时间戳 4 位校验码字母数字混合。这个指纹库不一定非要做成系统可以简单记在本地文档里甚至记在脑子里。积累多了之后排查效率会成倍提升。因为后端日志里百分之八十的“看不懂的数字”都能靠指纹库直接对上号。这个过程很像医生看化验单正常人血常规指标范围是已知的病人指标超出范围才能判断有异常。没有基线就谈不上异常检测。5.2 公示数据安全红线什么数字能发什么数字不能发最后必须强调一条红线这可能和标题数字本身无关但每次分析完数字串我都会提醒自己一遍能从一串 ID 中反推出系统信息的在安全评估里都属于敏感数据。如果这串11111177777777888888888是从生产环境日志里捞出来的那就不能直接把它贴到公开工单、技术社区或博客里。因为高位的重复数字可能直接暴露系统的“批次号设计规则”外加连续的编号可能让攻击者推测出系统在某段时间内生成的数据量。正确的做法是脱敏替换掉中段、末段字符保持位数和大致分布。或者只展示统计分析结果不展示完整原始串。我自己的习惯是分析这类数据时本地用脚本算完特征公开分享时只贴脱敏后的示例例如111XXX777YYY888ZZZ。这样既不泄露业务规则又能完整展示分析方法。这算是我踩过坑之后总结出的教训。以前写过一篇关于订单号特征的分享贴了真实号段结果被同行提醒“你这也太暴露了”从那以后就养成了先脱敏再分析的习惯。写这篇文章时我也再三确认过讨论的是数字串的分析方法完全基于构造示例没有涉及任何真实的业务系统数据。5.3 下次再遇到一串奇怪数字建议这样上手不是每个人都需要写 Python 脚本去分析数字但一套标准的操作流程可以复用先确认这根数字的来源和上下文这是最高优先级。用长度、字符集、重复模式这三个维度做初筛。如果判断它可能是随机生成的算一算熵值或转成二进制后看看分布。如果怀疑它是拼接生成的试着按固定前缀 变量后缀去切分。最后无论结果如何做好脱敏再拿出来讨论或存档。整个过程的核心其实不是“分析数字”而是“建立判断框架”。数字本身没有意义意义在于它从哪里来、被用来做什么、别人看到它能推断出什么信息。带着这三个问题去看哪怕看起来毫无规律的乱码也能拆出背后的逻辑。从我个人的实际操作体会来说面对类似11111177777777888888888这种“看上去很怪”的输入最有价值的动作不是盯着它看半天而是立刻做两件事量化它的统计特征再把它放回它出现的上下文里。两件事做完答案通常自己就浮出来了。
返回列表