簡體   English   中英

SecureString 在 C# 應用程序中實用嗎?

[英]Is SecureString ever practical in a C# application?

如果我的假設在這里有誤,請隨時糾正我,但讓我解釋一下我為什么要問。

取自 MSDN,一個SecureString

表示應該保密的文本。 文本在使用時加密以保護隱私,並在不再需要時從計算機內存中刪除。

我明白了,通過System.String將密碼或其他私人信息存儲在SecureString是完全有意義的,因為您可以控制它實際存儲在內存中的方式和時間,因為System.String

是不可變的,當不再需要時,不能以編程方式安排垃圾收集; 也就是說,實例在創建后是只讀的,無法預測實例何時會從計算機內存中刪除。 因此,如果 String 對象包含諸如密碼、信用卡號或個人數據之類的敏感信息,則存在信息在使用后可能會泄露的風險,因為您的應用程序無法從計算機內存中刪除數據。

但是,對於 GUI 應用程序(例如,ssh 客戶端),必須從System.String構建SecureString 所有文本控件都使用字符串作為其基礎數據類型

因此,這意味着每次用戶按下一個鍵時,舊字符串都會被丟棄,並且會構建一個新字符串來表示文本框中的值,即使使用密碼掩碼也是如此。 而且我們無法控制何時或是否從內存中丟棄這些值中的任何一個

現在是登錄服務器的時候了。 你猜怎么着? 您需要通過連接傳遞一個字符串以進行身份​​驗證 因此,讓我們將SecureString轉換為System.String .... 現在我們在堆上有一個字符串,無法強制它進行垃圾收集(或將 0 寫入其緩沖區)。

我的觀點是:無論你做什么,沿着這條線的某個地方, SecureString被轉換成System.String ,這意味着它至少會在某個時候存在於堆上(沒有任何垃圾收集保證)。

我的觀點不是:是否有辦法繞過將字符串發送到 ssh 連接,或者繞過讓控件存儲字符串(制作自定義控件)。 對於這個問題,您可以將“ssh 連接”替換為“登錄表”、“注冊表”、“付款表”、“foods-you-would-feed-your-puppy-but-not-your-children 表”,等等。

  • 那么,在什么時候使用SecureString真正變得實用呢?
  • 是否值得花費額外的開發時間來完全消除System.String對象的使用?
  • SecureString的全部意義在於簡單地減少System.String在堆上的時間(降低其移動到物理交換文件的風險)?
  • 如果攻擊者已經擁有進行堆檢查的手段,那么他很可能 (A) 已經擁有讀取擊鍵的手段,或者 (B) 已經物理上擁有機器......所以使用SecureString阻止他獲得數據呢?
  • 這只是“通過默默無聞的安全”嗎?

對不起,如果我把問題問得太厚了,好奇心占了上風。 隨時回答我的任何或所有問題(或告訴我我的假設完全錯誤)。 :)

SecureString實際上有非常實際的用途。

你知道我見過多少次這樣的場景嗎? (答案是:很多!):

  • 密碼意外出現在日志文件中。
  • 在某處顯示密碼 - 一旦 GUI 確實顯示了正在運行的應用程序的命令行,並且命令行由密碼組成。 哎呀
  • 使用內存分析器與您的同事一起分析軟件。 同事在記憶中看到您的密碼。 聽起來不真實? 一點也不。
  • 我曾經使用過RedGate軟件,它可以在異常情況下捕獲局部變量的“值”,非常有用。 不過,我可以想象它會意外地記錄“字符串密碼”。
  • 包含字符串密碼的故障轉儲。

你知道如何避免所有這些問題嗎? SecureString 它通常可以確保您不會犯這樣的愚蠢錯誤。 它是如何避免的? 通過確保密碼在非托管內存中加密,並且只有當您對自己在做什么有 90% 的把握時才能訪問真正的值。

從某種意義上說, SecureString很容易工作:

1)一切都是加密的

2)用戶調用AppendChar

3)解密UNMANAGED MEMORY中的所有內容並添加字符

4) 再次加密 UNMANAGED MEMORY 中的所有內容。

