ASP.NET 2.0起,web.cofig裡多了connectionStrings區段專門用以儲存資料庫連線字串,同時為避免連線字串中的帳號、密碼等機密資訊曝光,區段內容可以加密方式儲存。例如:

<connectionStrings>
<add name="PlaygroundConnectionString" connectionString="Data Source=(local);Initial Catalog=Playground;Integrated Security=True" providerName="System.Data.SqlClient" />
</connectionStrings>

經加密後會變成:

<EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element"
  xmlns="http://www.w3.org/2001/04/xmlenc#">
  <EncryptionMethod Algorithm="
http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
  <KeyInfo xmlns="
http://www.w3.org/2000/09/xmldsig#">
   <EncryptedKey xmlns="
http://www.w3.org/2001/04/xmlenc#">
    <EncryptionMethod Algorithm="
http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
    <KeyInfo xmlns="
http://www.w3.org/2000/09/xmldsig#">
     <KeyName>Rsa Key</KeyName>
    </KeyInfo>
    <CipherData>
<CipherValue>a44a3giX...略...o+VMsXS8os=</CipherValue>
    </CipherData>
   </EncryptedKey>
  </KeyInfo>
  <CipherData>
<CipherValue>TX5qKv+s...略...3YgrV5wcA==</CipherValue>
  </CipherData>
</EncryptedData>
</connectionStrings>

如此,即便web.config遭竊,拿不到藏在伺服器主機的RSA金鑰,要破解取出帳號密碼資料難如登天。關於加解密web.config區段的做法,微軟MSDN有詳細說明:

但美中不足的是,這些加解密動作目前只能透過aspnet_regiis.exe命令列工具完成,要操作得弄清楚一堆參數。假設今天我們要加密web.config,並部署到三台機器上,全部操作如下:

  1. 用aspnet_regiis -pc "SharedKeys"–exp建立三台機器共用的RSA金鑰容器(Key Container)
  2. 以aspnet_regiis -px "SharedKeys" keys.xml -pri將RSA金鑰容器匯出成XML檔
  3. 將keys.xml複製到三台機器上
  4. 在三台機器上執行aspnet_regiis -pi "SharedKeys" keys.xml滙入RSA金鑰容器
  5. 在三台機器上執行aspnet_regiis -pa "SharedKeys" "NT AUTHORITY\NETWORK SERVICE"授與ASP.NET程式執行權限 (2010-08-30更新: IIS 7.5預設會使用IIS APPPOOL\YourAppPoolName帳號而非NT AUTHORITY\NETWORK SERVICE,詳情可參見保哥的文章,以下應用到授權的地方均比照,不再重複說明。)
  6. 在web.config中加入
    <configProtectedData>
    <providers>
      <add keyContainerName="SharedKey" useMachineContainer="true"
       name="SharedKey" type="System.Configuration.RsaProtectedConfigurationProvider,System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
    </providers>
    </configProtectedData>
  7. 使用aspnet_regiis -pe "connectionStrings" -app "/WebApplication" -prov "SharedKeys"加密連線字串區
  8. 將加密後的web.config部署到三台機器上

以上的步驟挺繁雜的,細節頗多,我一直覺得應該要有輔助工具簡化操作較為人性化,因此我寫了Web Config ConnectionString Ecnryptor!

它是一個小工具程式,說穿了只是提供GUI的方式讓使用者輸入選項,完成原本須透過aspnet_regiis.exe才能達成的web.config加解密及RSA金鑰容器管理功能。

操作介面還蠻直覺的,下拉選單可列出本機上所有的網站應用程式,選擇要處理的web.config,下方視窗就會顯示其內容,按下【編輯】鈕可開啟Notepad進行編輯。若web.config的connectionStrings尚未加密,可按下【加密】鈕對連線字串加密;至於已加密的web.config,【加密】鈕會變成【解密】鈕,也是按一下鈕就可解密,省卻原本複雜的操作。

針對RSA金鑰容器的建立、刪除、匯出、匯入,也一併提供了GUI介面進行管理。

另外,順便練習了一下.NET多語系程式開發,程式目前支援英語與正體中文。

