2010年6月9日 星期三

Visual Studio 2010 Pro Power Tools

Visual Studio 2010 可不是只有4/12 時的版本哦!隨著時間推進,愈來愈多的新功能/工具也跟著發佈。而且,是免費。今天介紹的是2010/06/08 (昨天)發佈的 Pro Power。

安裝

與Visual Studio 2005/2008 相比,安裝在Visual Studio 2010 上顯得極度的簡單。首先執行 Visual Studio 2010, 執行 Tools/Extension Manager

image

在搜尋文字框上輸入Power後,可篩選出符合條件的 Extension。選擇 Visual Studio 2010 Pro Power Tools,點擊「Download」鍵。安裝完畢後會要求重新起動 Visual Studio。

image

功能

新增的功能好多啊!解決了不少的問題。

Add reference diaglog

例如,Add reference(新增參考)在以前的畫面真的是慘不忍睹,想要在 GAC內加入 ReportViewer.WebForms 參考,得在幾百個 dll 內挑選。現在呢,就好像 Windows 7 的搜尋一樣。如下圖。

image

Highlight Current Line

寫程式的時候,常常會發現不知道游標(Cursor)跑到哪裡去了,尤其螢幕愈大愈寬後,這種情況就愈嚴重。Hightlight current line 的功能會突顯出現在游標所在的行,就比較好找了。

image

Fix Mixed Tabs

縮排時,到底應該使用 Tab鍵,還是多個空白鍵(spaces)呢?都可以,開發人員統一即可。但到底這一行的縮排是 tab, 還是 space 呢?該怎麼修正呢?這個功能只需要一個 click 即可。

image

Triple Click

連續按Click三下,可以選一整行。

Move Line Up/Down Commands

按 Alt + Up/Down 可以將一整行上移/下移

結論

我只介紹了部份的功能,完整的功能,請參考Visual Studio 2010 Pro Power Tools。原本的快速鍵最好也背起來。有些重複的功能,參考著用即可。例如 Ctrl + Click ,可以Go To Definition,其實與F12鍵是相等的,看當時你的右手是握著滑鼠還是放在鍵盤上。Pro Power Tools 增加了許多的功能,解決一些開發時的困擾,非常建議安裝。

2010年6月4日 星期五

圖形驗證(4): 使用 Generic Handler 產生圖形

這個系列,請見
(1) 簡介
(2): ASP.NET 簡易實作圖形驗證
(3): ASP.NET 圖形加強版

上一篇加強了圖形後,發現了一個美中不足的部份。圖形是先產了圖檔後,再以 asp:image 元件指到該圖檔,讓使用者可以看圖猜字。但這樣長久下來,會讓圖檔愈來愈多,總有一天會讓硬碟爆掉。雖然可以定下MIS規則讓管理人員定期手動清理,但人是健忘的動物,還是有一天會忘的。

這一次,我們就來加強一下這個部份。

Generic Handler

我們將使用 Generic Handler來解決暫存圖檔的問題。首先新增一個  Captcha.ashx 到網站中。

image

由於在 ashx 中,我們要使用 Session,故必須實作 IRequiresSessionState,如下

public class Captcha : IHttpHandler, IRequiresSessionState 
{

public void ProcessRequest(HttpContext context)
{
  context.Response.ContentType = "image/gif";
  VerifyNow(context);
}

public bool IsReusable
{
  get
  {
    return false;
  }
}

接下來,經過重構之後,之前大部份關於Captcha 圖形驗證的程式都可以搬到 Captcha.ashx.cs 下了。程式如下

Captcha.ashx.cs

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.Web;
using System.Web.SessionState;

namespace Captcha.Utility
{
  /// <summary>
  /// Summary description for Captcha
  /// </summary>
  public class Captcha : IHttpHandler, IRequiresSessionState 
  {

    public void ProcessRequest(HttpContext context)
    {
      context.Response.ContentType = "image/gif";
      VerifyNow(context);
    }

    public bool IsReusable
    {
      get
      {
        return false;
      }
    }

    private void VerifyNow(HttpContext context)
    {
      string randomString = context.Session["ImgText"] as string;
      CreateImage(context, randomString);
    }

    

