網站的登陸頁、註冊頁等等等到處都是驗證碼,然而你的驗證碼真的安全麼?也許只需要一段簡單的小程式,你的驗證碼就會如同虛設。本文只是簡單實現,不會太過深入。
有攻就有防
寫這篇文章完全是因為同事的公眾號發了一篇文章叫"實踐-寫個驗證碼",你簡單寫了一下,我就簡單破解一下試試,生活處處有樂趣啊~
生成驗證碼
Copy程式碼,執行,生成如下驗證碼:
如圖我們能發現,這個驗證碼格式特別"規範",字型大小一樣,顏色都是黑色,讓我們省了不少事兒。
二值化
程式讀圖,二值化(關鍵點在於查詢字型顏色的閾值,這個驗證碼都是黑色,so...),通過程式一個畫素點一個畫素點判斷,將屬於字型顏色的標記為*,非字型顏色標記為0
<center>從上面的圖,能夠大概看出驗證碼的樣子(YTAD
)</center>
分析影象,切割
切割出字串(先切綠線,再分別切藍線,這樣即使這個字元上下移動一下,也不太容易影響我們的切割)
提取特徵碼
將字串拆分後,我們多次獲取驗證碼,將a-z,A-Z,0-9等驗證碼的特徵碼全部記錄下來。
<center>這個是提取出來的字母Y
</center>
識別
識別的過程就是重複上面的:二值化->切割->提取特徵碼,再加上和之前提取的特徵碼比對相似度,就OK了。
PHP程式碼實現
/**
* 簡單驗證碼識別
* @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);
*/
執行結果:
掃描二維碼,一起交流學習~