依一般資安準則,在設定檔使用明碼儲存連線字串是不被允許的,連線字串加密是基本要求。
雖然用反組譯.NET組件破解加密字串不是什麼難事,但駭客至少得先找到檔案拿到組件檔(DLL)才能動手,相較之下明碼連線字串就簡單多了,用關鍵字掃掃硬碟、備份媒體就能抓出一大把,不費吹灰之力蒐集到資料庫帳號密碼,很可怕吧!
ASP.NET 2.0起,web.config內建connectionStrings加密功能,但它綁死機器,必須解決Web Farm機器間的同步,且只能透過aspnet_regiis工具程式操作,部署管理較為麻煩。因此,我還是偏好自己處理連線字串加密。
ADO.NET時代,SqlConnection/OracleConnection由自己建立,連線字串怎麼取也由程式決定,要加密很簡單。到EF時代,連線字串由元件背後的機制處理,new BlahEntities()就連上資料庫做事,開發者不需要也不容易介入取得連線字串的過程。早期的EF版本還有個可傳入連線字串的DbContext建構式,到後來,DbContext只剩一個無參數的公開建構式,使用時想指定連線字串都沒辦法。
所幸,這問題不難克服,利用partial class技巧在專案裡多加個BBDPEntities.cs(記得namespace要跟EF的BBDPEntities相同),補上吃連線字串參數的建構式,搞定。
namespace BBDPWeb.Models
{
publicpartialclass BBDPEntities
{
public BBDPEntities(string cnStr) : base(cnStr)
{
}
}
}
加密解密演算法很多,就連線字串而言,不必動用什麼高深莫測堅不可破的演算法,避免帳號密碼被人一眼看穿就夠。以下是簡單的DES加解密函式範例:
//很陽春的加解密函式,改寫自http://goo.gl/sos1J
//程式以示意為主,只適用小型字串處理,未包含參數檢核,防錯邏輯
publicclass MyCipher
{
byte[] rgbKey = newbyte[8];
byte[] rgbIv = newbyte[8];
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
public MyCipher(string key)
{
var hash = MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(key));
Array.Copy(hash, 0, rgbKey, 0, 8);
Array.Copy(hash, 8, rgbIv, 0, 8);
}
publicstring Encrypt(string rawString)
{
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms,
des.CreateEncryptor(rgbKey, rgbIv), CryptoStreamMode.Write))
{
byte[] buff = Encoding.UTF8.GetBytes(rawString);
cs.Write(buff, 0, buff.Length);
}
return Convert.ToBase64String(ms.ToArray());
}
}
publicstring Decrypt(string encString)
{
using (var ms = new MemoryStream(Convert.FromBase64String(encString)))
{
using (var cs = new CryptoStream(ms,
des.CreateDecryptor(rgbKey, rgbIv), CryptoStreamMode.Read))
{
using (var sr = new StreamReader(cs))
{
return sr.ReadToEnd();
}
}
}
}
}
為DbContext增加了傳入連線字串的建構式,也準備好解密函式,接著只需將建立DbContext的程序改成:由設定檔讀取加密後的連線字串、解密、使用連線字串建立DbContext,如此就做到連線字串加密,提升系統安全性。另外奉送一則我常用的小技巧:啟用加密後,修改或比對連線主機、帳號、密碼設定會變得很麻煩,而這在測試開發階段屬於不必要的負擔。故我習慣讓程式聰明一點,遇到連線字串是明碼就直接用,若是加密版就解開再用,明碼加密兩相宜,「測試開發用明碼,正式上線再加密」,魚與熊掌兼得。加入一點關鍵字比對,就可以實現這點。
static MyCipher cipher = new MyCipher("加密金鑰");
//使用統一的靜態函式建立DbContext物件,避免自行建構
publicstatic BBDPEntities CreateDbContext()
{
//設定檔之連線字串應加密
var cnStr = ConfigurationManager.ConnectionStrings["BBDPEncCnnStr"].ConnectionString;
//自動偵測,支援加密及未加密的連線字串,測時不加密,上線時再加密
//在連線字串找到metadata字樣表示為加密字串
if (!cnStr.Contains("metadata"))
{
cnStr = cipher.Decrypt(cnStr);
}
var ent = new BBDPEntities(cnStr);
return ent;
}
要加密連線字串一樣靠MyCipher,var encCnnStr = cipher.Encrypt("metadata=res://*/BBDP……"),加密結果是一長串Base64編碼,用它替換掉原本的明碼連線字串即可。實務上多半會寫個通用加解密工具,輸入加密Key跟來源字串,按鈕執行加密或解密,用起來比較方便。
把這招學起來,以後別再讓帳號密碼脫光光躺在設定檔做日光浴囉!