在Asp.NET Core中如何優雅的管理用戶機密數據

在Asp.NET Core中如何優雅的管理用戶機密數據

背景

回顧

在軟件開發過程當中,使用配置文件來管理某些對應用程序運行中須要使用的參數是常見的做法。在早期VB/VB.NET時代,常用.ini文件來進行配置管理;而在.NET FX開發中,咱們則傾向於使用web.config文件,經過配置appsetting的配置節來處理;而在.NET Core開發中,咱們有了新的基於json格式的appsetting.json文件。html

不管採用哪一種方式,其實配置管理歷來都是一件看起來簡單,但影響很是深遠的基礎性工做。尤爲是配置的安全性,貫穿應用程序的始終,若是沒能作好安全性問題,極有可能會給系統帶來不可控的風向。git

源代碼比配置文件安全麼?

有人覺得把配置存放在源代碼中,可能比存放在明文的配置文件中彷佛更安全,實際上是「皇帝的新裝」。github

在前不久,筆者的一位朋友就跟我說了一段故事:他說一位同事在離職後,直接將曾經寫過的一段代碼上傳到github的公共倉庫,而這段代碼中包含了某些涉及到原企業的機密數據,還好被github的安全機制提早發現而及時終止了該行爲,不然後果不堪設想。web

因而,筆者順手查了一下因爲有意或無心泄露企業機密,形成企業損失的案例,發現還真很多。例如大疆前員工經過 Github 泄露公司源代碼,被罰 20 萬、獲刑半年 這起案件,也是一個典型的案例。算法

該員工離職後,將包含關鍵配置信息的源代碼上傳到github的公共倉庫,被黑客利用,使得大量用戶私人數據被黑客獲取,該前員工最終被刑拘。 大疆前員工經過Github泄露公司源代碼,被罰20萬、獲刑半年數據庫

圖片來源: http://www.digitalmunition.com/WhyIWalkedFrom3k.pdfjson

大部分IT公司都會在入職前進行背景調查,而一旦有案底,可能就已經與許多IT公司無緣;即使是成爲創業者,也可能面臨沒法跟不少正規企業合做的問題。c#

小結

因此,安全性問題不容小覷,哪怕時間再忙,也不要急匆匆的就將數據庫鏈接字符串或其餘包含敏感信息的內容輕易的記錄在源代碼或配置文件中。在這個點上,一旦出現問題,每每都是很是嚴重的問題。windows

如何實現

在.NET FX時代,咱們可使用對web.config文件的關鍵配置節進行加密的方式,來保護咱們的敏感信息,在.NET Core中,天然也有這些東西,接下來我將簡述在開發環境和生產環境下不一樣的配置加密手段,但願可以給讀者帶來啓迪。api

開發環境

在開發環境下,咱們可使用visual studio 工具提供的用戶機密管理器,只需0行代碼,便可輕鬆完成關鍵配置節的處理。

機密管理器概述

根據微軟官方文檔 的描述:

ASP.NET Core 機密管理器工具提供了開發過程當中在源代碼外部保存機密的另外一種方法 。 若要使用機密管理器工具,請在項目文件中安裝包 Microsoft.Extensions.Configuration.SecretManager 。 若是該依賴項存在而且已還原,則可使用 dotnet user-secrets 命令來經過命令行設置機密的值。 這些機密將存儲在用戶配置文件目錄中的 JSON 文件中(詳細信息隨操做系統而異),與源代碼無關。

機密管理器工具設置的機密是由使用機密的項目的 UserSecretsId 屬性組織的。 所以,必須確保在項目文件中設置 UserSecretsId 屬性,以下面的代碼片斷所示。 默認值是 Visual Studio 分配的 GUID,但實際字符串並不重要,只要它在計算機中是惟一的。

<PropertyGroup>
   <UserSecretsId>UniqueIdentifyingString</UserSecretsId>
</PropertyGroup>