    private void CreateImage(HttpContext context, string imageText)
    {
      //建立一個 Bitmap。寬高未定,故先給定1, 1
      Bitmap bmpImage = new Bitmap(1, 1);

      //指定字型
      Font font = new Font("Verdana", 24, FontStyle.Bold, GraphicsUnit.Point);

      //進行繪圖。繪圖需透過 Grahpics 物件。
      Graphics graphics = Graphics.FromImage(bmpImage);
      //測量文字的寬高
      int width = (int)graphics.MeasureString(imageText, font).Width;
      int height = (int)graphics.MeasureString(imageText, font).Height;

      //重新指定 Bitmap
      bmpImage = new Bitmap(bmpImage, new Size(width, height));

      //重新繪圖
      Random r = new Random();
      graphics = Graphics.FromImage(bmpImage);
      graphics.Clear(r.NextColor()); //使用亂數底色
      graphics.TextRenderingHint = TextRenderingHint.AntiAlias;  //不要鋸齒

      int avgWidth = width / imageText.Length;
      for (int i = 0; i < imageText.Length; i++)
      {
        font = new Font("Verdana", r.Next(12, 24), FontStyle.Bold, GraphicsUnit.Point);
        graphics.DrawString(imageText.Substring(i, 1), font, new SolidBrush(r.NextColor()), avgWidth * i, 0); //把文字畫上去
      }

      graphics.Flush();

      for (int i = 0; i < 10; i++)
        DrawRandomLine(graphics, height, width, r);

      bmpImage.Save(context.Response.OutputStream, ImageFormat.Gif);

      //記得 release 記憶體
      graphics.Dispose();
      font.Dispose();
      bmpImage.Dispose();
    }

    private static Color NextColor(Random r)
    {
      return Color.FromArgb(r.Next(255), r.Next(255), r.Next(255));
    }

    private static void DrawRandomLine(Graphics graphics, int height, int width, Random random)
    {
      Pen pen = new Pen(random.NextColor());
      pen.Width = random.Next(3);
      Point p1 = new Point(random.Next(width), random.Next(height));
      Point p2 = new Point(random.Next(width), random.Next(height));
      graphics.DrawLine(pen, p1, p2);
    }
  }
}
網頁的部份,就只剩下取得密碼及驗證的部份
Sample1.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Sample1.aspx.cs" Inherits="Captcha.Sample1" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
      <asp:Image runat="server" ID="image" ImageUrl="~/Utility/Captcha.ashx" />
      <asp:TextBox runat="server" ID="txtAnswer" />
      <asp:Button Text="輸入" runat="server" ID="btnSubmit" onclick="btnSubmit_Click" />
    </div>
    </form>
</body>
</html>

Sample1.aspx.cs

using System;
using System.Text;
using System.Web.UI;

namespace Captcha
{
  public partial class Sample1 : System.Web.UI.Page
  {
    protected void Page_Init(object sender, EventArgs e) 
    {
      if (!Page.IsPostBack)
      {
        CreateImageText();
      }
    }

    protected void btnSubmit_Click(object sender, EventArgs e)
    {
      string randomString = Session["ImgText"] as string;
      if (randomString != txtAnswer.Text.Trim())
      {
        Response.Write("驗證錯誤");
        CreateImageText();
      }
      else
        Response.Write("驗證成功");
    }

    private void CreateImageText()
    {
      string randomString = GetRandomString(4);
      Session["ImgText"] = randomString;
    }

