簡體   English   中英

序列化對象時如何加密選定的屬性?

[英]How can I encrypt selected properties when serializing my objects?

我正在使用 JSON 在我的應用程序中存儲某些設置。 一些設置包含敏感信息(例如密碼),而其他設置不敏感。 理想情況下,我希望能夠序列化我的對象,其中敏感屬性自動加密,同時保持非敏感設置可讀。 有沒有辦法使用 Json.Net 來做到這一點? 我沒有看到任何與加密相關的設置。

Json.Net沒有內置加密功能。 如果您希望能夠在序列化過程中加密和解密,則需要編寫一些自定義代碼。 一種方法是將自定義IContractResolverIValueProvider結合使用。 值提供程序為您提供了一個鈎子,您可以在其中轉換序列化過程中的值,而合同解析程序可讓您控制值提供程序的應用時間和位置。 他們可以一起為您提供所需的解決方案。

下面是您需要的代碼示例。 首先,您會注意到我已經定義了一個新的[JsonEncrypt]屬性; 這將用於指示您要加密的屬性。 EncryptedStringPropertyResolver類擴展了Json.Net提供的DefaultContractResolver 我已經重寫了CreateProperties()方法,以便我可以檢查由基本解析程序創建的JsonProperty對象,並將我的自定義EncryptedStringValueProvider的實例附加到應用了[JsonEncrypt]屬性的任何字符串屬性。 EncryptedStringValueProvider稍后通過相應的GetValue()SetValue()方法處理目標字符串屬性的實際加密/解密。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;

[AttributeUsage(AttributeTargets.Property)]
public class JsonEncryptAttribute : Attribute
{
}

public class EncryptedStringPropertyResolver : DefaultContractResolver
{
    private byte[] encryptionKeyBytes;

    public EncryptedStringPropertyResolver(string encryptionKey)
    {
        if (encryptionKey == null)
            throw new ArgumentNullException("encryptionKey");

        // Hash the key to ensure it is exactly 256 bits long, as required by AES-256
        using (SHA256Managed sha = new SHA256Managed())
        {
            this.encryptionKeyBytes = 
                sha.ComputeHash(Encoding.UTF8.GetBytes(encryptionKey));
        }
    }

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        IList<JsonProperty> props = base.CreateProperties(type, memberSerialization);

        // Find all string properties that have a [JsonEncrypt] attribute applied
        // and attach an EncryptedStringValueProvider instance to them
        foreach (JsonProperty prop in props.Where(p => p.PropertyType == typeof(string)))
        {
            PropertyInfo pi = type.GetProperty(prop.UnderlyingName);
            if (pi != null && pi.GetCustomAttribute(typeof(JsonEncryptAttribute), true) != null)
            {
                prop.ValueProvider = 
                    new EncryptedStringValueProvider(pi, encryptionKeyBytes);
            }
        }

        return props;
    }

    class EncryptedStringValueProvider : IValueProvider
    {
        PropertyInfo targetProperty;
        private byte[] encryptionKey;

        public EncryptedStringValueProvider(PropertyInfo targetProperty, byte[] encryptionKey)
        {
            this.targetProperty = targetProperty;
            this.encryptionKey = encryptionKey;
        }

        // GetValue is called by Json.Net during serialization.
        // The target parameter has the object from which to read the unencrypted string;
        // the return value is an encrypted string that gets written to the JSON
        public object GetValue(object target)
        {
            string value = (string)targetProperty.GetValue(target);
            byte[] buffer = Encoding.UTF8.GetBytes(value);

            using (MemoryStream inputStream = new MemoryStream(buffer, false))
            using (MemoryStream outputStream = new MemoryStream())
            using (AesManaged aes = new AesManaged { Key = encryptionKey })
            {
                byte[] iv = aes.IV;  // first access generates a new IV
                outputStream.Write(iv, 0, iv.Length);
                outputStream.Flush();

                ICryptoTransform encryptor = aes.CreateEncryptor(encryptionKey, iv);
                using (CryptoStream cryptoStream = new CryptoStream(outputStream, encryptor, CryptoStreamMode.Write))
                {
                    inputStream.CopyTo(cryptoStream);
                }

                return Convert.ToBase64String(outputStream.ToArray());
            }
        }

        // SetValue gets called by Json.Net during deserialization.
        // The value parameter has the encrypted value read from the JSON;
        // target is the object on which to set the decrypted value.
        public void SetValue(object target, object value)
        {
            byte[] buffer = Convert.FromBase64String((string)value);

            using (MemoryStream inputStream = new MemoryStream(buffer, false))
            using (MemoryStream outputStream = new MemoryStream())
            using (AesManaged aes = new AesManaged { Key = encryptionKey })
            {
                byte[] iv = new byte[16];
                int bytesRead = inputStream.Read(iv, 0, 16);
                if (bytesRead < 16)
                {
                    throw new CryptographicException("IV is missing or invalid.");
                }

                ICryptoTransform decryptor = aes.CreateDecryptor(encryptionKey, iv);
                using (CryptoStream cryptoStream = new CryptoStream(inputStream, decryptor, CryptoStreamMode.Read))
                {
                    cryptoStream.CopyTo(outputStream);
                }

                string decryptedValue = Encoding.UTF8.GetString(outputStream.ToArray());
                targetProperty.SetValue(target, decryptedValue);
            }
        }

    }
}

