Bushman.Secrets.Abstractions 1.0.0.2

There is a newer version of this package available.
See the version list below for details.
dotnet add package Bushman.Secrets.Abstractions --version 1.0.0.2                
NuGet\Install-Package Bushman.Secrets.Abstractions -Version 1.0.0.2                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Bushman.Secrets.Abstractions" Version="1.0.0.2" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Bushman.Secrets.Abstractions --version 1.0.0.2                
#r "nuget: Bushman.Secrets.Abstractions, 1.0.0.2"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Bushman.Secrets.Abstractions as a Cake Addin
#addin nuget:?package=Bushman.Secrets.Abstractions&version=1.0.0.2

// Install Bushman.Secrets.Abstractions as a Cake Tool
#tool nuget:?package=Bushman.Secrets.Abstractions&version=1.0.0.2                

Bushman.Secrets.Abstractions

Причина создания

В приложениях, написанных на .Net Framework, конфигурационные файлы обычно имеют формат XML. "Из коробки" эта платформа предоставляет возможность шифровать и расшифровывать секции в подобных файлах, дабы скрывать или отображать секреты: логины, пароли, строки подключения и URL различных сервисов.

Однако порой возникает необходимость хранить зашифрованные секреты и в других местах: в JSON-файлах конфигураций, в SQL базах данных, в записях CRM, в реестре Windows, а порой они могут понадобиться и в логах. Помимо этого, может возникать необходимость отправлять зашифрованные секреты по почте или через мессенджеры, а так же сохранять их в git-репозиториях (например, в составе конфигурационных файлов).

В приложениях, написанных на .NET (в отличии от .Net Framework), конфигурационные файлы обычно имеют формат JSON. "Из коробки" эта платформа не предоставляет возможность шифровать и расшифровывать секции в таких файлах. Вместо этого Microsoft рекомендует для хранения секретов использовать такие платные хранилища секретов, как Azure Key Vault или HashiCorp Vault.

Пакет Bushman.Secrets.Abstractions предоставляет абстрактную модель для создания, парсинга, шифрования, расшифровки и распаковки секретов в тексте. Реализация этой абстрактной модели находится в пакете Bushman.Secrets. Все операции шифрования и расшифровки выполняются на основе сертификатов.

Для того, чтобы при запуске приложения в объектной модели конфигурационных настроек, представленной интерфейсами Microsoft.Extensions.Configuration.IConfigurationRoot или Microsoft.Extensions.Configuration.IConfiguration выполнить распаковку всех секретов в памяти, можно использовать пакет Bushman.Extensions.Configuration.Secrets, в составе которого для интерфейса Microsoft.Extensions.Configuration.IConfiguration определён метод расширения ExpandSecrets() (см. ниже раздел Распаковка секретов в настройках приложения).

О форме записи секретов

Секреты записываются в особом формате, позволяющем без проблем идентифицировать их в тексте. Каждый секрет может находиться в одном из двух состояний: в расшифрованном или зашифрованном.

При использовании пакета Bushman.Secrets, JSON-файл с записанными в нём секретами как в качестве непосредственных значений (см. prop2 и prop4), так и в составе произвольного текста (см. prop3 и prop5), может выглядеть, например, так:

{
  "prop1": "Hello World", // Распакованное значение.
  "prop2": "%%DECRYPTED|CurrentUser|SHA512|00DD37AA6E8AA22E9B11DFC6B8B5DD9706D9FD8C|Hello World|DECRYPTED%%", // Секрет в расшифрованном состоянии.
  "prop3": "Расшифрованный секрет в составе произвольного текста: %%DECRYPTED|CurrentUser|SHA512|00DD37AA6E8AA22E9B11DFC6B8B5DD9706D9FD8C|Hello World|DECRYPTED%%. Мама мыла раму.",

  // Секрет в зашифрованном состоянии.
  "prop4": "%%ENCRYPTED|CurrentUser|SHA512|00DD37AA6E8AA22E9B11DFC6B8B5DD9706D9FD8C|cFyOsNujOBp21frIVpIwMT2hjzR6ZDsAtZfs8eWfoVcLiqDqEO+rAEXVmE6KbQMLv+pizS8O/Ri124uM7YvM8NbsKfP2AQI4G/reup5I8kmpGXGkVjevuDuQ0eo5MRbobBPIXPFtvja9zCFn3hpNk/rt243vGMCbhCdIRgXRyOGrHxNuxlB7wHDEkZ+cz68D5cLLYYTF2ctpvgqMHjU7DRg5Vm5NT3N+Rn1FuAFmTa1laBm+Db5CM3yQ1M376FbEU6fiW3xnVrd7i52BREo4T80asmjFLcIxR8R7j5nBpZcSCM4e+wmD6IJGjJDh9Pc79I/s5P2bQduczJIxWIS1mQ==|ENCRYPTED%%",
  "prop5": "Зашифрованный секрет в составе произвольного текста: %%ENCRYPTED|CurrentUser|SHA512|00DD37AA6E8AA22E9B11DFC6B8B5DD9706D9FD8C|cFyOsNujOBp21frIVpIwMT2hjzR6ZDsAtZfs8eWfoVcLiqDqEO+rAEXVmE6KbQMLv+pizS8O/Ri124uM7YvM8NbsKfP2AQI4G/reup5I8kmpGXGkVjevuDuQ0eo5MRbobBPIXPFtvja9zCFn3hpNk/rt243vGMCbhCdIRgXRyOGrHxNuxlB7wHDEkZ+cz68D5cLLYYTF2ctpvgqMHjU7DRg5Vm5NT3N+Rn1FuAFmTa1laBm+Db5CM3yQ1M376FbEU6fiW3xnVrd7i52BREo4T80asmjFLcIxR8R7j5nBpZcSCM4e+wmD6IJGjJDh9Pc79I/s5P2bQduczJIxWIS1mQ==|ENCRYPTED%%. Мама мыла раму."
}

Распаковкой секрета называется его замена в тексте на хранящееся в нём расшифрованное значение. Например, если в свойствах prop2 и prop4 приведённого выше JSON-файла выполнить распаковку секретов, то значения этих свойств станут таким же, как у свойства prop1.

Общая схема записи секрета в тексте следующая:

SecretOpenTag|SecretStorage|HashAlgorithmName|Thumbprint|Data|SecretCloseTag

где:

  • SecretOpenTag - тег открытия секрета. Если секрет зашифрован, то это будет тег %%ENCRYPTED. Если расшифрован, то %%DECRYPTED.
  • SecretStorage - хранилище, в котором находится нужный сертификат. Допустимые значения: LocalMachine и CurrentUser.
  • HashAlgorithmName - наименование алгоритма хеширования. Допустимые значения: MD5, SHA1, SHA256, SHA384, SHA512.
  • Thumbprint - отпечаток сертификата, с помощью ключей которого следует выполнять шифрование и расшифровку секрета. Это значение можно посмотреть в настройках сертификата на вкладке "Состав".
  • Data - данные, сохраняемые в секрете. Если секрет не зашифрован, то этими данными будет обычный текст. Если секрет зашифрован, то в качестве значения будут записаны зашифрованные данные в формате строки base64.
  • SecretCloseTag - тег закрытия секрета. Если секрет зашифрован, то это будет тег ENCRYPTED%%. Если расшифрован, то DECRYPTED%%.

В качестве разделителя полей используется |. Этот символ разрешено использовать в т.ч. и в тексте, сохраняемом в поле Data.

Примеры работы с секретами

В качестве наглядных примеров, демонстрирующих работу с секретами, ниже приведены юнит-тесты.

Вспомогательный класс, для использования в приведённых ниже юнит-тестах:

using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using Bushman.Secrets.Abstractions.Services;
using System;
using Bushman.Secrets.Abstractions.Models;

namespace Bushman.Secrets.Test {
    /// <summary>
    /// Статический класс, предоставляющий набор вспомогательных методов, используемых в тестах.
    /// </summary>
    public static class TestHelper {
        /// <summary>
        /// Создать экземпляр ISecretOptions на основе произвольного сертификата, доступного текущему
        /// пользователю в его локальном хранилище.
        /// </summary>
        /// <param name="secretFactory">Фабрика секретов.</param>
        /// <returns>Экземпляр ISecretOptions.</returns>
        /// <exception cref="ArgumentNullException">В качестве параметра передан null.</exception>
        public static ISecretOptions CreateSecretOptions(ISecretFactory secretFactory) {

            if (secretFactory == null) throw new ArgumentNullException(nameof(secretFactory));

            var storeLocation = StoreLocation.CurrentUser;

            using (X509Store store = new X509Store(storeLocation)) {
                store.Open(OpenFlags.ReadOnly);
                X509Certificate2 certificate = store.Certificates[0];
                store.Close();

                using (certificate) {
                    return secretFactory.CreateSecretOptions(storeLocation, HashAlgorithmName.SHA512, certificate.Thumbprint);
                }
            }
        }