以下說明幾種典型應用情境之操作步驟:

  1. 單一網站主機加密(使用預設加密金鑰)
    a) 由下拉選單選取網站應用程式
    b) 按下【加密】鈕
  2. 單一網站主機加密(指定特定金鑰容器)
    a) 由下拉選單選取網站應用程式
    b) 輸入金鑰容器名稱
    c) 按下【加密】鈕
    (web.config中會新增<configProtectedData><providers>並加入以金鑰容器名稱為名的RsaProtectedConfigurationProvider;若該金鑰容器不存在,系統會自動新增,但自動建立的金鑰容器無法匯出給其他機器共用。如針對Web Farm,請參考下一情境。)
    d) 開啟【管理金鑰容器】功能
    e) 輸入金鑰容器名稱,並按下【授權】鈕
  3. 多台網站主機共用指定RSA金鑰容器加密
    a) 啟動【管理金鑰容器】功能
    b) 輸入金鑰容器名稱,按下【建立】鈕
    c) 按【授權】鈕
    c) 按下【匯出】鈕,另存XML檔案
    d) 將XML檔案複製到要部署的網站主機,在該主機執行Web Config ConnectionString Encryptor,使用【管理金鑰容器】功能,輸入金鑰容器名稱,選取XML檔案後按下【匯入】,並執行【授權】(操作完畢請將XML檔案刪除,防止金鑰外洩)
    e) 在下拉選單選取網站應用程式
    f) 輸入金鑰容器名稱
    g) 按下【加密】鈕
    h) 將web.config複製到要部署的網站主機

我將程式及原始碼放到CodePlex中,歡迎大家參考指教!
(聲明: 雖然我認為本工具原理簡單,出錯機率不高,但各位仍請自行衡量風險決定是否使用,在此恕不對它可能造成的任何損失負責。)

CodePlex: Web Config ConnectionString Encryptor


Comments

# by flash

CodePlex 只有exe 執行檔並沒有您所說的SourceCode, 請問在哪裡? 還是說沒有開放?

# by Jeffrey

to flash, CodePlex頁面上有個Souce Code的頁籤,看起來目前已有30次下載,應該有設定好開放權限了才對。或者你試試這個URL直接連接: http://bit.ly/aSL2h5

# by Will 保哥

建議將授權的對象改成 IIS_WPG ( IIS5,6 ) 或 IIS_IUSRS ( IIS 7,7.5 )

# by aliku

此方式對承租的虛擬主機可行嗎?? 對虛擬主機的實體主機沒有權限直接操作. 所以這樣有解決方式嗎?? 謝謝!!

# by Jeffrey

to aliku, 以上程式需要管理者權限在本機上執行,我想並不適用於Shared Web Hosting(只能透過FTP或Web UI部署程式的那種虛擬主機)... 除了用aspnet_regiis外,.NET 2.0+支援SectionInformation.ProtectSection()方法對web.config加密,只是承租的虛擬網站多半會調低ASP.NET程式的trust level,所以可能需要在section上加註requirePermission="false"。參考: http://bit.ly/alTTA0

# by 不知我這樣子的想法對嗎

如果我用這個軟體加密後 其它人知道我用這樣子的方式加密, 就用這個軟體解密, 這樣子還是會被解開 請問要如何讓別人可以解密, 是否有辦法多加一個密碼參數到加密內, 讓要解開的人要知道我的密碼, 才有辦法解開 感謝

# by Jeffrey

to 不知我這樣子的想法對嗎, 如果被惡意人士取得登入IIS主機的權限,即便不用工具,也可以用aspnet_regiis解密。換句話說,一旦主機的管理權限被偷走,就失去了保護效力。我自己是在一些案子中用自己的演算法加密連線字串,再對web.config加密,等於上了兩層鎖,大致上的精神跟你的額外密碼參數差不多。

# by 小巫

對於加密演算法的學習,不知「黑大」可否提供學習連結, 應該說如何習得這些演算法,並且變成自己的。

# by John

你好 這篇文章與Code又讓我學到了很多 小弟目前有個小案子,剛好客戶需要這樣的工具 請問板大同意讓我提供給客戶使用嗎? (我會在保留UI上的作者..)

# by Jeffrey

to John, 這個工具我是以Open Source方式分享,歡迎隨意使用,不過因為你是要用在實務專案中,還是不免重提一下免責聲明: 我跟所有開源專案及部落格文章作者一樣,對於使用本站文章、範例程式或工具所可能造成的任何損失概不負責,風險的部分要自行評估與承擔就是了~~ (記得先備份會保險一些)