如果用戶可以訪問您的計算機怎么辦? 病毒是否能夠訪問所有SecureStrings 是的。 你需要做的就是在內存被解密的時候把自己掛在RtlEncryptMemory ,你會得到未加密的內存地址的位置,並讀出它。 瞧! 事實上,您可以制作一種病毒,它會不斷掃描SecureString使用情況並記錄所有使用它的活動。 我並不是說這將是一項容易的任務,但它是可以完成的。 如您所見,一旦您的系統中存在用戶/病毒, SecureString的“強大”就完全消失了。

你在你的帖子中有幾點。 當然,如果您使用一些在內部保存“字符串密碼”的 UI 控件,那么使用實際的SecureString就沒有那么有用了。 盡管如此,它仍然可以防止我上面列出的一些愚蠢行為。

此外,正如其他人所指出的,WPF 支持 PasswordBox,它通過其SecurePassword屬性在內部使用SecureString

底線是; 如果您有敏感數據(密碼、信用卡等),請使用SecureString 這就是 C# 框架所遵循的。 例如, NetworkCredential類將密碼存儲為SecureString 如果你看看這個,你會發現SecureString .NET 框架中有超過 80 多種不同的用法。

在很多情況下,您必須將SecureString轉換為字符串,因為某些 API 需要它。

通常的問題是:

  1. API 是通用的。 它不知道有敏感數據。
  2. API 知道它正在處理敏感數據並使用“字符串”——這只是糟糕的設計。

您提出了一個好觀點:當SecureString轉換為string時會發生什么? 這只能因為第一點而發生。 例如,API 不知道它是敏感數據。 我個人沒有看到這種情況發生。 從 SecureString 中取出字符串並不是那么簡單。

這並不簡單,原因很簡單 從來沒有打算讓用戶將 SecureString 轉換為字符串,正如您所說:GC 將啟動。如果您看到自己這樣做,您需要退后一步問問自己:我為什么要這樣做,或者我真的需要這,為什么?

我看到了一個有趣的案例。 即,WinApi 函數 LogonUser 以 LPTSTR 作為密碼,這意味着您需要調用SecureStringToGlobalAllocUnicode 這基本上為您提供了位於非托管內存中的未加密密碼。 完成后,您需要立即擺脫它:

// Marshal the SecureString to unmanaged memory.
IntPtr rawPassword = Marshal.SecureStringToGlobalAllocUnicode(password);
try
{
   //...snip...
}
finally 
{
   // Zero-out and free the unmanaged string reference.
   Marshal.ZeroFreeGlobalAllocUnicode(rawPassword);
}

您始終可以使用擴展方法擴展SecureString類,例如ToEncryptedString(__SERVER__PUBLIC_KEY) ,它為您提供使用服務器公鑰加密的SecureStringstring實例。 只有服務器才能解密它。 問題已解決:垃圾收集永遠不會看到“原始”字符串,因為您永遠不會在托管內存中公開它。 這正是PSRemotingCryptoHelper ( EncryptSecureStringCore(SecureString secureString) ) 中EncryptSecureStringCore(SecureString secureString)

作為一些非常相關的東西: Mono SecureString 根本不加密 該實現已被注釋掉,因為..等待它.. “它以某種方式導致 nunit 測試損壞” ,這帶來了我的最后一點:

並非所有地方都支持SecureString 如果平台/架構不支持SecureString ,您將收到異常。 文檔中提供了支持的平台列表。

您的假設中很少有問題。

首先, SecureString 類沒有 String 構造函數。 為了創建一個對象,您分配一個對象,然后附加字符。

在 GUI 或控制台的情況下,您可以非常輕松地將每個按下的鍵傳遞給安全字符串。

該類的設計方式使您不能錯誤地訪問存儲的值。 這意味着您無法直接從中獲取string作為密碼。

因此,例如,要使用它來通過 Web 進行身份驗證,您必須使用同樣安全的適當類。

在 .NET 框架中,您有幾個可以使用 SecureString 的類

  • WPF 的 PasswordBox 控件在內部將密碼保存為 SecureString。
  • System.Diagnostics.ProcessInfo 的 Password 屬性是 SecureString。
  • X509Certificate2 的構造函數采用 SecureString 作為密碼。