        /// <summary>
        /// Посчитать в исходном тексте (content) общее количество повторений некоторого фрагмента текста (value).
        /// </summary>
        /// <param name="content">Текст, в котором необходимо выполнить поиск и подсчёт количества значений (value).</param>
        /// <param name="value">Фрагмент текста, количество вхождений которого в исходном тексте (content) нужно посчитать.</param>
        /// <returns>Количество вхождений указанного фрагмента (value) в исходном тексте (content).</returns>
        public static int GetValuesCount(string content, string value) => new Regex(value).Matches(content).Count;
    }
}

Юнит-тесты по работе с секретами:

using Bushman.Secrets.Abstractions.Models;
using Bushman.Secrets.Abstractions.Services;
using Bushman.Secrets.Services;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Bushman.Secrets.Test {
    [TestClass]
    public sealed class EncryptorTest {

        [TestMethod]
        [DataRow("Hello, World!")]
        [DataRow("|Hello|World|")]
        [DataRow("")]
        [DataRow("|")]
        [DataRow("|||")]
        [DataRow("   ")]
        public void Secret_operations_work_fine(string value) {

            ISecretFactoryProvider secretFactoryProvider = new SecretFactoryProvider();

            // Получение фабрики секретов...

            string assemblyName = "Bushman.Secrets";
            string className = "Bushman.Secrets.Services.SecretFactory";

            // Вариант #1: по имени сборки. ВНИМАНИЕ! Этот способ подходит только если в сборке определено
            // не более одной фабрики секретов!
            ISecretFactory secretFactory1 = secretFactoryProvider.CreateSecretFactory(assemblyName);

            // Вариант #2: по имени сборки и полному имени конкретного публичного класса фабрики.
            ISecretFactory secretFactory2 = secretFactoryProvider.CreateSecretFactory(assemblyName, className);

            ISecretFactory secretFactory = secretFactory2;

            IEncryptor encryptor = secretFactory.CreateEncryptor();

            // Создаём незашифрованный секрет.
            ISecret decryptedSecret = secretFactory.CreateDecryptedSecret(TestHelper.CreateSecretOptions(secretFactory), value);

            // Создаём зашифрованный секрет.
            ISecret encryptedSecret = encryptor.Encrypt(decryptedSecret);

            Assert.AreNotEqual(decryptedSecret.ToString(), encryptedSecret.ToString());

            // Расшифровываем секрет.
            ISecret decryptedSecret2 = encryptor.Decrypt(decryptedSecret);

            Assert.AreEqual(decryptedSecret.ToString(), decryptedSecret2.ToString());

            // Распаковываем секрет.
            string expandedValue = encryptor.Expand(encryptedSecret);

            Assert.AreEqual(value, expandedValue);

            // Получаем строковое представление секрета.
            string encryptedSecretString = encryptedSecret.ToString();

            Assert.IsTrue(encryptor.IsEncryptedSecret(encryptedSecretString));
            Assert.IsFalse(encryptor.IsDecryptedSecret(encryptedSecretString));
            Assert.IsTrue(encryptor.IsSecret(encryptedSecretString));

            // Выполняем парсинг строкового представления секрета.
            ISecret parsedSecret = encryptor.ParseSecret(encryptedSecretString);

            Assert.AreEqual(encryptedSecretString, parsedSecret.ToString());
        }

        [TestMethod]
        public void RSAEncryptor_process_strings_with_secrets_correctly() {

            string value = "Hello World";

            ISecretFactoryProvider secretFactoryProvider = new SecretFactoryProvider();

            // Получение фабрики секретов...

            string assemblyName = "Bushman.Secrets";
            string className = "Bushman.Secrets.Services.SecretFactory";

            // Вариант #1: по имени сборки. ВНИМАНИЕ! Этот способ подходит только если в сборке определено
            // не более одной фабрики секретов!
            ISecretFactory secretFactory1 = secretFactoryProvider.CreateSecretFactory(assemblyName);

            // Вариант #2: по имени сборки и полному имени конкретного публичного класса фабрики.
            ISecretFactory secretFactory2 = secretFactoryProvider.CreateSecretFactory(assemblyName, className);

            ISecretFactory secretFactory = secretFactory2;

            IEncryptor encryptor = secretFactory.CreateEncryptor();

            // Создаём расшифрованный секрет.
            ISecret decryptedSecret = secretFactory.CreateDecryptedSecret(TestHelper.CreateSecretOptions(secretFactory), value);
            // Создаём зашифрованный секрет.
            ISecret encryptedSecret = encryptor.Encrypt(decryptedSecret);

            // Тестовая строка, содержащая распакованное значение,
            // а так же расшифрованный и зашифрованный секреты.
            string text = $@"{{
                ""prop1"": ""{value}"",
                ""prop2"": ""{decryptedSecret}"",
                ""prop3"": ""{encryptedSecret}""
            }}";

            Assert.AreEqual(2, TestHelper.GetValuesCount(text, value));
            Assert.AreEqual(1, encryptor.GetEncryptedSecretsCount(text));
            Assert.AreEqual(1, encryptor.GetDecryptedSecretsCount(text));

            var encryptedText = encryptor.Encrypt(text);

            Assert.AreEqual(1, TestHelper.GetValuesCount(encryptedText, value));
            Assert.AreEqual(2, encryptor.GetEncryptedSecretsCount(encryptedText));
            Assert.AreEqual(0, encryptor.GetDecryptedSecretsCount(encryptedText));

            var decryptedText = encryptor.Decrypt(text);

            Assert.AreEqual(3, TestHelper.GetValuesCount(decryptedText, value));
            Assert.AreEqual(0, encryptor.GetEncryptedSecretsCount(decryptedText));
            Assert.AreEqual(2, encryptor.GetDecryptedSecretsCount(decryptedText));

            var expandedText = encryptor.Expand(text);

            Assert.AreEqual(3, TestHelper.GetValuesCount(expandedText, value));
            Assert.AreEqual(0, encryptor.GetEncryptedSecretsCount(expandedText));
            Assert.AreEqual(0, encryptor.GetDecryptedSecretsCount(expandedText));

            INodeCollection nodes = encryptor.ParseToNodes(text);
            string text2 = nodes.ToString();

            Assert.AreEqual(text, text2);
            Assert.AreEqual(5, nodes.Count);
        }
    }
}

