依一般資安準則,在設定檔使用明碼儲存連線字串是不被允許的,連線字串加密是基本要求。

雖然用反組譯.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
{
    public partial class BBDPEntities
    {
        public BBDPEntities(string cnStr) : base(cnStr)
        {
        }
    }
}

加密解密演算法很多,就連線字串而言,不必動用什麼高深莫測堅不可破的演算法,避免帳號密碼被人一眼看穿就夠。以下是簡單的DES加解密函式範例:

    //很陽春的加解密函式,改寫自http://goo.gl/sos1J
    //程式以示意為主,只適用小型字串處理,未包含參數檢核,防錯邏輯
 
    public class MyCipher
    {
        byte[] rgbKey = new byte[8];
        byte[] rgbIv = new byte[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);
        }
        
        public string 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());
            }
        }
 
        public string 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物件,避免自行建構
        public static 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跟來源字串,按鈕執行加密或解密,用起來比較方便。

把這招學起來,以後別再讓帳號密碼脫光光躺在設定檔做日光浴囉!


Comments

Be the first to post a comment

Post a comment