(更多的)

總而言之,SecureString 類可能很有用,但需要開發人員給予更多關注。

所有這一切,以及示例,在 MSDN 的SecureString文檔中都有很好的描述

SecureString 在以下情況下很有用:

  • 您逐個構建它(例如從控制台輸入)或從非托管 API 獲取它

  • 您可以通過將其傳遞給非托管 API (SecureStringToBSTR) 來使用它。

如果您曾經將其轉換為托管字符串,那么您就違背了它的目的。

更新以回應評論

... 或者像你提到的 BSTR,這似乎不再安全

將其轉換為 BSTR 后,使用 BSTR 的非托管組件可以將內存歸零。 非托管內存更安全,因為它可以通過這種方式重置。

但是,.NET Framework 中支持 SecureString 的 API 很少,所以您說它在今天的價值非常有限是正確的。

我看到的主要用例是在要求用戶輸入高度敏感的代碼或密碼的客戶端應用程序中。 可以逐個字符地使用用戶輸入來構建 SecureString,然后可以將其傳遞給非托管 API,該 API 在使用后將收到的 BSTR 歸零。 任何后續內存轉儲都不會包含敏感字符串。

在服務器應用程序中,很難看出它在哪里有用。

更新 2

接受 SecureString 的 .NET API 的一個示例是X509Certificate 類的構造函數 如果您使用 ILSpy 或類似方法進行探索,您會看到 SecureString 在內部轉換為非托管緩沖區 ( Marshal.SecureStringToGlobalAllocUnicode ),然后在完成 ( Marshal.ZeroFreeGlobalAllocUnicode ) 后將其歸零。

Microsoft 不建議對較新的代碼使用SecureString

SecureString Class 的文檔中:

重要的

我們不建議您將SecureString類用於新開發。 有關更多信息,請參閱不應使用SecureString

其中推薦:

不要將SecureString用於新代碼。 將代碼移植到 .NET Core 時,請考慮數組的內容在內存中未加密。

處理憑證的一般方法是避免使用憑證,而是依靠其他方式進行身份驗證,例如證書或 Windows 身份驗證。 在 GitHub 上。

正如您已經正確識別的那樣, SecureString提供了一個優於string特定優勢:確定性擦除。 這個事實有兩個問題:

  1. 正如其他人所提到的以及您自己所注意到的,這本身是不夠的。 您必須確保過程的每一步(包括檢索輸入、構造字符串、使用、刪除、傳輸等)都不會違背使用SecureString的目的。 這意味着您必須小心,永遠不要創建 GC 管理的不可變string或任何其他將存儲敏感信息的緩沖區(或者您也必須跟蹤)。 在實踐中,這並不總是容易實現,因為許多 API 只提供一種處理string ,而不是SecureString 即使你確實做到了一切正確......
  2. SecureString可以防止非常特定類型的攻擊(對於其中一些攻擊,它甚至不那么可靠)。 例如, SecureString確實允許您縮小攻擊者可以轉儲進程內存並成功提取敏感信息的時間窗口(同樣,正如您正確指出的那樣),但希望窗口對於攻擊者來說太小對您的記憶進行快照根本不被認為是安全的。

那么,你應該什么時候使用它? 只有當您使用的東西可以讓您使用SecureString滿足您的所有需求時,您仍然應該注意這僅在特定情況下是安全的。

以下文字復制自 HP Fortify 靜態代碼分析器

摘要: PassGenerator.cs 中的 PassString() 方法以不安全的方式(即以字符串形式)存儲敏感數據,從而可以通過檢查堆來提取數據。