# by John

感謝板大的慷慨精神~ 我會小心使用的

# by stn

to # Jeffrey 首先感謝分享,剛好工作上遇到個廠商開發的程式, 在不改連線方式可以簡單加密是真的很有幫助. 但是我遇到個怪問題,下拉選單有部份網站無法看到. 有改授權對象 IIS_WPG ( IIS5,6 ) 也是不行.. 有查了一下程式,可能是 string path = String.Format("IIS://{0}/W3SVC", ip);的問題, 因為我有把預設站台拿掉,自己再新增站台. 如果要改成抓iis 上所有網站,不知該如何修改. 抱歉對這塊不熟,尚請高手指導

# by Jeffrey

to stn, 倒沒遇過這種狀況,預設站台會是//yourIp/W3SVC/1/*,新增站台在清單中也應以//yourIp/W3SVC/3(或其他數字)/*的方式出現才對。關於"部分網站無法看到",這些消失的網站有什麼共同特徵? 與下拉選單中出現的站台有沒有什麼差異?

# by 周阿中

To stn: 您是否遇到某些虛擬目錄沒有在這支程式裡的列表出現? 我今天發現問題在哪了,您是否是在站台裡面某個普通目錄按右鍵選內容,再按"建立"的方式來將該目錄變成虛擬目錄? 若是這樣的方式就不會在列表出現喔! 必須手動在站台建立虛擬目錄,(虛擬目錄名稱要與原本目錄名稱相同,這樣才不會同一個目錄有兩個入口),這樣才會出現在這支程式的列表裡面~ 今天也是研究了很久,然後慢慢地將許多虛擬目錄重建...@@

# by Wang

to # Jeffrey 執行工具後...出現未知的錯誤 System.Runtime.InteropServices.COMException (0x80005000) ... 不知是哪裡需調整

# by Jeffrey

to Wang, 由此一線索不易推估原因。程式有提供原始碼,能否指出錯誤發生的程式碼來源? 應能更明確鎖定問題方向。

# by wong

Firstly, thanks so much for sharing your talents and works. It is a great tool you have created. I'm sorry for not being able to type in Chinese, even though I can read it. I cannot find the source code of the newer v0.95 in CodePlex. When I run the v0.90a source on VS2010 on Win7, I have the error of: "Exception has been thrown by the target of an invocation" at the Application.Run(new Form1()); in the Program.cs file. I think I didn't see this error before when I ran it on another PC. Not sure why I encounter this now. How can I fix this, please? One suggestion, and I don't know if it's possible to change, is instead of looking for the Web.Config file from the IIS server, can it just browse the Web.Config file from a folder we select instead? This would be much easier for us to use during Dev especially as we do not have to install the Web App to IIS, because we may run it directly within VS (Visual Studio) Thanks much again.

# by Jeffrey

to wong, 感謝您的肯定。 關於"Exception has been thrown by the target of an invocation",建議檢查出錯時的Exception.InnerException,可能會有較明確的錯誤資訊。 v0.95版只多加入當年customError資安漏洞的警示,稍後ASP.NET Team已修復該問題,故0.95版已無價值,原則上這個專案仍以v0.9的Source為準,至於修改成在無IIS模式下瀏覽web.config進行修改,技術上或許可行,但這個工具的目標在簡化IIS設定步驟,也需與實際運作主機加密容器搭配,故預期不會為此改版,但您可試著修改原始程式碼符合您的需求。

# by wong

