C#發現之旅第九講 ASP.NET驗證碼技術
袁永福 2008-5-15
系列課程說明
為了讓大家更深入的瞭解和使用C#,我們將開始這一系列的主題為“C#發現之旅”的技術講座。考慮到各位大多是進行WEB資料庫開發的,而所謂發現就是發現我們所不熟悉的領域,因此本系列講座內容將是C#在WEB資料庫開發以外的應用。目前規劃的主要內容是圖形開發和XML開發,並計劃編排了多個課程。在未來的C#發現之旅中,我們按照由淺入深,循序漸進的步驟,一起探索和發現C#的其他未知的領域,更深入的理解和掌握使用C#進行軟體開發,拓寬我們的視野,增強我們的軟體開發綜合能力。
本系列課程配套的示範代碼為 http://files.cnblogs.com/xdesigner/cs_discovery.zip 。
本系列課程發行的文章有
C#發現之旅第一講 C#-XML開發
C#發現之旅第二講 C#-XSLT開發
C#發現之旅第三講 使用C#開發基於XSLT的代碼產生器
C#發現之旅第四講 Windows圖形開發入門
C#發現之旅第五講 圖形開發基礎篇
C#發現之旅第六講 C#圖形開發中級篇
C#發現之旅第七講 C#圖形開發進階篇
C#發現之旅第八講 ASP.NET圖形開發帶超連結的餅圖
C#發現之旅第九講 ASP.NET驗證碼技術
C#發現之旅第十講 文件物件模型
課程說明
大家好,在上一節課程中,我們開始瞭解了如何在在ASP.NET中使用圖形編程的技術。今天我們針對驗證碼技術深入的瞭解圖形編程在ASP.NET中的應用。
驗證碼技術是目前很多WEB程式採用的一種安全防禦技術。系統在登入的時候不但要輸出使用者名稱和密碼,還要額外輸入一種隨機產生的驗證碼文本,此時使用者需要正確的輸入這三個資訊才能登入到系統中。
由於驗證碼技術能有效抵禦某些駭客攻擊,因此得到相當廣泛的應用,而且在一些C/S系統中也採用了這種源自WEB開發的技術。
驗證碼原理
在現在的軟體運行環境下,安全成為大部分軟體必須考慮的問題,駭客無處不在,攻擊方式日益豐富,尤其是WEB系統由於其開放性更是遇到嚴峻的考驗,駭客事件層出不窮,造成的損失和影響也不斷變大,對此我們軟體開發人員需要對此有相當的認識並採取措施抵禦各種駭客攻擊。
枚舉字典安全攻擊
在各種駭客攻擊中,很常見的就是套取使用者名稱和密碼,其中很多是採用枚舉字典的方式來不斷的測試使用者名稱和密碼。
比如某駭客獲得一銀行帳號,然後開啟帳號的開戶銀行的網上銀行登入介面。分析其中的HTML代碼,發現其頁面粗製濫造,沒有驗證碼,沒有任何安全控制,只要求輸入銀行帳號和取款密碼就可以登入。駭客心中大喜,馬上寫了一個程式,直接調用HTTP協議,使用程式來類比瀏覽器向網上銀行伺服器提交帳號和密碼嘗試登入。由於取款密碼是6位阿拉伯數字,因此也就有一百萬種組合,駭客的電腦從六個零開始測試一直到六個九,這一定會測試出真正的密碼。駭客找到一台寬頻高速上網的電腦,運行套取取款密碼程式後就忙其他事了,假設這台電腦1秒能測試10個密碼,於是花費10萬秒的時間肯定能找到密碼。10萬秒也就是27小時,一天多點的時間,實際上很可能用不了那麼長的時間。駭客外頭轉了一圈回來,發現密碼已經找到了,於是馬上登入網上銀行撈錢,或者偽造一個銀行卡去ATM機上提取現金。也就是說駭客最多花了一天時間即可獲得數目不可預知的非法收入。
驗證碼防禦
網上銀行可以有很多手段來抵禦駭客攻擊,比如使用ActiveX控制項代替標準的文字框來輸入帳號和密碼,可以使用USB介面的密碼盤來進行資料加密和檢測,或者使用一個用戶端程式代替瀏覽器來登入網上銀行。但這些是用戶端技術,千千萬萬的駭客可以操著各種手術刀來解剖這些技術,從根本上說用戶端技術是不可靠的。
相對而言採用伺服器端技術就比較安全了。比如發現密碼連續錯誤3次即鎖定賬戶,1天后才能登入;也可以使用驗證碼技術來很大程度的抵禦枚舉字典套取密碼的攻擊。
現有一個新的網上銀行,和舊網銀差不多,但採用了驗證碼技術,使用者登入時除了要輸入帳號和取款密碼,瀏覽器還顯示一個圖片,裡面顯示了一些潦草的字元,使用者需要辨認這些字元然後再輸入進去,瀏覽器向伺服器提交表單時會附加使用者輸入的驗證碼,伺服器接受表單資料後除了校正帳號和取款密碼後,還要檢查驗證碼是否輸入正確,若登入資訊校正失敗,則伺服器端則會提示重新登入,而且還產生包含隨機內容的新的驗證碼,使用者在次登入時又得重新識別新的驗證碼了。
由於正確的驗證碼文本是儲存在伺服器上的,用戶端的駭客程式不可能獲得,驗證碼的內容是隨機的,駭客程式也無法找到規律,只能辨認從伺服器端發出的包含驗證碼的圖片來獲得驗證碼。這裡就體現了電腦和人腦的差別了,人腦在圖形識別方面遠遠超過了目前的電腦,伺服器端使用一些技術產生的書寫潦草,充滿隨機分布的雜點的圖片,人腦是可以相當容易的識別的,但目前的電腦是難以識別的。駭客程式無法識別驗證碼,只能顯示圖片讓駭客親自辨認,這時每測試一次密碼,駭客都得仔細辨認一下驗證碼圖片,然後手工輸入驗證碼文本。最多要輸入一百萬次,估計全世界沒人會願意進行這樣的工作。這樣驗證碼技術就有效抵禦了這種枚舉字典測試密碼的安全攻擊。此時駭客會轉而尋找其他方法,而大量的初級駭客會放棄攻擊這個網站。
驗證碼技術概念
驗證碼技術利用了人腦和電腦之間的差別。
大家都知道電腦和人腦是存在很大的差別的,電腦很勝任數值運算和精確的邏輯判斷,很適合執行那些重複又重複的簡單資料處理,但Image Recognition,模糊邏輯判斷,學習和創新能力很差。而人腦正好相反,數值運算不行,但Image Recognition卻很擅長。
在驗證碼技術中,有一個很關鍵的過程就是需要從一個充滿隨機形狀的圖片中辨認出驗證碼文本,這個過程目前的電腦是難以實現的,而對人腦卻能相當容易。
採用電腦難於識別而人腦容易識別的圖片,強迫人腦參與安全資訊驗證過程,就是驗證碼技術。這裡包含驗證碼文本的圖片是驗證碼媒介。仔細觀察,我們可以知道這種驗證碼媒介具有電腦建立容易識別難的特點,因此類似的我們也可以採用合成語音等其他手段來作為驗證碼媒介。例如伺服器提供一個類似QQ表情的圖片,加上雜訊,然後讓使用者判斷選擇這個圖片的表情狀態,是哭是笑還是流鼻血,這樣也可以當作驗證碼。
由於枚舉字典安全攻擊需要大數量的嘗試猜測安全資訊,其重複過程可能需要數萬甚至數億次,而驗證碼技術強迫了人腦參與每一次嘗試猜測安全資訊,人腦難以勝任長時間高頻率的簡單重複勞動,因此這就使得枚舉字典安全攻擊變得不可行,如此應用程式成功的防禦了枚舉字典安全攻擊。
ASP.NET中使用驗證碼技術
由於驗證碼技術中伺服器程式需要建立驗證碼圖片,裡面用到了圖形編程,因此本節課程仍然是C#發現之旅的圖形編程系列教程。
根據驗證碼的原理,我們使用C#在ASP.NET中實現了驗證碼的功能。
checkimage.aspx
首先根據上節課程的內容,我們要建立一個圖片服務頁面,專門用於提供包含驗證碼文本的圖片,為此我們建立一個 checkimage.aspx 的頁面。其HTML代碼很簡單,只有一行,不輸出任何內容。在其Page_Load方法中就有建立驗證碼圖片的過程。
// 建立一個包含隨機內容的驗證碼文本 System.Random rand = new Random(); int len = rand.Next(4 , 6 ); char[] chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray(); System.Text.StringBuilder myStr = new System.Text.StringBuilder(); for( int iCount = 0 ; iCount < len ; iCount ++ ) { myStr.Append( chars[ rand.Next( chars.Length )]); } string text = myStr.ToString(); // 儲存驗證碼到 session 中以便其他模組使用 this.Session["checkcode"] = text ; Size ImageSize = Size.Empty ; Font myFont = new Font("MS Sans Serif" , 20 ); // 計算驗證碼圖片大小 using( Bitmap bmp = new Bitmap( 10 , 10 )) { using( Graphics g = Graphics.FromImage( bmp )) { SizeF size = g.MeasureString( text , myFont , 10000 ); ImageSize.Width = ( int ) size.Width + 8 ; ImageSize.Height = ( int ) size.Height + 8 ; } } // 建立驗證碼圖片 using( Bitmap bmp = new Bitmap( ImageSize.Width , ImageSize.Height )) { // 繪製驗證碼文本 using( Graphics g = Graphics.FromImage( bmp )) { g.Clear( Color.White ); using( StringFormat f = new StringFormat()) { f.Alignment = StringAlignment.Near ; f.LineAlignment = StringAlignment.Center ; f.FormatFlags = StringFormatFlags.NoWrap ; g.DrawString( text , myFont , Brushes.Black , new RectangleF( 0 , 0 , ImageSize.Width , ImageSize.Height ), f ); }//using }//using // 製造雜訊 雜點面積占圖片面積的 30% int num = ImageSize.Width * ImageSize.Height * 30 / 100 ; for( int iCount = 0 ; iCount < num ; iCount ++ ) { // 在隨機的位置使用隨機的顏色設定圖片的像素 int x = rand.Next( ImageSize.Width ); int y = rand.Next( ImageSize.Height ); int r = rand.Next( 255 ); int g = rand.Next( 255 ); int b = rand.Next( 255 ); Color c = Color.FromArgb( r , g , b ); bmp.SetPixel( x , y , c ); }//for // 輸出圖片 System.IO.MemoryStream ms = new System.IO.MemoryStream(); bmp.Save( ms , System.Drawing.Imaging.ImageFormat.Png ); this.Response.ContentType = "image/png"; ms.WriteTo( this.Response.OutputStream ); ms.Close(); }//using myFont.Dispose(); |
首先我們使用.NET架構中隨機數產生器 Random類型來產生一個不定長的包含隨機數字和英文字元的文本,這就是驗證碼原始文本,我們將其儲存在session中供以後使用。
然後我們建立一個臨時圖片,並據此建立一個臨時的圖象繪製對象,然後調用Graphics的MeasureString函數獲得這個字串的顯示大小。據此我們就可以計算出驗證碼圖片的大小。
然後我們建立一個位元影像對象,在此基礎上建立一個圖形繪製對象,然後調用圖形繪製對象的DrawString函數將驗證碼文本繪製在這個位元影像上。
繪製驗證碼後我們在圖片上隨機的製造雜點來混淆圖片內容。這些雜點的面積占圖片面積的30%,而且其位置和顏色都是隨機的。這些雜點能嚴重的幹擾程式辨認驗證碼文本。但人腦在辨認文本時能比較輕鬆的排除這些幹擾。
圖片產生後頁面就使用PNG格式將圖片文檔發送到用戶端。
checkimage.aspx還提供了一個靜態函數來檢測驗證碼。
/// <summary> /// 檢查指定的文本是否匹配驗證碼 /// </summary> /// <param name="text">要判斷的文本</param> /// <returns>是否匹配</returns> public static bool CheckCode( string text ) { string txt = System.Web.HttpContext.Current.Session["checkcode"] as string ; return text == txt ; } |
代碼很簡單。就是看看參數傳進的文本是否等於 session 中儲存的驗證碼文本。其他的頁面程式調用這個函數就可以判斷驗證碼的正確性。
login.aspx
驗證碼圖片服務頁面完成後,我們就可以利用這個頁面來實現驗證碼技術。我們建立一個類比系統登入的頁面。
上面放置輸入使用者名稱,密碼和驗證碼的三個文本輸入框。其中驗證碼輸入框後面放置一個圖片,圖片就來源於checkimage.aspx頁面。使用者輸入三個資訊後點擊確定按鈕進行登入。則運行該按鈕的伺服器段代碼。
private void cmdOK_Click(object sender, System.EventArgs e) { string UserName = this.txtUserName.Text ; string Password = this.txtPassword.Text ; string CheckCode = this.txtCheckCode.Text ; if( UserName == "張三" && Password == "abc" && checkimage.CheckCode( CheckCode ) ) { this.lblResult.Text = "<b>登入成功</b>"; this.RegisterStartupScript("a" , "<script>alert('登入成功');</script>"); } else { this.lblResult.Text = "<font color=red><b>使用者登入資訊錯誤,請重新輸入</b></font>"; } } |
在該代碼中,程式獲得使用者輸入的使用者名稱,密碼和驗證碼,然後判斷使用者名稱密碼是否正確,還調用checkimage的靜態函數CheckCode來判斷驗證碼是否正確。只有這三個資訊都正確則登入成功,否則登入失敗。
在少數情況下,程式產生的驗證碼圖片難以辨認,則需要重新提供新的驗證碼圖片,此時我們在登入頁面中可以雙擊這個圖片來更新驗證碼圖片。顯示驗證碼圖片的HTML代碼片斷為
<img src="checkimage.aspx" title='看不清楚,雙擊圖片換一張。' ondblclick="this.src = 'checkimage.aspx?flag=' + Math.random() " border="1"> |
可以看到 ondblclick 事件處理中更新了圖片來源,這裡使用了一個毫無意義的flag頁面參數,這是保證瀏覽器不會使用本機快取的驗證碼圖片而是下載最新的驗證碼圖片。
使用者雙擊圖片後,瀏覽器重新調用checkimage.aspx頁面,於是伺服器端的驗證碼文本用了新的,而圖片內容也隨之更新。
由於每次嘗試登入或更換驗證碼圖片時,正確的驗證碼都是隨機的發生改變,毫無規律,這樣就很大的增強了登入頁面的安全性。但這樣做會讓使用者登入時需要辨認和輸入驗證碼,這會降低應用程式的可用性。因此是否使用驗證碼技術是需要多方面權衡的。
小結
在本次課程中,我們一起研究了驗證碼技術的原理,並使用C#在ASP.NET中實現了簡單的驗證碼技術。驗證碼技術是一種安全防禦技術,其中使用了一定的圖形編程。這樣看來圖形編程應用是廣泛的,可以為很多其他的技術提供支援。