Secret Manager工具容許開發人員在開發ASP.NET Core應用程序期間存儲和檢索敏感數據。敏感數據存儲在與應用程序源代碼不一樣的位置。因爲Secret Manager將祕密與源代碼分開存儲,所以敏感數據不會提交到源代碼存儲庫。但機密管理器不會對存儲的敏感數據進行加密,所以不該將其視爲可信存儲。敏感數據做爲鍵值對存儲在JSON文件中。最好不要在開發和測試環境中使用生產機密。查看引文

存放位置

在windows平臺下,機密數據的存放位置爲:

%APPDATA%\Microsoft\UserSecrets\\secrets.json

而在Linux/MacOs平臺下,機密數據的存放位置爲:

~/.microsoft/usersecrets/<user_secrets_id>/secrets.json

在前面的文件路徑中, ``將替換UserSecretsId.csproj文件中指定的值。

在Windows環境下使用機密管理器

在windows下,若是使用Visual Studio2019做爲主力開發環境,只需在項目右鍵單擊,選擇菜單【管理用戶機密】,便可添加用戶機密數據。

在管理用戶機密數據中,添加的配置信息和傳統的配置信息沒有任何區別。

{
"ConnectionStrings": {
"Default": "Server=xxx;Database=xxx;User ID=xxx;Password=xxx;"
}
}

咱們一樣也可使用IConfiguration的方式、IOptions 的方式,進行配置的訪問。

在非Windows/非Visual Studio環境下使用機密管理器

完成安裝dotnet-cli後,在控制檯輸入

dotnet user-secrets init

前面的命令將在UserSecretsId .csproj 文件的PropertyGroup中添加 .csproj一個元素。 UserSecretsId是對項目是惟一的Guid值。

<PropertyGroup>  
 	<TargetFramework>netcoreapp3.1</TargetFramework>
    <UserSecretsId>79a3edd0-2092-40a2-a04d-dcb46d5ca9ed</UserSecretsId> 
 </PropertyGroup>

設置機密

dotnet user-secrets set "Movies:ServiceApiKey" "12345"

列出機密

dotnet user-secrets list

刪除機密

dotnet user-secrets remove "Movies:ConnectionString"

清除全部機密

dotnet user-secrets clear

生產環境

機密管理器爲開發者在開發環境下提供了一種保留機密數據的方法,但在開發環境下是不建議使用的,若是想在生產環境下,對機密數據進行保存該怎麼辦?

按照微軟官方文檔的說法,推薦使用Azure Key Vault 來保護機密數據,但。。我不是貴雲的用戶(固然,買不起貴雲不是貴雲太貴,而是我我的的問題[手動狗頭])。

其次,與Azure Key Valut相似的套件,例如其餘雲,差很少都有,因此均可覺得咱們所用。

但。。若是您若是跟我同樣,不想經過第三方依賴的形式來解決這個問題,那不如就用最簡單的辦法,例如AES加密。

使用AES加密配置節

該方法與平時使用AES對字符串進行加密和解密的方法並沒有區別,此處從略。

使用數據保護Api(DataProtect Api實現)

在平時開發過程當中,可以動手擼AES加密是一種很是好的習慣,而微軟官方提供的數據保護API則將這個過程進一步簡化,只需調Api便可完成相應的數據加密操做。

關於數據保護api, Savorboard 大佬曾經寫過3篇博客討論這個技術問題,你們能夠參考下面的文章來獲取信息。

ASP.NET Core 數據保護(Data Protection 集羣場景)【上】

ASP.NET Core 數據保護(Data Protection 集羣場景)【中】

ASP.NET Core 數據保護(Data Protection 集羣場景)【下】