說明:如果將存儲在內存中的敏感數據(例如密碼、社會保險號、信用卡號等)存儲在托管 String 對象中,則可能會泄漏。 字符串對象沒有被固定,因此垃圾收集器可以隨意重新定位這些對象並在內存中保留多個副本。 這些對象默認不加密,因此任何可以讀取進程內存的人都可以看到內容。 此外,如果進程的內存被換出到磁盤,字符串的未加密內容將被寫入交換文件。 最后,由於 String 對象是不可變的,從內存中刪除 String 的值只能由 CLR 垃圾收集器完成。 除非 CLR 內存不足,否則不需要運行垃圾收集器,因此無法保證垃圾收集何時發生。 在應用程序崩潰的情況下,應用程序的內存轉儲可能會泄露敏感數據。

建議:不要將敏感數據存儲在像字符串這樣的對象中,而是將它們存儲在 SecureString 對象中。 每個對象始終以加密格式將其內容存儲在內存中。

我想說明這一點:

如果攻擊者已經擁有進行堆檢查的方法,那么他們很可能 (A) 已經擁有讀取擊鍵的方法,或者 (B) 已經物理上擁有機器......那么使用SecureString阻止他們進入數據呢?

攻擊者可能無法完全訪問計算機和應用程序,但可以訪問進程內存的某些部分。 當特殊構造的輸入可能導致應用程序暴露或覆蓋內存的某些部分時,它通常是由緩沖區溢出等錯誤引起的。

HeartBleed 內存泄漏

以 Heartbleed 為例。 特殊構造的請求可能會導致代碼向攻擊者公開進程內存的隨機部分。 攻擊者可以從內存中提取 SSL 證書,但他唯一需要的只是使用格式錯誤的請求。

在托管代碼的世界中,緩沖區溢出成為問題的頻率要低得多。 對於 WinForms,數據已經以不安全的方式存儲,您對此無能為力。 這使得SecureString的保護幾乎毫無用處。

但是,可以對 GUI 進行編程以使用SecureString ,在這種情況下,減少內存中的密碼可用性窗口是值得的。 例如,WPF 中的PasswordBox.SecurePasswordSecureString類型。

前段時間我不得不針對 java 信用卡支付網關創建 ac# 接口,並且需要一個兼容的安全通信密鑰加密。 由於 Java 實現相當具體,我必須以給定的方式處理受保護的數據。

我發現這種設計非常易於使用,而且比使用 SecureString 更容易……對於那些喜歡使用的人……隨意,沒有法律限制:-)。 請注意,這些類是內部類,您可能需要將它們設為公開。

namespace Cardinity.Infrastructure
{
    using System.Security.Cryptography;
    using System;
    enum EncryptionMethods
    {
        None=0,
        HMACSHA1,
        HMACSHA256,
        HMACSHA384,
        HMACSHA512,
        HMACMD5
    }


internal class Protected
{
    private  Byte[] salt = Guid.NewGuid().ToByteArray();

    protected byte[] Protect(byte[] data)
    {
        try
        {
            return ProtectedData.Protect(data, salt, DataProtectionScope.CurrentUser);
        }
        catch (CryptographicException)//no reason for hackers to know it failed
        {
#if DEBUG
            throw;
#else
            return null;
#endif
        }
    }

    protected byte[] Unprotect(byte[] data)
    {
        try
        {
            return ProtectedData.Unprotect(data, salt, DataProtectionScope.CurrentUser);
        }
        catch (CryptographicException)//no reason for hackers to know it failed
        {
#if DEBUG
            throw;
#else
            return null;
#endif
        }
    }
}


    internal class SecretKeySpec:Protected,IDisposable
    {
        readonly EncryptionMethods _method;

        private byte[] _secretKey;
        public SecretKeySpec(byte[] secretKey, EncryptionMethods encryptionMethod)
        {
            _secretKey = Protect(secretKey);
            _method = encryptionMethod;
        }

        public EncryptionMethods Method => _method;
        public byte[] SecretKey => Unprotect( _secretKey);

        public void Dispose()
        {
            if (_secretKey == null)
                return;
            //overwrite array memory
            for (int i = 0; i < _secretKey.Length; i++)
            {
                _secretKey[i] = 0;
            }

            //set-null
            _secretKey = null;
        }
        ~SecretKeySpec()
        {
            Dispose();
        }
    }

