ASP.NET web.config資料庫連線字串加密與解密

在傳統中ASP.NET,大家習慣用來統一存放SQL資料庫連線帳號密碼的connectionStrings區塊,再透過ConfigurationManager在執行期動態取出的作法,這中間如果引入資訊安全的觀念,就是再也不能採用明碼存放必須將其加密起來。

依據OWASP Developer Guide:

5.4 Implementation Do's and Don'ts
5.4.2 Secure coding
Do not store passwords in code or in configuration files. Use Secrets Manager to store passwords.

依據OWASP Top 10 2021:

A07:2021 Identification and Authentication Failures
Uses plain text, encrypted, or weakly hashed passwords data stores.

這篇文章採用的是RSA(RSAProtectedConfigurationProvider )預設值來進行加解密演算,其實微軟另有提供DPAPI(DPAPIProtectedConfigurationProvider)的實作。

廢話不多說,開始正題吧!

ASP.NET端準備測試的工具程式碼

一般來說我們的web.config比較機敏的資料庫連線字串會長的如下:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <connectionStrings>
    <add name="MyDB" connectionString="server=192.168.1.1;uid=user;pwd=1234;Initial Catalog=Main" providerName="System.Data.SqlClient" />
  </connectionStrings>
</configuration>

然後我們要準備一個輕量可以跑aspx的架構,那當然就是短小精幹的WebForm啦!下列這段程式碼可用來進行後續加解密時期的可行性驗證(是否成功):

<script runat="server">
public void page_load(Object sender, EventArgs e)
{ //從web.config取出連線字串並顯示
  uMsg.Text = System.Configuration.ConfigurationManager.ConnectionStrings["MyDB"].ConnectionString;
}
</script>
<asp:Literal ID="uMsg" runat="server" />

找到作業關鍵工具aspnet_regiis.exe放在哪裡?

正式環境通常可以在下列路徑找到:

C:\Windows\Microsoft.NET\Framework\v4.0.30319

若是在開發環境下,也可以從開始選單到類似這種目錄結構下找到:

C:\Program Files\Microsoft Visual Studio\2022\Professional

因為涉及作業系統的金鑰區塊存取,因此開啟的Console一律以Administrator最高管理者身分運行會比較妥當。

確立金鑰程序過程中使用的名稱

這裡會用到兩個觀念

一、實際存放金鑰的名稱:ContainerName

一般來說都會取名為XX-Key,例如MyKey、PublicKey...,在範例中我們會取名為RSAKey

二、指引金鑰提供者的名稱:ProtectedProvider

一般來說都會取名為XX-ProtectedProvider,在本範例中我們會取名為RSAProvider

⚠ 注意!將金鑰取名為Key後也有可能會引發原始碼檢測軟體的過激反應。

建立RSA金鑰(Protect Create;-pc)

透過下列指令可以建立RSA金鑰,並將其設定成可匯出,金鑰大小為2048-bit。

aspnet_regiis -pc "RSAKey" -size 2048 -exp

⚠ 注意!自2009年RSA-768分機成功後至撰寫文章的當下,全世界依然沒有任何人可以破解RSA-1024。但1024這個關鍵字後可能會引發某些人高潮,若你不想浪費生命在這種事情上,建議拉升到2048-bit

上面的動作,將會在下列目錄建立亂數檔名金鑰,事實上這個目錄是作業系統在保護的目標,你也很難透過檔案複製或刪除去對這個目錄做些什麼。

C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys

指引web.config有哪些Protected Providers可參考

打開web.config,在組態最末端插入下列參考程式碼,記得RSAProviderRSAKey關鍵字要替換成自己適用的字串。

<configuration>
  ...略
  <configProtectedData>
    <providers>
      <add name="RSAProvider" type="System.Configuration.RsaProtectedConfigurationProvider, System.Configuration, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" keyContainerName="RSAKey" useMachineContainer="true" />
    </providers>
  </configProtectedData>
  ...略
<configuration>

⚠ 當我們在web.config插入上列組態程式碼並儲存,但該部主機尚未匯入RSA金鑰(或者、以及)web.config的connectionStrings尚未加密,這兩個因素都不會造成整個網站崩潰,因為這個設定動作只有指引而已,還沒有被正式引用。

將web.config的connectionStrings區段加密(Protect Encryption Fragment;-pef)

透過下列指令可以將web.config的connectionStrings區段,也就是有紀錄資料庫連線字串的connectionString,全部透過RSA技術加密起來。

aspnet_regiis -pef connectionStrings "C:\W3Test" -prov "RSAProvider"

運行後會看到吐出Succeeded!字樣,代表加密成功

Microsoft (R) ASP.NET RegIIS version 4.0.30319.0
Administration utility to install and uninstall ASP.NET on the local machine.
Copyright (C) Microsoft Corporation.  All rights reserved.
Encrypting configuration section...
Succeeded!

