Является ли SecureString практичным в приложении C#?

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

Взято из MSDN, SecureString:

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

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

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

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

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

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

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

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

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

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

7 ответов

Решение

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

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

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

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

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

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

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

3) Расшифруйте все в НЕЗАМЕНИМОЙ ПАМЯТИ и добавьте персонажа

4) Зашифруйте все снова в НЕЗАМЕНИМОЙ ПАМЯТИ.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

(Больше)

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

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

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

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

Важный

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

Что рекомендует:

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

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

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

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

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

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

ОБНОВЛЕНИЕ в ответ на комментарий

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

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

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

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

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

ОБНОВЛЕНИЕ 2

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

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

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

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

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

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

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

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

Я хотел бы обратиться к этой точке:

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

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

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

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

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

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

Некоторое время назад мне пришлось создать интерфейс aC# против шлюза оплаты через кредитную карту 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();
        }
    }
}
Другие вопросы по тегам