Является ли SecureString когда-либо практичным в приложении С#?

Не стесняйтесь исправлять меня, если мои предположения здесь неправильны, но позвольте мне объяснить, почему я спрашиваю.

Взято из MSDN, a SecureString:

Представляет собой текст, который должен быть конфиденциальным. Текст зашифровывается для обеспечения конфиденциальности при использовании и удаляется из памяти компьютера, когда он больше не нужен.

Я получаю это, имеет смысл хранить пароль или другую личную информацию в SecureString поверх System.String, потому что вы можете контролировать, как и когда он фактически хранится в памяти, потому что System.String:

является неизменным и, когда он больше не нужен, не может быть программно запланирован для сбора мусора; то есть экземпляр доступен только для чтения после его создания, и невозможно предсказать, когда экземпляр будет удален из памяти компьютера. Следовательно, если объект String содержит конфиденциальную информацию, такую ​​как пароль, номер кредитной карты или личные данные, существует риск того, что информация может быть обнаружена после ее использования, потому что ваше приложение не может удалить данные из памяти компьютера.

Однако, в случае приложения GUI (например, ssh-клиент), SecureString должен быть построен из System.String. Все текстовые элементы управления используют строку в качестве базового типа данных.

Таким образом, это означает, что каждый раз, когда пользователь нажимает клавишу, старая строка, которая была там, отбрасывается, и создается новая строка для представления того, что значение внутри текстового поля, даже если используется маска пароля. И мы не можем контролировать, когда или любое из этих значений будет отброшено из памяти.

Теперь пришло время входа на сервер. Угадай, что? Для аутентификации вам необходимо передать строку. Поэтому давайте преобразуем наш SecureString в System.String.... и теперь у нас есть строка в куче без возможности заставить ее пройти сборку мусора (или записать 0 в ее буфер).

Моя точка: независимо от того, что вы делаете, где-то вдоль линии, SecureString будет преобразована в System.String, что означает, что она, по крайней мере, будет существовать в куче на (без каких-либо гарантий сбора мусора).

Моя точка зрения не: существуют ли способы обхода отправки строки в ssh-соединение или обход содержимого контрольной таблицы (создание настраиваемого элемента управления). Для этого вопроса вы можете заменить "ssh connection" на "регистрационную форму", "регистрационную форму", "форму оплаты", "формулу" еда-вы-кормит-ваш-щенок, но не ваш ребенок "", и др.

  • Итак, в какой момент использование SecureString действительно становится практическое?
  • Стоит ли лишнее время разработки полностью искоренить использование объекта System.String?
  • Является ли целая точка SecureString просто уменьшать количество времени a System.String на куче (уменьшая риск перехода в файл физической подкачки)?
  • Если у злоумышленника уже есть средство для проверки кучи, то он, скорее всего, либо (A) уже имеет средства для чтения нажатий клавиш, либо (B) уже физически имеет машину... Так что, используя SecureString не позволять ему в любом случае добраться до данных?
  • Это просто "безопасность через неясность"?

Извините, если я задаю слишком сложные вопросы, любопытство только что улучшило меня. Не стесняйтесь отвечать на все или на все мои вопросы (или скажите мне, что мои предположения совершенно неверны).:)

Ответ 1

На самом деле очень практическое использование SecureString.

Вы знаете, сколько раз я видел такие сценарии? (ответ: много!):

  • Пароль случайно появляется в журнале.
  • В какой-то момент отображается пароль - как только GUI показывал запущенную командную строку приложения, а в командной строке - пароль. К сожалению.
  • Использование профилировщика профилей для профильного программного обеспечения с вашим коллегой. Коллега видит ваш пароль в памяти. Звучит нереально? Не за что.
  • Я когда-то использовал программное обеспечение RedGate которое могло бы захватить "значение" локальных переменных в случае исключений, удивительно полезно. Хотя, я могу представить, что он будет записывать "строковые пароли" случайно.
  • Дамп сбоя, который включает строковый пароль.

Вы знаете, как избежать всех этих проблем? SecureString. Обычно он делает глупые ошибки как таковые. Как это избежать? Убедившись, что пароль зашифрован в неуправляемой памяти, и реальное значение может быть доступно только тогда, когда вы на 90% уверены, что делаете.

В этом смысле SecureString работает довольно легко:

1) Все зашифровано

2) Пользовательские вызовы AppendChar

3) Расшифруйте все в UNMANAGED MEMORY и добавьте символ

4) Зашифруйте все снова в UNMANAGED MEMORY.

Что делать, если пользователь имеет доступ к вашему компьютеру? Может ли вирус получить доступ ко всем SecureStrings? Да. Все, что вам нужно сделать, это подключить себя к RtlEncryptMemory когда память расшифровывается, вы получите местоположение незашифрованного адреса памяти и прочитаете его. Вуаля! Фактически, вы можете создать вирус, который будет постоянно проверять использование SecureString и регистрировать все действия с ним. Я не говорю, что это будет непростая задача, но это можно сделать. Как вы можете видеть, "мощь" SecureString полностью исчезла, когда в вашей системе появился пользователь/вирус.