    private string GetRandomString(int length)
    {
      char[] chars = @"23456789ABCDEFGHIJKLMNPQRSTUVWXYZ".ToCharArray();
      Random r = new Random((int)DateTime.Now.Ticks);
      StringBuilder sb = new StringBuilder(length);
      for (int i = 0; i < length; i++)
        sb.Append(chars[r.Next(chars.Length)]);
      return sb.ToString();
    }
  }
}

結論

經過這樣一重構,ashx 的可重用性變高了,也不再需要圖片的暫存檔。

範例下載

圖形驗證(3): ASP.NET 圖形加強版

(1) 簡介(2): ASP.NET 簡易實作圖形驗證後,這次針對圖形太簡單的問題再做加強。

加強1

底色換成亂數取色。為了亂數取色,我寫了一個 extension method,延伸了 Random

using System;
using System.Drawing;

namespace Captcha.Utility
{
  public static class RandomExtension
  {
    /// <summary>
    /// 取得下一個顏色
    /// </summary>
    /// <param name="random"></param>
    /// <returns></returns>
    public static Color NextColor(this Random random)
    {
      return Color.FromArgb(random.Next(255), random.Next(255), random.Next(255));
    }
  }
}

因此,可以在換底色時,改成亂數取色。

//重新繪圖
Random r = new Random();
graphics = Graphics.FromImage(bmpImage);
graphics.Clear(r.NextColor()); //使用亂數底色
graphics.TextRenderingHint = TextRenderingHint.AntiAlias;  //不要鋸齒

加強2

讓字型大小也能亂數忽大忽小,並且就亂數取色,我將部份程式修改如下

int avgWidth = width / imageText.Length;
for (int i = 0; i < imageText.Length; i++)
{
font = new Font("Verdana", r.Next(12, 24), FontStyle.Bold, GraphicsUnit.Point);
graphics.DrawString(imageText.Substring(i, 1), font, new SolidBrush(r.NextColor()), avgWidth * i, 0); //把文字畫上去
}

加強3

最後,我再加強甘擾,讓機器人更難以進行 AntiCaptcha,我畫了10條粗細長度顏色不一的線。程式如下

private static void DrawRandomLine(Graphics graphics, int height, int width, Random random)
{
  Pen pen = new Pen(random.NextColor());
  pen.Width = random.Next(3);
  Point p1 = new Point(random.Next(width), random.Next(height));
  Point p2 = new Point(random.Next(width), random.Next(height));
  graphics.DrawLine(pen, p1, p2);
}
...
for (int i = 0; i < 10; i++)
        DrawRandomLine(graphics, height, width, r);

結果

以下是這三個步驟加強後的結果.還不錯呢!

image

結論

看起來不錯,但仍有些美中不足的部份。例如字的顏色如果很接進底色的話,使用者就看不出來了。此時必須讓使用者自行換圖,或者想辦法讓兩者的顏色差異變大。這些小加強就請讀者自行發揮了。

範例程式下載

2010年6月3日 星期四

圖形驗證(2): ASP.NET 簡易實作圖形驗證

接續上一篇的簡介,我們這一次實作一個簡易的 ASP.NET 的圖現驗證。

步驟1: 顯示圖形

一般來說,圖形只能給人工來閱讀,SpamRobot 碰到圖形當然較吃力。這個步驟,我們就先把文字變成最簡單的圖形吧。

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Text;
using System.Web.UI;

namespace Captcha
{
  public partial class Sample1 : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {
      if (!Page.IsPostBack)
      {
        CreateImage("I wanna go home");
      }
      image.ImageUrl = ImageUrl;
    }

    //圖檔放置的位置
    private string ImagePath
    {
      get
      {
        return Server.MapPath(ImageUrl);
      }
    }
    //圖檔的 Url。使用 SessionID 避免重複
    private string ImageUrl
    {
      get
      {
        return "~/imgs/" + Session.SessionID + ".gif";
      }
    }

    private void CreateImage(string imageText)
    {
      //建立一個 Bitmap。寬高未定,故先給定1, 1
      Bitmap bmpImage = new Bitmap(1, 1);

      //指定字型
      Font font = new Font("Verdana", 24, FontStyle.Bold, GraphicsUnit.Point);

      //進行繪圖。繪圖需透過 Grahpics 物件。
      Graphics graphics = Graphics.FromImage(bmpImage);
      //測量文字的寬高
      int width = (int)graphics.MeasureString(imageText, font).Width;
      int height = (int)graphics.MeasureString(imageText, font).Height;

      //重新指定 Bitmap
      bmpImage = new Bitmap(bmpImage, new Size(width, height));

      //重新繪圖
      graphics = Graphics.FromImage(bmpImage);
      graphics.Clear(Color.White); //使用白底
      graphics.TextRenderingHint = TextRenderingHint.AntiAlias;  //不要鋸齒
      graphics.DrawString(imageText, font, new SolidBrush(Color.Red), 0, 0); //把文字畫上去
      graphics.Flush();

      bmpImage.Save(ImagePath, ImageFormat.Gif);

      //記得 release 記憶體
      graphics.Dispose();
      font.Dispose();
      bmpImage.Dispose();
    }
  }
}