Юнит-тесты по созданию и использованию пользовательских настроек секретов:

using Bushman.Secrets.Abstractions.Models;
using Bushman.Secrets.Abstractions.Services;
using Bushman.Secrets.Models;
using Bushman.Secrets.Services;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace Bushman.Secrets.Test {
    [TestClass]
    public sealed class SecretOptionsTest {
        [TestMethod]
        public void Custom_SecretOptions_work_fine() {

            ISecretFactoryProvider secretFactoryProvider = new SecretFactoryProvider();

            // Получение фабрики секретов...

            // Вариант #1: по имени сборки. Этот способ подходит только если в сборке определено не более одной фабрики секретов.
            ISecretFactory secretFactory1 = secretFactoryProvider.CreateSecretFactory("Bushman.Secrets");

            // Вариант #2: по имени сборки и полному имени конкретного публичного класса фабрики.
            ISecretFactory secretFactory2 = secretFactoryProvider.CreateSecretFactory("Bushman.Secrets", "Bushman.Secrets.Services.SecretFactory");

            ISecretFactory secretFactory = secretFactory2;

            // Получение базовых настроек секретов...

            // Вариант #1: Получение базовых настроек, используемых по умолчанию.
            ISecretOptionsBase optionsBase1 = secretFactory.CreateSecretOptionsBase();

            // Вариант #2: Результат тот же, что и у варианта #1.
            ISecretOptionsBase optionsBase2 = secretFactory2.CreateSecretOptionsBase(
                SecretOptionsBase.DefaultEncoding,
                SecretOptionsBase.DefaultFieldSeparator,
                SecretOptionsBase.DefaultEncryptedTagPair,
                SecretOptionsBase.DefaultDecryptedTagPair);

            // Вариант #3: Создать пользовательские базовые настройки, отличные от базовых настроек, используемых по умолчанию.

            Encoding encoding = Encoding.UTF32;
            char fieldSeparator = ':';
            ITagPair encryptedTagPair = secretFactory.CreateTagPair("!!LOCKED", "LOCKED!!");
            ITagPair decryptedTagPair = secretFactory.CreateTagPair("!!UNLOCKED", "UNLOCKED!!");

            ISecretOptionsBase optionsBase3 = secretFactory.CreateSecretOptionsBase(encoding, fieldSeparator, encryptedTagPair, decryptedTagPair);

            ISecretOptionsBase optionsBase = optionsBase3;

            // Выбираем первый попавшийся сертификат из локального хранилища

            var storeLocation = StoreLocation.CurrentUser;

            string thumbprint = null; // Отпечаток сертификата.

            using (X509Store store = new X509Store(storeLocation)) {
                store.Open(OpenFlags.ReadOnly);
                X509Certificate2 certificate = store.Certificates[0];
                store.Close();

                using (certificate) {
                    thumbprint = certificate.Thumbprint;
                }
            }

            // Формируем настройки для работы с секретами.

            // Вариант #1: формирование настроек на основе базовых настроек, используемых по умолчанию.

            ISecretOptions options1 = secretFactory.CreateSecretOptions(storeLocation, HashAlgorithmName.SHA1, thumbprint);

            // Вариант #2: формирование настроек на основе пользовательских базовых настроек.

            ISecretOptions options2 = secretFactory.CreateSecretOptions(optionsBase, storeLocation, HashAlgorithmName.SHA1, thumbprint);

            ISecretOptions options = options2;

            string value = "Hello World";

            // Создаём незашифрованный секрет.
            ISecret decryptedSecret = secretFactory.CreateDecryptedSecret(options, value);

            Assert.AreEqual(options, decryptedSecret.Options);
            Assert.AreEqual(options.OptionsBase, decryptedSecret.Options.OptionsBase);
            Assert.AreEqual(value, decryptedSecret.Data);
            Assert.IsFalse(decryptedSecret.IsEncrypted);

            // Создаём экземпляр объекта, с помощью которого можно шифровать, расшифровывать, распаковывать, валидировать и парсить секреты.

            // Вариант #1: Создать экземпляр IEncryptor на основе базовых настроек, используемых по умолчанию.
            IEncryptor encryptor1 = secretFactory.CreateEncryptor();

            // Вариант #2: Создать экземпляр IEncryptor на основе пользовательских базовых настроек.
            IEncryptor encryptor2 = secretFactory.CreateEncryptor(optionsBase);

            IEncryptor encryptor = encryptor2;

            // Шифруем ранее созданный не зашифрованный секрет
            ISecret encryptedSecret = encryptor.Encrypt(decryptedSecret);

            Assert.AreEqual(decryptedSecret.Options, encryptedSecret.Options);
            Assert.AreEqual(decryptedSecret.Options.OptionsBase, encryptedSecret.Options.OptionsBase);
            Assert.AreNotEqual(decryptedSecret.Data, encryptedSecret.Data);
            Assert.IsTrue(encryptedSecret.IsEncrypted);

            string encryptedSecretString = encryptedSecret.ToString();
            string decryptedSecretString = decryptedSecret.ToString();

            Assert.AreNotEqual(encryptedSecretString, decryptedSecretString);
        }
    }
}