(接下來我要貼代碼了,若是沒興趣,請出門左拐,代碼不能完整運行,查看代碼

首先,注入配置項

public static IServiceCollection AddProtectedConfiguration(this IServiceCollection services, string directory)
        {
            services
                .AddDataProtection()
                .PersistKeysToFileSystem(new DirectoryInfo(directory))
                .UseCustomCryptographicAlgorithms(new ManagedAuthenticatedEncryptorConfiguration
                {
                    EncryptionAlgorithmType = typeof(Aes),
                    EncryptionAlgorithmKeySize = 256,
                    ValidationAlgorithmType = typeof(HMACSHA256)
                });
            ;

            return services;
        }

其次,實現對配置節的加/解密。(使用AES算法的數據保護機制)

public class ProtectedConfigurationSection : IConfigurationSection
    {
        private readonly IDataProtectionProvider _dataProtectionProvider;
        private readonly IConfigurationSection _section;
        private readonly Lazy<IDataProtector> _protector;

        public ProtectedConfigurationSection(
            IDataProtectionProvider dataProtectionProvider,
            IConfigurationSection section)
        {
            _dataProtectionProvider = dataProtectionProvider;
            _section = section;

            _protector = new Lazy<IDataProtector>(() => dataProtectionProvider.CreateProtector(section.Path));
        }

        public IConfigurationSection GetSection(string key)
        {
            return new ProtectedConfigurationSection(_dataProtectionProvider, _section.GetSection(key));
        }

        public IEnumerable<IConfigurationSection> GetChildren()
        {
            return _section.GetChildren()
                .Select(x => new ProtectedConfigurationSection(_dataProtectionProvider, x));
        }

        public IChangeToken GetReloadToken()
        {
            return _section.GetReloadToken();
        }

        public string this[string key]
        {
            get => GetProtectedValue(_section[key]);
            set => _section[key] = _protector.Value.Protect(value);
        }

        public string Key => _section.Key;
        public string Path => _section.Path;

        public string Value
        {
            get => GetProtectedValue(_section.Value);
            set => _section.Value = _protector.Value.Protect(value);
        }

        private string GetProtectedValue(string value)
        {
            if (value == null)
                return null;

            return _protector.Value.Unprotect(value);
        }
    }

再次,在使用前,先將待加密的字符串轉換成BASE64純文本,而後再使用數據保護API對數據進行處理,獲得處理後的字符串。

private readonly IDataProtectionProvider _dataProtectorTokenProvider;
public TokenAuthController( IDataProtectionProvider dataProtectorTokenProvider)
{
}
[Route("encrypt"), HttpGet, HttpPost]
public string Encrypt(string section, string value)
{
     var protector = _dataProtectorTokenProvider.CreateProtector(section);
     return protector.Protect(value);
}

再替換配置文件中的對應內容。

{
  "ConnectionStrings": {
    "Default": "此處是加密後的字符串"
  }
}

而後咱們就能夠按照平時獲取IOptions 的方式來獲取了。

問題

公衆號【DotNET騷操做】號主【周杰】同窗提出如下觀點:

一、在生產環境下,使用AES加密,其實依然是一種不夠安全的行爲,充其量也就能忽悠下產品經理,畢竟幾條簡單的語句,就能把機密數據dump出來。

也許在這種狀況下,咱們應該優先考慮accessKeyId/accessSecret,儘可能經過設置多級子帳號,經過受權Api的機制來管理機密數據,而不是直接暴露相似於數據庫鏈接字符串這樣的關鍵配置信息。另外,應該按期更換數據庫的密碼,儘可能將相似的問題可能形成的風險降到最低。數據保護api也提供的相似的機制,使得開發者可以輕鬆的管理機密數據的時效性問題。

二、配置文件放到CI/CD中,發佈的時候在CI/CD中進行組裝,而後運維只是負責管理CI/CD的帳戶信息,而最高機密數據,則由其餘人負責配置。

嗯,我徹底贊成他的第二種作法,另外考慮到因爲運維一樣有可能會有意無心泄露機密數據,因此若是再給運維配備一本《刑法》,並讓他平常補習【侵犯商業祕密罪】相關條款,這個流程就更加閉環了。

結語

本文簡述了在.NET Core中,如何在開發環境下使用用戶機密管理器、在生產環境下使用AES+IDataProvider的方式來保護咱們的用戶敏感數據。因爲時間倉促,若有考慮不周之處,還請各位大佬批評指正。

相關文章
相關標籤/搜索