应直接从字符池剔除0/O/o/l/I/1等易混淆字符,推荐用str_split()构造含2-9、大写ABCDEFGHJKLMNPQRSTUVWXYZ、小写abcdefghjkmnpqrstuvwxyz的安全池,再shuffle()抽取。

PHP验证码生成时怎么排除0/O/l/I/1等易混淆字符
直接从字符池里砍掉这些字,比事后校验更干净、更省资源。常见错误是只删了大写O却忘了小写o,或排除了1但漏了l(小写L)和I(大写i),结果用户看到“P1O9”和“PlO9”根本分不清。
推荐做法是用 str_split() 构造一个明确剔除后的字符池:
- 数字保留 2-9,去掉 0、1
- 大写字母用
ABCDEFGHJKLMNPQRSTUVWXYZ(跳过 I、O) - 小写字母用
abcdefghjkmnpqrstuvwxyz(跳过 l、o、i) - 拼起来再
str_split(),后续用array_rand()或shuffle()取值
别用 range('a','z') 然后 array_diff() 剔除——容易漏掉大小写全形态,也难一眼确认是否真砍干净。
preg_match() 校验法为什么容易漏判
有人图省事,在生成后用 preg_match('/[0OlI1]/', $code) 检查,匹配到就重来。这看似可行,但有两个硬伤:
立即学习“PHP免费学习笔记(深入)”;
- 正则里
O和0是不同字符,l(小写L)和1、I(大写i)视觉接近但 ASCII 值差很远,单靠一个字符类写不全 - 更关键的是:如果正则写成
/[0OlI1]/i,i修饰符会让l和I被当成同一类,但l小写本身就没问题,真正要防的是l和1、I的混用,大小写不敏感反而引入误判
所以校验必须显式列出所有需排除字符:/[0OlI12Z5S8B]/,且不要加 i。不过还是建议优先用「源头剔除」,校验只是兜底。
验证码字符串去重时混淆字符干扰怎么破
当你要生成无重复字符的验证码(比如4位不重复),如果字符池里还留着 0 和 O,哪怕它们没被同时抽中,用户端仍可能因字体渲染把 A0K9 看成 AOK9。所以去重逻辑不能只解决“不重复”,还得保证“不可混淆”。
正确顺序是:
- 先做混淆字符剔除,得到安全字符池(如
'23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz') - 再在这个池子里做去重抽取:用
shuffle()+array_slice()最稳;若用循环+in_array(),注意每次取的$char必须来自已清洗过的池子 - 千万别在去重之后、输出之前再做一次混淆字符过滤——那会破坏“无重复”约束,导致长度缩水或死循环
strtolower() 验证时混淆字符带来的坑
用户输入 oLmN,你存的是 OLMN,用 strtolower() 两边转小写后比对,看起来没问题。但问题出在:如果原始池子没剔除 l 和 1,用户输 1LmN 也会被 strtolower() 变成 1lmn,而服务端是 olmn,这时候比较就崩了。
也就是说:strtolower() 解决不了混淆问题,它只解决大小写问题。真正要防的,是用户把 1 当 l 输、把 0 当 O 输——这种错误只能靠源头剔除字符池来堵死。验证环节的大小写处理,只是锦上添花,不是雪中送炭。
字符池一旦定下来,就别在验证层试图用转换补救视觉歧义。那不是容错,是埋雷。



