Распаковка секретов в настройках приложений

В качестве наглядных примеров, демонстрирующих работу с секретами, ниже приведены юнит-тесты.

Вспомогательный класс, для использования в приведённых ниже юнит-тестах:

using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using Bushman.Secrets.Abstractions.Services;
using System;
using Bushman.Secrets.Abstractions.Models;

namespace Bushman.Secrets.Test {
    /// <summary>
    /// Статический класс, предоставляющий набор вспомогательных методов, используемых в тестах.
    /// </summary>
    public static class TestHelper {
        /// <summary>
        /// Создать экземпляр ISecretOptions на основе произвольного сертификата, доступного текущему
        /// пользователю в его локальном хранилище.
        /// </summary>
        /// <param name="secretFactory">Фабрика секретов.</param>
        /// <returns>Экземпляр ISecretOptions.</returns>
        /// <exception cref="ArgumentNullException">В качестве параметра передан null.</exception>
        public static ISecretOptions CreateSecretOptions(ISecretFactory secretFactory) {

            if (secretFactory == null) throw new ArgumentNullException(nameof(secretFactory));

            var storeLocation = StoreLocation.CurrentUser;

            using (X509Store store = new X509Store(storeLocation)) {
                store.Open(OpenFlags.ReadOnly);
                X509Certificate2 certificate = store.Certificates[0];
                store.Close();

                using (certificate) {
                    return secretFactory.CreateSecretOptions(storeLocation, HashAlgorithmName.SHA512, certificate.Thumbprint);
                }
            }
        }