准備好解析器之后,下一步是將自定義[JsonEncrypt]屬性應用於您希望在序列化期間加密的類中的字符串屬性。 例如,這是一個可能代表用戶的人為的類:

public class UserInfo
{
    public string UserName { get; set; }

    [JsonEncrypt]
    public string UserPassword { get; set; }

    public string FavoriteColor { get; set; }

    [JsonEncrypt]
    public string CreditCardNumber { get; set; }
}

最后一步是將自定義解析器注入序列化過程。 為此,請創建一個新的JsonSerializerSettings實例,然后將ContractResolver屬性設置為自定義解析程序的新實例。 將設置傳遞給JsonConvert.SerializeObject()DeserializeObject()方法,一切都應該正常工作。

這是一個往返演示:

public class Program
{
    public static void Main(string[] args)
    {
        try
        {
            UserInfo user = new UserInfo
            {
                UserName = "jschmoe",
                UserPassword = "Hunter2",
                FavoriteColor = "atomic tangerine",
                CreditCardNumber = "1234567898765432",
            };

            // Note: in production code you should not hardcode the encryption
            // key into the application-- instead, consider using the Data Protection 
            // API (DPAPI) to store the key.  .Net provides access to this API via
            // the ProtectedData class.

            JsonSerializerSettings settings = new JsonSerializerSettings();
            settings.Formatting = Formatting.Indented;
            settings.ContractResolver = new EncryptedStringPropertyResolver("My-Sup3r-Secr3t-Key");

            Console.WriteLine("----- Serialize -----");
            string json = JsonConvert.SerializeObject(user, settings);
            Console.WriteLine(json);
            Console.WriteLine();

            Console.WriteLine("----- Deserialize -----");
            UserInfo user2 = JsonConvert.DeserializeObject<UserInfo>(json, settings);

            Console.WriteLine("UserName: " + user2.UserName);
            Console.WriteLine("UserPassword: " + user2.UserPassword);
            Console.WriteLine("FavoriteColor: " + user2.FavoriteColor);
            Console.WriteLine("CreditCardNumber: " + user2.CreditCardNumber);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.GetType().Name + ": " + ex.Message);
        }
    }
}

輸出:

----- Serialize -----
{
  "UserName": "jschmoe",
  "UserPassword": "sK2RvqT6F61Oib1ZittGBlv8xgylMEHoZ+1TuOeYhXQ=",
  "FavoriteColor": "atomic tangerine",
  "CreditCardNumber": "qz44JVAoJEFsBIGntHuPIgF1sYJ0uyYSCKdYbMzrmfkGorxgZMx3Uiv+VNbIrbPR"
}

----- Deserialize -----
UserName: jschmoe
UserPassword: Hunter2
FavoriteColor: atomic tangerine
CreditCardNumber: 1234567898765432

小提琴: https//dotnetfiddle.net/trsiQc

雖然@Brian的解決方案非常聰明,但我不喜歡自定義ContractResolver的復雜性。 我將Brian的代碼轉換為JsonConverter ,因此您的代碼將變為

public class UserInfo
{
    public string UserName { get; set; }

    [JsonConverter(typeof(EncryptingJsonConverter), "My-Sup3r-Secr3t-Key")]
    public string UserPassword { get; set; }

    public string FavoriteColor { get; set; }

    [JsonConverter(typeof(EncryptingJsonConverter), "My-Sup3r-Secr3t-Key")]
    public string CreditCardNumber { get; set; }
}

我已經發布了(相當冗長的) EncryptingJsonConverter作為Gist,並且還發布了博客

我的解決方案

    public string PasswordEncrypted { get; set; }

    [JsonIgnore]
    public string Password
    {
        get
        {
            var encrypted = Convert.FromBase64String(PasswordEncrypted);
            var data = ProtectedData.Unprotect(encrypted, AdditionalEntropy, DataProtectionScope.LocalMachine);
            var res = Encoding.UTF8.GetString(data);
            return res;
        }
        set
        {
            var data = Encoding.UTF8.GetBytes(value);
            var encrypted = ProtectedData.Protect(data, AdditionalEntropy, DataProtectionScope.LocalMachine);
            PasswordEncrypted = Convert.ToBase64String(encrypted);
        }

(可以減少冗長)

在計划文本上使用 AES class ( https://docs.microsoft.com/fr-fr/dotnet/api/system.security.cryptography.aes?view=net-6.0 )怎么樣? class 反之亦然? 是否足夠安全?

暫無
暫無

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

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