Thanks Jeffrey for your prompt reply. There is no other information or msg about this Exception. I clicked View Detial link under the Msg box when this occurs, but the detail msg is exactly the same. I ran it as Admin already. I wish I can send you the screen shot, but again the msg is the same: "Exception has been thrown by the target of an invocation". It happens when the App is being loaded at the line: Application.Run(new Form1()); I cannot do anything about it. Maybe could you try and see if it works on Win7 and VS2010, please? I tried the EXE on Win XP and it still has the same problem. Thanks. Oh, I found the way to copy and paste the very detail level as below: ------------------------------------------------------------------------------------- System.Reflection.TargetInvocationException was unhandled Message=Exception has been thrown by the target of an invocation. Source=mscorlib StackTrace: at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner) at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner) at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks) at System.Delegate.DynamicInvokeImpl(Object[] args) at System.Windows.Forms.Control.InvokeMarshaledCallbackDo(ThreadMethodEntry tme) at System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(Object obj) at System.Threading.ExecutionContext.runTryCode(Object userData) at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Windows.Forms.Control.InvokeMarshaledCallback(ThreadMethodEntry tme) at System.Windows.Forms.Control.InvokeMarshaledCallbacks() at System.Windows.Forms.Control.WndProc(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m) at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam) at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg) at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData) at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context) at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context) at System.Windows.Forms.Application.Run(Form mainForm) at WebConfigEncryptor.Program.Main() in C:\Dev\Encryption\EncryptWebConfigTool4MutliServers_WithSrcFromCodePlex\WebConfigEncryptor_v0.9a\Src\webConfigenc-25482\Program.cs:line 18 at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args) at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() at System.Threading.ThreadHelper.ThreadStart_Context(Object state) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart() InnerException: System.Reflection.TargetInvocationException Message=An exception occurred during the operation, making the result invalid. Check InnerException for exception details. Source=System StackTrace: at System.ComponentModel.AsyncCompletedEventArgs.RaiseExceptionIfNecessary() at System.ComponentModel.RunWorkerCompletedEventArgs.get_Result() at WebConfigEncryptor.Form1.wkrQuery_RunWorkerCompleted(Object sender, RunWorkerCompletedEventArgs e) in C:\Dev\Encryption\EncryptWebConfigTool4MutliServers_WithSrcFromCodePlex\WebConfigEncryptor_v0.9a\Src\webConfigenc-25482\Form1.cs:line 49 at System.ComponentModel.BackgroundWorker.OnRunWorkerCompleted(RunWorkerCompletedEventArgs e) at System.ComponentModel.BackgroundWorker.AsyncOperationCompleted(Object arg) InnerException: System.Runtime.InteropServices.COMException Message=Unknown error (0x80005000) Source=System.DirectoryServices ErrorCode=-2147463168 StackTrace: at System.DirectoryServices.DirectoryEntry.Bind(Boolean throwIfFail) at System.DirectoryServices.DirectoryEntry.Bind() at System.DirectoryServices.DirectoryEntry.get_IsContainer() at System.DirectoryServices.DirectoryEntries.ChildEnumerator..ctor(DirectoryEntry container) at System.DirectoryServices.DirectoryEntries.GetEnumerator() at IISDataHelper.exploreTree(DirectoryEntry de, XElement xe, Action`1 cb) in C:\Dev\Encryption\EncryptWebConfigTool4MutliServers_WithSrcFromCodePlex\WebConfigEncryptor_v0.9a\Src\webConfigenc-25482\IISDataHelper.cs:line 30 at IISDataHelper.ReadSettings(String ip, String uid, String pwd, Action`1 progressCallback) in C:\Dev\Encryption\EncryptWebConfigTool4MutliServers_WithSrcFromCodePlex\WebConfigEncryptor_v0.9a\Src\webConfigenc-25482\IISDataHelper.cs:line 20 at WebConfigEncryptor.Form1.wkrQuery_DoWork(Object sender, DoWorkEventArgs e) in C:\Dev\Encryption\EncryptWebConfigTool4MutliServers_WithSrcFromCodePlex\WebConfigEncryptor_v0.9a\Src\webConfigenc-25482\Form1.cs:line 31 at System.ComponentModel.BackgroundWorker.OnDoWork(DoWorkEventArgs e) at System.ComponentModel.BackgroundWorker.WorkerThreadStart(Object argument) InnerException:

# by Jeffrey