而網頁使用了簡單的 asp:image

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Sample1.aspx.cs" Inherits="Captcha.Sample1" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
      <asp:Image runat="server" ID="image" />
    </div>
    </form>
</body>
</html>

在步驟1中,我使用了 Session 作為圖檔的名稱,儲存在網站的目錄下,並以 asp:image 來顯示圖形。

 

步驟2: 亂數

上一個步驟中,驗證的文字是寫固定地。這個步驟我們就來亂一下吧!

 

protected void Page_Load(object sender, EventArgs e)
{
  if (!Page.IsPostBack)
  {
    string randomString = GetRandomString(4);
    CreateImage(randomString);
  }
  image.ImageUrl = ImageUrl;
}

private string GetRandomString(int length)
{
  char[] chars = @"2346789ABCDEFGHIJKLMNPQRTUVWXYZ".ToCharArray();
  Random r = new Random((int)DateTime.Now.Ticks);
  StringBuilder sb = new StringBuilder(length);
  for (int i = 0; i < length; i++)
    sb.Append(chars[r.Next(chars.Length)]);
  return sb.ToString();
}

顯示的文字,不能讓使用者不小心看錯,例如O 與 0,l, i, 與 1,S與5。為了易於使用,必須將這些容易誤解的字元全部移掉。圖形如下

image

步驟3: 驗證

這是最簡單的步驟了。將驗證用的文字儲存起來,放到 Session中,等待使用者的回應並檢查之。

protected void Page_Load(object sender, EventArgs e)
{
  if (!Page.IsPostBack)
  {
    VerifyNow();
  }
}

private void VerifyNow()
{
  string randomString = GetRandomString(4);
  Session["ImgText"] = randomString;
  CreateImage(randomString);
  image.ImageUrl = ImageUrl;
}

protected void btnSubmit_Click(object sender, EventArgs e)
{
  string randomString = Session["ImgText"] as string;
  if (randomString != txtAnswer.Text.Trim())
  {
    Response.Write("驗證錯誤");
    VerifyNow(); //重新產生驗證文字
  }
  else
    Response.Write("驗證成功");
}

結論

這是一個極簡單的CAPTCHA圖形驗證的範例。雖然已經可以運作了,但仍然有不少的缺點,例如可重用性,使用了Session,以及最重要的是「圖形太簡單」的問題。

由於道高一尺魔高一丈的布袋戲真理,一定有 AntiCaptcha 工具的產生,圖形太過於簡單,等同於不設防。另外驗證文字的長度也太短了些,10000次總有一次會猜中,也是個問題。

範例可在這裡下載

圖形驗證(1): 簡介

在網路上無奇不有,什麼都賣,什麼都不奇怪。

為了許多的商業目的,如商品廣告、網站知名度、色情、甚至詐諞、釣魚等網路詐諞等,有時會在各大留言版、部落格、msn 等地方發一些廣告連結。這些文章、訊息通常不能被接受,通稱為惡意文章或訊息。

這些惡意文意如果以人工的方式發文,那當然費日秏時。因此接著發展出機器人,自動發文,甚至自動註冊帳號。免費的 Spam Robots甚至可自行下載。

使用人工來發這些帶有其他目的之訊息,通常不為原網站及網站使用者所喜。為了防止這些不正常目的惡意文章、帳號註冊,常用的防堵方式就是圖形驗證,(英文縮寫為 CAPTCHA),確保的確是由使用者正常的發文,而非機器人。這些圖形(如下圖例),通常希望讓人以目視的方式讀出驗證的通關密碼,防止機器人的攻擊。

然而有攻有防,有圖形驗證,就有破解圖形驗證。PWNtcha 就是一套這樣的工具,並且解釋了各式各樣的 CAPTCHA 及對應的破解率。

個資法通過的今天,圖形驗證已經是必要的加強網站安裝的設計。下一篇會為大家寫一篇 ASP.NET 上CAPTCHA的實作。