У вас есть несколько моментов в вашем посте. Конечно, если вы используете некоторые элементы управления пользовательским интерфейсом, которые содержат "строковый пароль" внутри, использование реальной SecureString не так уж и полезно. Хотя, тем не менее, он может защитить от некоторой глупости, которую я перечислил выше.

Кроме того, как отмечали другие, WPF поддерживает PasswordBox, который использует SecureString внутренне через свое свойство SecurePassword.

Суть заключается в следующем: если у вас есть конфиденциальные данные (пароли, кредитные карты,..), используйте SecureString. Это то, что следует за С# Framework. Например, класс NetworkCredential хранит пароль как SecureString. Если вы посмотрите на это, вы можете увидеть более ~ 80 различных применений в.NET framework SecureString.

Есть много случаев, когда вам нужно преобразовать SecureString в строку, потому что некоторые API ожидают ее.

Обычная проблема заключается в следующем:

  1. API - GENERIC. Он не знает, что есть конфиденциальные данные.
  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), который предоставляет string экземпляр SecureString который шифруется с использованием открытого ключа сервера. Только сервер может его расшифровать. Проблема решена: коллекция мусора никогда не увидит "оригинальную" строку, так как вы никогда не выставляете ее в управляемой памяти. Это именно то, что делается в PSRemotingCryptoHelper (EncryptSecureStringCore(SecureString secureString)).

И как-то очень почти-связанное: Mono SecureString вообще не шифрует. Реализация была прокомментирована, потому что.. ждите ее... "Это почему-то вызывает обрыв теста nunit", что приводит к моему последнему моменту:

SecureString не поддерживается везде. Если платформа/архитектура не поддерживает SecureString, вы получите исключение. Там список платформ, которые поддерживаются в документации.

Ответ 2

В ваших предположениях мало что.

Прежде всего класс SecureString не имеет конструктора String. Чтобы создать один, вы выделяете объект, а затем добавляете символы.

В случае графического интерфейса или консоли вы можете легко передать каждую нажатую клавишу в защищенную строку.

Класс разработан таким образом, что вы не можете, по ошибке, получить доступ к хранящемуся значению. Это означает, что вы не можете получить string в качестве пароля непосредственно из него.

Итак, для его использования, например, для аутентификации через Интернет, вам придется использовать соответствующие классы, которые также являются безопасными.

В .NET Framework у вас есть несколько классов, которые могут использовать SecureString

  • WPF PasswordBox управляет паролем как SecureString внутренне.
  • Свойство Password System.Diagnostics.ProcessInfo - это SecureString.
  • Конструктор для X509Certificate2 берет SecureString для пароля.

(подробнее)

В заключение, класс SecureString может быть полезен, но требует большего внимания со стороны разработчика.

Все это, с примерами, хорошо описано в документации MSDN SecureString

Ответ 3

SecureString полезна, если:

  • Вы создаете его по символу (например, с входа в консоль) или получаете его из неуправляемого API

  • Вы используете его, передавая его неуправляемому API (SecureStringToBSTR).

Если вы когда-либо преобразовали его в управляемую строку, вы победили ее цель.

UPDATE в ответ на комментарий

... или BSTR, как вы упоминаете, что не кажется более безопасным

После его преобразования в BSTR неуправляемый компонент, который потребляет BSTR, может обнулить память. Неуправляемая память более безопасна в том смысле, что она может быть reset таким образом.

Однако API-интерфейсов .NET Framework, поддерживающих SecureString, очень мало, поэтому вы можете сказать, что он имеет очень ограниченное значение сегодня.

Основной пример использования, который я видел, - это клиентское приложение, которое требует, чтобы пользователь вводил очень чувствительный код или пароль. Пользовательский ввод может использоваться символом по символу для создания SecureString, тогда это может быть передано неуправляемому API, который обнуляет BSTR, который он получает после его использования. Любой последующий дамп памяти не будет содержать чувствительную строку.

В серверном приложении трудно понять, где это было бы полезно.

ОБНОВЛЕНИЕ 2

Одним из примеров .NET API, который принимает SecureString, является этот конструктор для класса X509Certificate. Если вы поделитесь с ILSpy или похожим, вы увидите, что SecureString внутренне преобразован в неуправляемый буфер (Marshal.SecureStringToGlobalAllocUnicode), который затем обнуляется по окончании (Marshal.ZeroFreeGlobalAllocUnicode).

Ответ 4

