Отмена защиты полезных данных, ключи которых были отменены в ASP.NET Core

API-интерфейсы защиты основных данных ASP.NET не предназначены в первую очередь для неограниченного сохранения конфиденциальных полезных данных. Другие технологии, такие как Windows CNG DPAPI и Azure Rights Management , более подходят для сценария неограниченного хранилища, и они имеют соответствующие сильные возможности управления ключами. Тем не более чем разработчик не запрещает использовать API защиты данных ASP.NET Core для долгосрочной защиты конфиденциальных данных. Ключи никогда не удаляются из кольца ключей, поэтому IDataProtector.Unprotect всегда можно восстановить существующие полезные данные, пока ключи доступны и допустимы.

Однако при попытке разработчика отменить защиту данных, защищенных с помощью отозванного ключа, возникает проблема, так как IDataProtector.Unprotect в этом случае возникает исключение. Это может быть хорошо для коротких или временных полезных данных (таких как маркеры проверки подлинности), так как эти полезные данные могут быть легко воссоздан системой, и в худшем случае посетитель сайта может потребоваться войти снова. Но для сохраняемых полезных данных, Unprotect что может привести к неприемлемой потере данных.

IPersistedDataProtector

Чтобы обеспечить поддержку сценария, позволяющего незащищенным полезным данным даже перед лицом отозванных ключей, система защиты данных содержит IPersistedDataProtector тип. Чтобы получить экземпляр, просто получите экземпляр IPersistedDataProtectorв обычном режиме и попробуйте приведение к IDataProtectorIPersistedDataProtector.IDataProtector

Примечание.

Не все IDataProtector экземпляры можно привести к IPersistedDataProtector. Разработчики должны использовать C# как оператор или аналогичные, чтобы избежать исключений среды выполнения, вызванных недопустимыми приведениями, и они должны быть готовы обрабатывать случай сбоя соответствующим образом.

IPersistedDataProtector предоставляет следующую поверхность API:

DangerousUnprotect(byte[] protectedData, bool ignoreRevocationErrors,
     out bool requiresMigration, out bool wasRevoked) : byte[]

Этот API принимает защищенные полезные данные (как массив байтов) и возвращает незащищенные полезные данные. Перегрузка на основе строк отсутствует. Ниже приведены два параметра.

  • requiresMigration: будет задано значение true, если ключ, используемый для защиты полезных данных, больше не является активным ключом по умолчанию, например, ключ, используемый для защиты этой полезных данных, старый, и с тех пор выполняется операция скользящего ключа. Вызывающий может рассмотреть возможность повторного защиты полезных данных в зависимости от потребностей бизнеса.

  • wasRevoked: будет задано значение true, если ключ, используемый для защиты полезных данных, был отозван.

Предупреждение

Соблюдайте крайнюю осторожность при передаче ignoreRevocationErrors: true в DangerousUnprotect метод. Если после вызова этого метода wasRevoked значение имеет значение true, то ключ, используемый для защиты этой полезных данных, был отозван, а подлинность полезных данных должна рассматриваться как подозреваемая. В этом случае продолжайте работать только на незащищенных полезных данных, если у вас есть некоторая отдельная уверенность в том, что она является аутентичной, например, она поступает из безопасной базы данных, а не отправляется ненадежным веб-клиентом.

using System;
using System.IO;
using System.Text;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.Extensions.DependencyInjection;

public class Program
{
    public static void Main(string[] args)
    {
        var serviceCollection = new ServiceCollection();
        serviceCollection.AddDataProtection()
            // point at a specific folder and use DPAPI to encrypt keys
            .PersistKeysToFileSystem(new DirectoryInfo(@"c:\temp-keys"))
            .ProtectKeysWithDpapi();
        var services = serviceCollection.BuildServiceProvider();

        // get a protector and perform a protect operation
        var protector = services.GetDataProtector("Sample.DangerousUnprotect");
        Console.Write("Input: ");
        byte[] input = Encoding.UTF8.GetBytes(Console.ReadLine());
        var protectedData = protector.Protect(input);
        Console.WriteLine($"Protected payload: {Convert.ToBase64String(protectedData)}");

        // demonstrate that the payload round-trips properly
        var roundTripped = protector.Unprotect(protectedData);
        Console.WriteLine($"Round-tripped payload: {Encoding.UTF8.GetString(roundTripped)}");

        // get a reference to the key manager and revoke all keys in the key ring
        var keyManager = services.GetService<IKeyManager>();
        Console.WriteLine("Revoking all keys in the key ring...");
        keyManager.RevokeAllKeys(DateTimeOffset.Now, "Sample revocation.");

        // try calling Protect - this should throw
        Console.WriteLine("Calling Unprotect...");
        try
        {
            var unprotectedPayload = protector.Unprotect(protectedData);
            Console.WriteLine($"Unprotected payload: {Encoding.UTF8.GetString(unprotectedPayload)}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
        }

        // try calling DangerousUnprotect
        Console.WriteLine("Calling DangerousUnprotect...");
        try
        {
            IPersistedDataProtector persistedProtector = protector as IPersistedDataProtector;
            if (persistedProtector == null)
            {
                throw new Exception("Can't call DangerousUnprotect.");
            }

            bool requiresMigration, wasRevoked;
            var unprotectedPayload = persistedProtector.DangerousUnprotect(
                protectedData: protectedData,
                ignoreRevocationErrors: true,
                requiresMigration: out requiresMigration,
                wasRevoked: out wasRevoked);
            Console.WriteLine($"Unprotected payload: {Encoding.UTF8.GetString(unprotectedPayload)}");
            Console.WriteLine($"Requires migration = {requiresMigration}, was revoked = {wasRevoked}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"{ex.GetType().Name}: {ex.Message}");
        }
    }
}

/*
 * SAMPLE OUTPUT
 *
 * Input: Hello!
 * Protected payload: CfDJ8LHIzUCX1ZVBn2BZ...
 * Round-tripped payload: Hello!
 * Revoking all keys in the key ring...
 * Calling Unprotect...
 * CryptographicException: The key {...} has been revoked.
 * Calling DangerousUnprotect...
 * Unprotected payload: Hello!
 * Requires migration = True, was revoked = True
 */