2010年6月1日 星期二

資料庫的資料加解密

最近常在搞資安。現在要求一些個資的資料需要在資料表內加密,讓一般的人看不懂。

而在 SQL Server 2005 以上,就內建了加解密的功能。

現在,就建立一個測試的資料庫

CREATE DATABASE [Test] ON  PRIMARY 
( NAME = N'Test', FILENAME = N'C:\db\.mdf' , SIZE = 2048KB , FILEGROWTH = 1024KB )
 LOG ON 
( NAME = N'Test_log', FILENAME = N'C:\db.ldf' , SIZE = 1024KB , FILEGROWTH = 10%)
GO

要加解密之前,需有一些基礎建設,就如同PKI

步驟1

-- 在 database 層級建立 master key
use Test;
GO

CREATE MASTER KEY ENCRYPTION BY
PASSWORD = 'Password1'
GO

步驟2

--建立憑證。會自動使用資料庫層級唯一的 master key
CREATE CERTIFICATE EncryptTestCert
    WITH SUBJECT = 'Test Encrypt'
GO

步驟3

-- 使用憑證建立對稱式金鑰
CREATE SYMMETRIC KEY SymKey
    WITH ALGORITHM = TRIPLE_DES
    ENCRYPTION BY CERTIFICATE EncryptTestCert
GO
最後,就是要加解密資料了。

加解密資料

USE Test
GO
--開啟對稱式金龠
OPEN SYMMETRIC KEY SymKey
DECRYPTION BY CERTIFICATE EncryptTestCert

declare @bin varbinary(256)

-- 對身份證字號加密
set @bin = ENCRYPTBYKEY(KEY_GUID('SymKey'), N'A123456789') 

-- 解密
select @bin
select Convert(nvarchar, DECRYPTBYKEY(@bin))

程式執行結果如下

0x0087FA222A1EE545A4EA753E7B09299801000000147A6F5EEB331D83A9E23024E78DB424319011020A4558E3D5AE13F8758484E3F9628F7464EA098C

(1 row(s) affected)


------------------------------
A123456789

(1 row(s) affected)

加解密的過程,當然可以改成存取資料表的寫法,不過這並不是加解密的重點,讀者可以自行發揮。

結論

資料表內的資料經過上述的操作,可以進行加解密,但是選擇解密欄位的對象,卻要斤斤計較。例如做 foreign key,where 子句的查詢欄位,因為效能的關係,就不適合加密了。

 

TPL 學習日記(1): Start new task

這次要學習 TPL 了. (Task Parallel Library)。當然,還是從最簡單的開始學起。

範例1

static void Main(string[] args)
{
  Task.Factory.StartNew(() => {
    int sum = 0;
    for (int i = 0; i < 100; i++) sum += i;
    Console.WriteLine("總和為{0}",sum);
  });
  Console.WriteLine("完成");
  Console.ReadLine();
}

執行結果

image

這是最簡單的範例了。

Task.Factory.StartNew 起始一個新的 Task 並且開始執行。在大括弧內的為 Task 要執行的程式內容。由於 該 Task 尚未執行完畢,主程式就執行了 Console.WriteLine(“完成”) 故先輸出完成。我們尚未按Enter 時Task 就執行完了,故再輸出 總和為4950。

範例2

這個範例的目的同上一個,只是這次我們想要自行決定 task 何時開始執行。

static void Main(string[] args)
{
  Task task1 = new Task(() =>
  {
    int sum = 0;
    for (int i = 0; i < 100; i++) sum += i;
    Console.WriteLine("總和為{0}", sum);
  });
  task1.Start();
  Console.WriteLine("完成");
  Console.ReadLine();
}

範例3

上述的範例都是在 task 內輸出結果。這次我們想要由  task 內得到結果,但在主程式內輸出。

static void Main(string[] args)
{
  Task<int> task1 = new Task<int>(() =>
  {
    int sum = 0;
    for (int i = 0; i < 100; i++) sum += i;
    return sum;
  });
  task1.Start();
  Console.WriteLine("總和為{0}", task1.Result);
  Console.ReadLine();
}
Task<int> 的<int> 代表回傳值的型別,其結果由 Task.Result 來讀取。

Share with Facebook