        /// <summary>
        /// Посчитать в исходном тексте (content) общее количество повторений некоторого фрагмента текста (value).
        /// </summary>
        /// <param name="content">Текст, в котором необходимо выполнить поиск и подсчёт количества значений (value).</param>
        /// <param name="value">Фрагмент текста, количество вхождений которого в исходном тексте (content) нужно посчитать.</param>
        /// <returns>Количество вхождений указанного фрагмента (value) в исходном тексте (content).</returns>
        public static int GetValuesCount(string content, string value) => new Regex(value).Matches(content).Count;
    }
}

Юнит-тест с примером того, как следует распаковывать секреты, хранящиеся в конфигурационных настройках приложения:

using Bushman.Secrets.Abstractions.Models;
using Bushman.Secrets.Abstractions.Services;
using Bushman.Secrets.Services;
using Bushman.Secrets.Test;
using Microsoft.Extensions.Configuration;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;

namespace Bushman.Extensions.Configuration.Secrets.Test {
    [TestClass]
    public sealed class ConfigurationExtensionTest {

        [TestMethod]
        public void TestMethod1() {

            ISecretFactoryProvider secretFactoryProvider = new SecretFactoryProvider();

            var value = "Hello World";

            // Получение фабрики секретов...

            string assemblyName = "Bushman.Secrets";
            string className = "Bushman.Secrets.Services.SecretFactory";

            // Вариант #1: по имени сборки. ВНИМАНИЕ! Этот способ подходит только если в сборке определено
            // не более одной фабрики секретов!
            ISecretFactory secretFactory1 = secretFactoryProvider.CreateSecretFactory(assemblyName);

            // Вариант #2: по имени сборки и полному имени конкретного публичного класса фабрики.
            ISecretFactory secretFactory2 = secretFactoryProvider.CreateSecretFactory(assemblyName, className);

            ISecretFactory secretFactory = secretFactory2;

            IEncryptor encryptor = secretFactory.CreateEncryptor();

            ISecret decryptedSecret = secretFactory.CreateDecryptedSecret(TestHelper.CreateSecretOptions(secretFactory), value);
            ISecret encryptedSecret = encryptor.Encrypt(decryptedSecret);

            var text = $@"{{
                    ""prop1"": ""{value}"",
                    ""prop2"": {{ ""prop2.1"": ""{decryptedSecret}"" }},
                    ""prop3"": [ {{ ""prop3.1"": ""{encryptedSecret}"" }} ]
                }}";

            Stream stream = new MemoryStream(encryptor.OptionsBase.Encoding.GetByteCount(value));

            using (var writer = new StreamWriter(stream, encryptor.OptionsBase.Encoding, 1024, true)) {
                writer.Write(text);
                writer.Flush();
                stream.Position = 0;
            }

            IConfigurationRoot configRoot = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonStream(stream)
                .Build();

            // На данный момент времени секреты в настройках приложения всё ещё не распакованы...

            configRoot.ExpandSecrets(secretFactory); // Распаковываем все секреты в настройках приложения.

            // Теперь все секреты в configRoot распакованы!

            // ...
        }
    }
}
Product Compatible and additional computed target framework versions.
.NET Framework net462 is compatible.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETFramework 4.6.2

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Bushman.Secrets.Abstractions:

Package Downloads
Bushman.Secrets

Реализация абстракций, определённых в пакете Bushman.Secrets.Abstractions.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
3.0.0 185 9/2/2024
2.0.1 190 8/28/2024
2.0.0 247 8/28/2024
1.0.0.2 113 8/26/2024
1.0.0.1 126 8/23/2024
1.0.0 138 8/23/2024

В readme.md добавлены примеры кода по работе с секретами.