Как вы правильно определили, SecureString предлагает одно конкретное преимущество перед string: детерминированное стирание. Есть две проблемы с этим фактом:

  • Как упомянули другие, и, как вы заметили сами, этого недостаточно. Вы должны убедиться, что каждый шаг процесса (включая поиск ввода, построение строки, использование, удаление, транспортировка и т.д.) Происходит без нарушения цели использования SecureString. Это означает, что вы должны быть осторожны, чтобы никогда не создавать неизменяемый string, управляемый GC, или любой другой буфер, который будет хранить конфиденциальную информацию (или вам также нужно будет отслеживать это). На практике это не всегда легко достичь, потому что многие API-интерфейсы предлагают только способ работы с string, а не SecureString. И даже если вам все будет в порядке...
  • SecureString защищает от очень специфических видов атак (и для некоторых из них он даже не настолько надежный). Например, SecureString позволяет вам сократить время, в которое злоумышленник может сбросить память вашего процесса и успешно извлечь конфиденциальную информацию (опять же, как вы правильно указали), но надеясь, что окно слишком мало для злоумышленник, чтобы сделать снимок вашей памяти, вообще не считается безопасностью.

Итак, когда вы должны его использовать? Только когда вы работаете с чем-то, что позволяет вам работать с SecureString для всех ваших потребностей, и даже тогда вы все равно должны помнить, что это безопасно только в определенных обстоятельствах.

Ответ 5

Я хотел бы остановиться на этом вопросе:

Если у злоумышленника уже есть средство для проверки кучи, то они, скорее всего, либо (A) уже имеют средства для чтения нажатий клавиш, либо (B) уже физически имеют машину... Так что, используя SecureString запретить им получать данные в любом случае?

Злоумышленник может не иметь полного доступа к компьютеру и приложению, но может иметь средства для доступа к некоторым частям памяти процесса. Обычно это вызвано ошибками, такими как переполнение буфера, когда специально сконструированный ввод может заставить приложение выставить или перезаписать некоторую часть памяти.

утечка памяти HeartBleed

Возьмите, например, Heartbleed. Специально сконструированные запросы могут привести к тому, что код будет подвергать атакующего случайные части памяти процесса. Злоумышленник может извлекать SSL-сертификаты из памяти, но единственное, что ему нужно, это просто использовать неверный запрос.

В мире управляемого кода переполнение буфера становится проблемой гораздо реже. А в случае WinForms данные уже хранятся в небезопасном режиме, и вы ничего не можете с этим поделать. Это делает защиту с SecureString практически бесполезной.

Однако, GUI может быть запрограммирован на использование SecureString, и в таком случае сокращение окна доступности пароля в памяти может стоить того. Например, PasswordBox.SecurePassword из WPF имеет тип SecureString.

Ответ 6

Ниже скопирован текст из анализатора статического кода HP Fortify

Аннотация: Метод PassString() в PassGenerator.cs хранит конфиденциальные данные небезопасным образом (т.е. В строке), что позволяет извлечь данные путем проверки кучи.

Объяснение: Чувствительные данные (например, пароли, номера социального страхования, номера кредитных карт и т.д.), Хранящиеся в памяти, могут быть пропущены, если они хранится в управляемом объекте String. Строковые объекты не закреплены, поэтому сборщик мусора может переместить эти объекты по желанию и оставить несколько копий в памяти. Эти объекты по умолчанию не зашифрованы, поэтому любой, кто может читать память процесса, будет способный видеть содержимое. Кроме того, если память процесса выгружается на диск, незашифрованное содержимое строки будут записаны в файл подкачки. Наконец, поскольку объекты String неизменяемы, удаление значения строки из памяти может сделайте сборщик мусора CLR. Сборщик мусора не требуется запускать, если CLR на памяти невелика, поэтому существует никаких гарантий относительно того, когда будет собираться сбор мусора. В случае сбоя приложения дамп памяти приложение может обнаруживать конфиденциальные данные.

Рекомендация: Вместо хранения конфиденциальных данных в таких объектах, как строки, сохраните их в объекте SecureString. Каждый объект хранит его содержимое в зашифрованный формат в памяти в любое время.

Ответ 7

Некоторое время назад мне пришлось создать интерфейс aС# с шлюзом оплаты через кредитную карту Java, а одному требовалось совместимое шифрование ключей защищенной связи. Поскольку реализация 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();
        }
    }
}

Ответ 8

Microsoft не рекомендует использовать SecureString для новых кодов.

Из документации класса SecureString:

Важный

Мы не рекомендуем использовать класс SecureString для новых разработок. Для получения дополнительной информации см. SecureString не должен использоваться

Который рекомендует:

Не используйте SecureString для нового кода. При переносе кода на .NET Core учтите, что содержимое массива не зашифровано в памяти.

Общий подход к работе с учетными данными - избегать их и вместо этого полагаться на другие средства аутентификации, такие как сертификаты или проверка подлинности Windows. на GitHub.