简体   繁体   English

序列化对象时如何加密选定的属性?

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

I'm using JSON to store certain settings within my application.我正在使用 JSON 在我的应用程序中存储某些设置。 Some of the settings contain sensitive information (eg passwords) while other settings are not sensitive.一些设置包含敏感信息(例如密码),而其他设置不敏感。 Ideally I'd like to be able to serialize my objects where the sensitive properties are encrypted automatically while keeping the non-sensitive settings readable.理想情况下,我希望能够序列化我的对象,其中敏感属性自动加密,同时保持非敏感设置可读。 Is there a way to do this using Json.Net?有没有办法使用 Json.Net 来做到这一点? I did not see any setting related to encryption.我没有看到任何与加密相关的设置。

Json.Net does not have built-in encryption. Json.Net没有内置加密功能。 If you want to be able to encrypt and decrypt during the serialization process, you will need to write some custom code. 如果您希望能够在序列化过程中加密和解密,则需要编写一些自定义代码。 One approach is to use a custom IContractResolver in conjunction with an IValueProvider . 一种方法是将自定义IContractResolverIValueProvider结合使用。 The value provider gives you a hook where you can transform values within the serialization process, while the contract resolver gives you control over when and where the value provider gets applied. 值提供程序为您提供了一个钩子,您可以在其中转换序列化过程中的值,而合同解析程序可让您控制值提供程序的应用时间和位置。 Together, they can give you the solution you are looking for. 他们可以一起为您提供所需的解决方案。

Below is an example of the code you would need. 下面是您需要的代码示例。 First off, you'll notice I've defined a new [JsonEncrypt] attribute; 首先,您会注意到我已经定义了一个新的[JsonEncrypt]属性; this will be used to indicate which properties you want to be encrypted. 这将用于指示您要加密的属性。 The EncryptedStringPropertyResolver class extends the DefaultContractResolver provided by Json.Net. EncryptedStringPropertyResolver类扩展了Json.Net提供的DefaultContractResolver I've overridden the CreateProperties() method so that I can inspect the JsonProperty objects created by the base resolver and attach an instance of my custom EncryptedStringValueProvider to any string properties which have the [JsonEncrypt] attribute applied. 我已经重写了CreateProperties()方法,以便我可以检查由基本解析程序创建的JsonProperty对象,并将我的自定义EncryptedStringValueProvider的实例附加到应用了[JsonEncrypt]属性的任何字符串属性。 The EncryptedStringValueProvider later handles the actual encryption/decryption of the target string properties via the respective GetValue() and SetValue() methods. 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);
            }
        }

    }
}

Once you have the resolver in place, the next step is to apply the custom [JsonEncrypt] attribute to the string properties within your classes that you wish to be encrypted during serialization. 准备好解析器之后,下一步是将自定义[JsonEncrypt]属性应用于您希望在序列化期间加密的类中的字符串属性。 For example, here is a contrived class that might represent a user: 例如,这是一个可能代表用户的人为的类:

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; }
}

The last step is to inject the custom resolver into the serialization process. 最后一步是将自定义解析器注入序列化过程。 To do that, create a new JsonSerializerSettings instance, then set the ContractResolver property to a new instance of the custom resolver. 为此,请创建一个新的JsonSerializerSettings实例,然后将ContractResolver属性设置为自定义解析程序的新实例。 Pass the settings to the JsonConvert.SerializeObject() or DeserializeObject() methods and everything should just work. 将设置传递给JsonConvert.SerializeObject()DeserializeObject()方法,一切都应该正常工作。

Here is a round-trip demo: 这是一个往返演示:

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);
        }
    }
}

Output: 输出:

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

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

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

Though @Brian's solution is quite clever, I don't like the complexity of a custom ContractResolver . 虽然@Brian的解决方案非常聪明,但我不喜欢自定义ContractResolver的复杂性。 I converted Brian's code to a JsonConverter , so your code would become 我将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; }
}

I've posted the (quite lengthy) EncryptingJsonConverter as a Gist and also blogged about it . 我已经发布了(相当冗长的) EncryptingJsonConverter作为Gist,并且还发布了博客

My solution: 我的解决方案

    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);
        }

(can be made less verbose) (可以减少冗长)

What about using the AES class ( https://docs.microsoft.com/fr-fr/dotnet/api/system.security.cryptography.aes?view=net-6.0 ) on a plan text which will be bassically your temporary jsonified class and vis-versa?在计划文本上使用 AES class ( https://docs.microsoft.com/fr-fr/dotnet/api/system.security.cryptography.aes?view=net-6.0 )怎么样? class 反之亦然? Is it enough secure?是否足够安全?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM