zhjx922 De Blog

超简单的PHP验证码识别

网站的登陆页、注册页等等等到处都是验证码,然而你的验证码真的安全么?也许只需要一段简单的小程序,你的验证码就会如同虚设。本文只是简单实现,不会太过深入。

有攻就有防

写这篇文章完全是因为同事的公众号发了一篇文章叫”实践-写个验证码”,你简单写了一下,我就简单破解一些试试,生活处处有乐趣啊~

生成验证码

Copy代码,执行,生成如下验证码:

验证码

如图我们能发现,这个验证码格式特别”规范”,字体大小一样,颜色都是黑色,让我们省了不少事儿。

二值化

程序读图,二值化(关键点在于查找字体颜色的阈值,这个验证码都是黑色,so…),通过程序一个像素点一个像素点判断,将属于字体颜色的标记为*,非字体颜色标记为0

验证码

从上面的图,能够大概看出验证码的样子(YTAD)

分析图像,切割

切割出字符串(先切绿线,再分别切蓝线,这样即使这个字符上下移动一下,也不太容易影响我们的切割)

验证码

提取特征码

将字符串拆分后,我们多次获取验证码,将a-z,A-Z,0-9等验证码的特征码全部记录下来。

验证码

这个是提取出来的字母Y

识别

识别的过程就是重复上面的:二值化->切割->提取特征码,再加上和之前提取的特征码比对相似度,就OK了。

PHP代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
/**
* 简单验证码识别
* @author zhjx922
*/
class vCode{
//字符特征码
private $_wordKeys = array (
'A' => '000**00000****000**00**0**0000****0000****0000************0000****0000****0000**',
'B' => '******00**000**0**0000****000**0******00**000**0**0000****0000****000**0******00',
'C' => '00*****00**000****00000***000000**000000**000000**000000**00000*0**000**00*****0',
'D' => '******00**000**0**0000****0000****0000****0000****0000****0000****000**0******00',
'E' => '*********00000**00000**00000******0**00000**00000**00000**00000*******',
'F' => '**********000000**000000**000000******00**000000**000000**000000**000000**000000',
'G' => '00*****00**000****000000**000000**000000**000*****0000****0000**0**000**00*****0',
'H' => '**0000****0000****0000****0000************0000****0000****0000****0000****0000**',
'I' => '******00**0000**0000**0000**0000**0000**0000**0000**00******',
'J' => '00****0000**0000**0000**0000**0000**0000***000****0**00***00',
'K' => '**0000****000**0**00**00**0**000****0000****0000**0**000**00**00**000**0**0000**',
'L' => '**00000**00000**00000**00000**00000**00000**00000**00000**00000*******',
'M' => '**0000*****00*************0**0****0**0****0**0****0000****0000****0000****0000**',
'N' => '**0000*****000******00******00****0**0****0**0****00******000*****000*****0000**',
'P' => '*******0**0000****0000****0000*********0**000000**000000**000000**000000**000000',
'Q' => '00****000**00**0**0000****0000****0000****0000****0**0****00****0**00**000****0*',
'R' => '*******0**0000****0000****0000*********0*****000**00**00**000**0**0000****0000**',
'S' => '0******0**0000****000000**0000000******0000000**000000**000000****0000**0******0',
'T' => '********000**000000**000000**000000**000000**000000**000000**000000**000000**000',
'U' => '**0000****0000****0000****0000****0000****0000****0000****0000**0**00**000****00',
'V' => '**0000****0000****0000**0**00**00**00**00**00**000****0000****00000**000000**000',
'W' => '**0000****0000****0000****0000****0**0****0**0****0**0*************00*****0000**',
'X' => '**0000****0000**0**00**000****00000**000000**00000****000**00**0**0000****0000**',
'Y' => '**0000****0000**0**00**000****00000**000000**000000**000000**000000**000000**000',
'Z' => '*******00000**00000**0000**0000**0000**0000**0000**00000**00000*******',
'a' => '00*****00**000**000000**0*********0000****000***0****0**',
'b' => '**000000**000000**000000**0***00***00**0**0000****0000****0000*****00**0**0***00',
'c' => '00*****00**000****000000**000000**0000000**000**00*****0',
'd' => '000000**000000**000000**00***0**0**00*****0000****0000****0000**0**00***00***0**',
'e' => '00****000**00**0**0000************0000000**000**00*****0',
'f' => '000****000**00**00**00**00**000000**0000******0000**000000**000000**000000**0000',
'g' => '0*****0***000*****000**0**000**00*****00**0000000******0**0000**0******0',
'h' => '**000000**000000**000000**0***00***00**0**0000****0000****0000****0000****0000**',
'i' => '00**0000**000000000***0000**0000**0000**0000**0000**00******',
'k' => '**00000**00000**00000**00**0**0**00****000****000**0**00**00**0**000**',
'l' => '***00**00**00**00**00**00**00**00**0****',
'm' => '*0**0**0**0**0****0**0****0**0****0**0****0**0****0**0**',
'n' => '**0***00***00**0**0000****0000****0000****0000****0000**',
'o' => '00****000**00**0**0000****0000****0000**0**00**000****00',
'p' => '**0***00***00**0**0000****0000****0000*****00**0**0***00**000000**000000',
'q' => '00***0**0**00*****0000****0000****0000**0**00***00***0**000000**000000**',
'r' => '**0****00***00**0**000000**000000**000000**000000**00000',
's' => '0******0**0000****0000000******0000000****0000**0******0',
't' => '00**000000**0000******0000**000000**000000**000000**000000**00**000****0',
'u' => '**0000****0000****0000****0000****0000**0**00***00***0**',
'v' => '**0000****0000**0**00**00**00**000****0000****00000**000',
'w' => '**0000****0000****0**0****0**0****0**0**********0**00**0',
'x' => '**0000**0**00**000****00000**00000****000**00**0**0000**',
'y' => '**0000****0000****0000****0000****0000**0**00***00***0***00000**0******0',
'z' => '******0000**000**000**000**000**0000******',
'0' => '000**00000****000**00**0**0000****0000****0000****0000**0**00**000****00000**000',
'1' => '00**000***00****0000**0000**0000**0000**0000**0000**00******',
'2' => '00****000**00**0**0000**000000**00000**00000**00000**00000**00000**00000********',
'3' => '0*****00**000**0000000**00000**0000***0000000**0000000**000000****000**00*****00',
'4' => '00000**00000***0000****000**0**00**00**0**000**0********00000**000000**000000**0',
'5' => '*******0**000000**000000**0***00***00**0000000**000000****0000**0**00**000****00',
'6' => '00****000**00**0**0000*0**000000**0***00***00**0**0000****0000**0**00**000****00',
'7' => '********000000**000000**00000**00000**00000**00000**00000**00000**000000**000000',
'8' => '00****000**00**0**0000**0**00**000****000**00**0**0000****0000**0**00**000****00',
'9' => '00****000**00**0**0000****0000**0**00***00***0**000000**0*0000**0**00**000****00',
);
/**
* 生成验证码
* @author 武老师
*/
public function make($verCode = '') {
if(empty($verCode)) {
$baseChars = 'ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789';
$verCode = '';
$codeCharLenth = 4;
for ($i = 1; $i <= $codeCharLenth; $i++) {
// 通过字符串下标形式随机获取
$verCode .= $baseChars{mt_rand(0, strlen($baseChars) - 1)};
}
}
// 以下代码是将生成的验证码生成图片
$font_size = 20;
$width = 60;
$height = 30;
$img = imagecreate($width, $height); // 新建一个基于调色板的图像
$bgR = mt_rand(50, 200); //r(ed)
$bgG = mt_rand(50, 200); //g(reen)
$bgB = mt_rand(50, 200); //b(lue)
$background = imagecolorallocate($img, $bgR, $bgG, $bgB); // 背景色
$black = imagecolorallocate($img, 0, 0, 0);
imagestring($img, 5, 9, 8, $verCode, $black); // 水平地画一行字符串
ob_start();
imagepng($img);
$image = ob_get_contents();
ob_end_clean();
return array(
'image' => $image,
'code' => $verCode
);
}
/**
* 获取原始图像数组
* @param string $imageString
* @return array
*/
public function getImage($imageString) {
$im = imagecreatefromstring($imageString);
list($width, $height) = getimagesizefromstring($imageString);
$image = array();
for($x = 0;$x < $width;$x++) {
for($y =0;$y < $height;$y++) {
$rgb = imagecolorat($im, $x, $y);
$rgb = imagecolorsforindex($im, $rgb);
if($rgb['red'] == 0 && $rgb['green'] == 0 && $rgb['blue'] == 0) {
$image[$y][$x] = '*';
} else {
$image[$y][$x] = 0;
}
}
}
return $image;
}
/**
* 移除无用数据
* @param array $image
* @return array
*/
public function remove($image) {
//计算x和y轴的
$xCount = count($image[0]); //60
$yCount = count($image); //30
$xFilter = array();
for($x = 0;$x < $xCount;$x++) {
$filter = true;
for($y = 0;$y < $yCount;$y++) {
$filter = $filter && ($image[$y][$x] == '0');
}
if($filter) {
$xFilter[] = $x;
}
}
//有字符的列
$xImage = array_values(array_diff(range(0, 59), $xFilter));
//存放关键字
$wordImage = array();
$preX = $xImage[0] - 1;
$wordCount = 0;
foreach($xImage as $xKey => $x) {
if($x != ($preX + 1)) {
$wordCount++;
}
$preX = $x;
for($y = 0;$y < $yCount;$y++) {
$wordImage[$wordCount][$y][$x] = $image[$y][$x];
}
}
foreach($wordImage as $key=>$image) {
$wordImage[$key] = $this->removeByLine($image);
}
return $wordImage;
}
/**
* 按行移除无用数据
* @param array $image
* @return array
*/
public function removeByLine($image) {
$isFilter = false;
foreach($image as $y => $yImage) {
if($isFilter == true || array_filter($yImage)) {
$isFilter = true;
} else {
unset($image[$y]);
}
}
krsort($image);
$isFilter = false;
foreach($image as $y => $yImage) {
if($isFilter == true || array_filter($yImage)) {
$isFilter = true;
} else {
unset($image[$y]);
}
}
ksort($image);
return $image;
}
/**
* 获取关键字字符串
* @param array $wordImage
* @return string
*/
public function getWordString($wordImage) {
$wordString = '';
foreach($wordImage as $image) {
foreach($image as $string) {
$wordString .= $string;
}
}
return $wordString;
}
/**
* 匹配关键字
* @param array $image
* @return array
*/
public function match($image) {
$match = array(
'min' => '',
'key' => ''
);
foreach($this->_wordKeys as $k => $v) {
$percent = 0.0;
similar_text($this->getWordString($image), $v, $percent);
if($match['min'] == '') {
$match['min'] = $percent;
$match['key'] = $k;
} else {
if($percent > $match['min']) {
$match['min'] = $percent;
$match['key'] = $k;
}
}
}
return $match;
}
/**
* 终端显示验证码
* @param $image
*/
public function show($image) {
foreach($image as $xImage) {
foreach($xImage as $yImage) {
echo $yImage;
}
echo PHP_EOL;
}
echo PHP_EOL;
}
}
$vCode = new vCode();
$codeImage = $vCode->make();
$imageString = $codeImage['image'];
$image = $vCode->getImage($imageString);
//原图
$vCode->show($image);
//去除干扰边框、拆字
$newImage = $vCode->remove($image);
$word = array();
$code = '';
foreach($newImage as $image) {
$vCode->show($image);
$code .= $vCode->match($image)['key'];
}
echo "生成的验证码为:{$codeImage['code']}" . PHP_EOL;
echo "识别的验证码为:{$code}" . PHP_EOL;
/*
//用来批量生成验证码的特征码。识别他人网站验证码,需要自己采集多张,人肉标记特征码
$vCode = new vCode();
$string = 'ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789';
$max = ceil(strlen($string) / 4);
$wordKeys = array();
for($i=0;$i<$max;$i++) {
$code = substr($string, $i * 4, 4);
$imageString = $vCode->make($code)['image'];
$image = $vCode->getImage($imageString);
$newImage = $vCode->remove($image);
foreach($newImage as $key => $image) {
$word = $vCode->getWordString($image);
isset($code[$key]) && $wordKeys[$code[$key]] = $word;
}
}
echo var_export($wordKeys);
*/

运行结果:

验证码

推荐公众号

文章最后推荐一下上面说到的公众号,更新频繁,特别适合初级以及基础不扎实的PHP程序员,作者武老师可是上过CCTV的人,如图:

验证码

长按二维码,关注武老师公众号

验证码

zhjx922 wechat
欢迎关注二维码,一起交流学习!