to wong, 感謝提供詳細錯誤資訊,似乎也呼應了早先另一位網友Wang在2013/2/5反應的System.Runtime.InteropServices.COMException (0x80005000)問題。 由錯誤訊息推測問題與IIS ADSI Provider 安裝有關,stackoverflow上有一則相關討論(http://stackoverflow.com/questions/507519/crashing-with-c-sharp-and-directory-services-on-xp),有網友回報在重裝IIS後問題排除,希望這點資訊對你有幫助。

# by wong

(I submitted the reply last night and don't see it posted for some reasons. So I try to retype it again.) Thanks Jeffrey again for the tip. After installing IIS again with more options checked, it works! I checked most of the items under Security and the others under other main branches though. I don't know which ones fixed this issue. Now when I run it, I have couple questions: 1. What is the Key Container Name that text box that we can leave it empty? 2. Where / how can I set a password when I want to encrypt the ConnectionString section using RSA? Thanks so much again.

# by Jeffrey

to Wong, ASP.NET加密web.config的做法是使用RSA非對稱金鑰,這些加解密金鑰被保存在金鑰容器中,ASP.NET知道如何取出使用,加解密時不需要再額外提供密碼。系統有一個預設的金鑰容器,當工具介面的Key Container Name留白不指定時,就會用預設的金鑰加解密web.config;另外你也可以選擇不要用預設的金鑰,自行建立額外的金鑰容器使用,此時就要填入自訂金鑰容器的名稱。相關細節可參考文中的應用情境說明。

# by Gpx1981

你好: 真不好意思翻出這麼舊的文章 目前在匯出金鑰時出現問題 建立金鑰成功,嘗試再次建立也說已存在 但無法匯出跟刪除 希望能指引一條明路QQ Thanks, c:\Windows\Microsoft.NET\Framework\v2.0.50727>aspnet_regiis -pc "SharedKeys"–exp 正在建立 RSA 金鑰容器... RSA 金鑰容器已經存在。 失敗! c:\Windows\Microsoft.NET\Framework\v2.0.50727>aspnet_regiis -px "SharedKeys" keys.xml -pri 正在將 RSA 金鑰匯出至檔案... 找不到 RSA 金鑰容器。 失敗! c:\Windows\Microsoft.NET\Framework\v2.0.50727>aspnet_regiis -pz "SharedKeys" 正在刪除 RSA 金鑰容器... 找不到 RSA 金鑰容器。 失敗!

# by Jeffrey

to Gpx1981, 懷疑是權限問題,會不會是沒有用管理者權限啟動DOS視窗?(右鍵選單「以系統管理員身分執行」)

# by Gpx1981

to Jeffrey,確定是使用系統管理者執行。而且執行加解密是沒有問題的,所以很難理解會出現找不到的錯誤訊息... https://drive.google.com/file/d/0BxF_lJkwu-GtbW8yUGc5VUsxUUU/edit?usp=sharing

# by Gpx1981

抱歉,上篇留言的加解密是使用預設金鑰成功,使用"SharedKeys"還是找不到 Sorry! aspnet_regiis.exe -pef "connectionStrings" D:\_workspace_ms\CMIS_R -prov "SharedKeys" 正在加密組態區段... 找不到保護提供者 'SharedKeys'。 失敗!

# by Jeffrey

to Gpx1981, 案例挺罕見的,不太有頭緒,我能想到的只有試著檢查金鑰容器檔案看是否有進一步的線索。(參考:http://security.stackexchange.com/questions/1771/how-can-i-enumerate-all-the-saved-rsa-keys-in-the-microsoft-csp http://msdn.microsoft.com/en-us/library/ff650304.aspx [Machine Key Container & User Key Container一節])

# by David Kuo

問題似乎在「aspnet_regiis -pc "SharedKeys"–exp」中 exp 指令前的符號好像不太一樣。

# by Rico

CodePlex is shutting down. 黑大有沒有考慮放上GitHub?

# by Jeffrey

to Rico, 沒想到還有人記得它,呵。已移轉: https://github.com/darkthread/WebConfigCnStrEncryptor

# by 小子

請問黑哥,這個對於一般應用程式的App.config 是否同樣適用?

# by Jeffrey

to 小子,連線字串加解密機制對app.config也適用,但此工具是為web.config設計的,可以管理金鑰,但不能拿來修改exe.config。

# by Arcelibs

來回應一下,用vs2019打開後也是會抱錯,後續直接拉專案檔到4.6 Windows Server 2016上必須安裝IIS6 相容性組件,且使用administrator執行方可使用,謝謝黑大

# by Sam

請問 黑大,這個範例的程式碼有上到git 嗎? 因為最近也在處理 RSA對config加解密的設定,剛好看到您的文章,但因為年代久遠,連結已經失效

# by Jeffrey

to Sam, 在這裡 https://github.com/darkthread/WebConfigCnStrEncryptor

# by Sam

感謝 黑大,我參考看看您的做法,再研究一下,這陣年底工作多,要還的技術債也很多

Post a comment