回去看web.config的connectionStrings區段已經全部變成亂碼了,其他未被指定的XML節點依然維持正常的明碼狀態。這裡有一個可以關注的重點,就是configProtectionProvider被指定成RSAProvider,也就是在指引web.config該到哪裡找解碼方案

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <connectionStrings configProtectionProvider="RSAProvider">
    <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#aes256-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-oaep-mgf1p" />
          <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
            <KeyName>Rsa Key</KeyName>
          </KeyInfo>
          <CipherData>
            <CipherValue>CeG5IOsmYGIwXpMe7PNR...略</CipherValue>
          </CipherData>
        </EncryptedKey>
      </KeyInfo>
      <CipherData>
        <CipherValue>EVEpb9907d3+GzIbG9+u...原本所有的連線內容都會被編碼到此處...</CipherValue>
      </CipherData>
    </EncryptedData>
  </connectionStrings>
  ...略
</configuration>

⚠ 值得一提的是,aspnet_regiis -pef有防呆設計,如果你很白目地進行加密→加密,事實上並不會怎樣。另外依據實驗我也觀察到似乎在加密的期間有加入時間戳記鹽,當我在沒有變更檔案內容的情況下,進行加密→解密→加密,觀察兩次加密後的Vaule值,會呈現很大的亂數跳動。

⚠ 多數時刻appSettings區塊也會被判定含有大量機敏資訊,如果可以的話也可以考慮加密一下。

將web.config的connectionStrings區段解密(Protect Decryption Fragment;-pdf)

看到加密先別急,使用下列指令就可以將原本的明文解密回來,但是令人不開心的是原本的註解或排版全部都會消失,回到最基礎的XML格式,這部分還蠻無言的。

aspnet_regiis -pdf connectionStrings "C:\W3Test"

解密成功會看到下列訊息:

Microsoft (R) ASP.NET RegIIS version 4.0.30319.0
Administration utility to install and uninstall ASP.NET on the local machine.
Copyright (C) Microsoft Corporation.  All rights reserved.
Decrypting configuration section...
Succeeded!

讓IIS擁有RSA金鑰的存取權限(Protect AccessControlList;-pa)

加密後很開心地再跑去運行一次網站,看到噴出下列錯誤:

Failed to decrypt using provider 'RSAProvider'. Error message from the provider: The RSA key container could not be opened.

這代表你現在運行的IIS所使用的身分,沒有辦法取得RSA金鑰回來解密,因為那個目錄的保護是很嚴格的,因此必須透過下列指令賦予權限。請回到IIS行中的網站,觀察網站運行在哪個應用程式集區(Application Pool)之下,通常預設值是DefaultAppPool

aspnet_regiis -pa "RSAKey" "IIS AppPool\DefaultAppPool"

若設定權限成功,會顯示下列資訊:

Microsoft (R) ASP.NET RegIIS version 4.0.30319.0
Administration utility to install and uninstall ASP.NET on the local machine.
Copyright (C) Microsoft Corporation.  All rights reserved.
Adding ACL for access to the RSA Key container...
Succeeded!

⚠ 如果你是開發端跑IIS Express,那麼不需要經過這個存取權限的設定步驟。若仍然有無法讀取鑰匙方面的問題,建議手動把當下本機運作的USER,加入具有讀取C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys路徑的權限。

匯出RSA金鑰(Protect eXport;-px)

現在的伺服器環境通常不會只有一台機器(就算只有一台機器你也總應該把金鑰匯出來備份,難道電腦都不會掛掉?),我們可以透過下列指令將金鑰以XML格式匯出成檔案,後面記得加上-pri引數,把私鑰一併匯出。

aspnet_regiis -px "RSAKey" "C:\RSAKey.xml" -pri

若匯出成功,可以到C磁碟根目錄下找檔案了。

Microsoft (R) ASP.NET RegIIS version 4.0.30319.0
Administration utility to install and uninstall ASP.NET on the local machine.
Copyright (C) Microsoft Corporation.  All rights reserved.
Exporting RSA Keys to file...
Succeeded!

匯入RSA金鑰(Protect Import;-pi)

本機電腦掛掉或ServerFarm架構下的伺服器群要使用,肯定要進行金鑰匯入系統的過程,輸入下列指令就可以進行RSA金鑰的匯入作業。

aspnet_regiis -pi "RSAKey" "C:\RSAKey.xml"

成功後會出現下列訊息:

Microsoft (R) ASP.NET RegIIS version 4.0.30319.0
Administration utility to install and uninstall ASP.NET on the local machine.
Copyright (C) Microsoft Corporation.  All rights reserved.
Importing RSA Keys from file..
Succeeded!

心得

aspnet_regiis這個工具提供了強大的RSA加解密功能,但他對於某些目錄(中文、空白)名稱的支援性不足,如果在加解密時期出現問題,不妨將web.config複製出來到外部目錄處理。

相關連結

ASP.NET AspNet_RegIIS Web.Config Configuration ConnectionStrings Encode Encryption Decode Decryption SQLConnection Account Password Security