    internal class Mac : Protected,IDisposable
    {
        byte[] rawHmac;
        HMAC mac;
        public Mac(SecretKeySpec key, string data)
        {

            switch (key.Method)
            {
                case EncryptionMethods.HMACMD5:
                    mac = new HMACMD5(key.SecretKey);
                    break;
                case EncryptionMethods.HMACSHA512:
                    mac = new HMACSHA512(key.SecretKey);
                    break;
                case EncryptionMethods.HMACSHA384:
                    mac = new HMACSHA384(key.SecretKey);
                    break;
                case EncryptionMethods.HMACSHA256:
                    mac = new HMACSHA256(key.SecretKey);

                break;
                case EncryptionMethods.HMACSHA1:
                    mac = new HMACSHA1(key.SecretKey);
                    break;

                default:                    
                    throw new NotSupportedException("not supported HMAC");
            }
            rawHmac = Protect( mac.ComputeHash(Cardinity.ENCODING.GetBytes(data)));            

        }

        public string AsBase64()
        {
            return System.Convert.ToBase64String(Unprotect(rawHmac));
        }

        public void Dispose()
        {
            if (rawHmac != null)
            {
                //overwrite memory address
                for (int i = 0; i < rawHmac.Length; i++)
                {
                    rawHmac[i] = 0;
                }

                //release memory now
                rawHmac = null;

            }
            mac?.Dispose();
            mac = null;

        }
        ~Mac()
        {
            Dispose();
        }
    }
}

如果我的假設在這里錯誤,請隨時糾正我,但是讓我解釋為什么我要問。

取自MSDN,一個SecureString

表示應保密的文本。 文本在使用時被加密以保護隱私,並在不再需要時從計算機內存中刪除。

我得到這個,它使完整意義上存儲的密碼或其他私人信息SecureStringSystem.String ,因為您可以控制如何以及何時它實際上是存儲在內存中,因為System.String

既是不可變的,並且在不再需要時不能以編程方式安排進行垃圾回收; 也就是說,實例在創建后是只讀的,無法預測何時將實例從計算機內存中刪除。 因此,如果String對象包含敏感信息,例如密碼,信用卡號或個人數據,則使用該信息后可能會泄露該信息,因為您的應用程序無法從計算機內存中刪除數據。

但是,對於GUI應用程序(例如ssh客戶端),必須從System.String構建SecureString 所有文本控件都使用字符串作為其基礎數據類型

因此,這意味着即使用戶使用密碼掩碼,每次用戶按下一個鍵時,舊的字符串都會被丟棄,而新的字符串將被構建以表示文本框中的值是什么。 而且我們無法控制何時或是否從內存中丟棄這些值中的任何一個

現在該登錄服務器了。 你猜怎么了? 您需要在連接上傳遞字符串以進行身份​​驗證 因此,讓我們將SecureString轉換為System.String ...。現在堆上有一個字符串,無法強制其通過垃圾回收(或將0寫入其緩沖區)。

我的觀點是:無論您做什么,可以將SecureString轉換為System.String ,這意味着它至少會在某個時刻存在於堆中(不保證任何垃圾回收)。

我的意思不是:是否有某種方法可以繞過向ssh連接發送字符串,或者可以避免通過控件存儲字符串(創建自定義控件)。 對於這個問題,您可以將“ ssh連接”替換為“登錄表格”,“注冊表格”,“付款表格”,“您要喂養的食物,而不是您的孩子的食物”,等等。

  • 那么,在什么時候使用SecureString實際上變得可行呢?
  • 完全根除System.String對象的使用是否值得花費額外的開發時間?
  • SecureString的全部目的是簡單地減少System.String在堆上的時間(減少其移至物理交換文件的風險)嗎?
  • 如果攻擊者已經具有檢查堆的方法,那么他很可能要么(A)已經具有讀取擊鍵的方法,要么(B)已經物理擁有了機器……因此使用SecureString阻止他進入反正數據呢?
  • 這僅僅是“默默無聞的安全”嗎?

抱歉,如果我將問題放在太深的地方,好奇心只會使我變得更好。 隨時回答我的任何或所有問題(或告訴我我的假設完全錯誤)。 :)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM