简体   繁体   English

使用 Json.NET,如何在序列化对象时加密任何类型的选定属性?

[英]Using Json.NET, how can I encrypt selected properties of any type when serializing my objects?

I would like to generalize this answer by Brian Rogers to How can I encrypt selected properties when serializing my objects?我想将Brian Rogers这个答案概括为如何在序列化我的对象时加密选定的属性? to make Json.NET automatically encrypt any type of property to which an attribute [JsonEncrypt] has been applied -- not just string valued properties which are supported by that answer.使 Json.NET 自动加密已应用属性[JsonEncrypt]的任何类型的属性 - 而不仅仅是该答案支持的字符串值属性。 How can this be implemented?如何实施?

Currently I am trying to achieve attribute based encryption/decryption by calling custom serializer and providers like this:目前我正在尝试通过调用自定义序列化程序和提供程序来实现基于属性的加密/解密,如下所示:

[Test]
public void MessageJsonSerializer_TopLevelModel_Success()
{    
    // arrange    
    var key = _fixture.Create<Guid>().ToString().ToUpper();    
    var model = _fixture.Create<TopLevelModel>();    
    var serializer = new MessageJsonSerializer<TopLevelModel>(_encryptionService);    

    // act    
    var serializedEncrypted = serializer.SerializeEncrypted(model, key);    
    var deserializedDecrypted = serializer.DeserializeEncrypted(serializedEncrypted, key);    

    // assert    
    model.Should().BeEquivalentTo(deserializedDecrypted, "After de/serialization models should be equal");    
    serializer.Invoking(x => x.Deserialize(serializedEncrypted))        
        .Should()        
        .Throw<SerializationException>("Full sub-class will be converted to encrypted string - couldn't be deserialized");}

Where Models are:模型在哪里:

public class TopLevelModel
{    
    [JsonEncrypt]    
    public string PrivateText { get; set; }    
    public string Note { get; set; }    

    [JsonEncrypt]
    public IEnumerable<InternalFlatStringModel> FlatStringModels { get; set; }    

    public class InternalFlatStringModel : IEncryptedDTO    
    {        
        [JsonEncrypt]        
        public string PrivateText { get; set; }        
        public string Note { get; set; }    
    }
}

The provider:提供者:

public class EncryptedObjectValueProvider : IValueProvider
{
    private readonly IEncryptionService _encryptionService;
    private readonly PropertyInfo _targetProperty;
    private readonly string _encryptionKey;

    public EncryptedObjectValueProvider(IEncryptionService encryptionService, PropertyInfo targetProperty, string encryptionKey)
    {
        _encryptionService = encryptionService;
        _targetProperty = targetProperty;
        _encryptionKey = encryptionKey;

    }

    public object GetValue(object target)
    {
        var jsonString = JsonConvert.SerializeObject(_targetProperty.GetValue(target), Formatting.None);
        return _encryptionService.EncryptStringAES(jsonString, _encryptionKey);
    }
    public void SetValue(object target, object value)
    {
        // --> never hits this one
        var decryptedValue = _encryptionService.DecryptStringAES((string) value, _encryptionKey);
        var decryptedObject = JsonConvert.DeserializeObject(decryptedValue);
        _targetProperty.SetValue(target, decryptedObject);
    }
}

The problem is SetValue will never be called for nested object InternalFlatStringModel.问题是永远不会为嵌套的 object InternalFlatStringModel 调用 SetValue。 Getting System.Runtime.Serialization.SerializationException: Error converting value %encrypted object%.获取 System.Runtime.Serialization.SerializationException:转换值 %encrypted object% 时出错。

Brian Roger's custom contract resolver uses a custom IValueProvider to inject logic to encrypt and decrypt string-valued properties. Brian Roger 的自定义合约解析器使用自定义IValueProvider注入逻辑来加密和解密字符串值属性。 You would like to generalize this to encrypt and decrypt any type of property by doing a nested serialization or deserialization to or from JSON, and then encrypting or decrypting the resulting JSON string.您希望通过对 JSON 进行嵌套序列化或反序列化,然后加密或解密生成的 JSON 字符串,将其概括为加密和解密任何类型的属性 Unfortunately, a custom value provider is not really suited for this purpose.不幸的是,自定义值提供者并不真正适合此目的。 The responsibility of a value provider is to set and get a value from the database.值提供者的职责是从数据库中设置和获取值。 It isn't designed to do arbitrarily complex transformations of that value, or to do nested serializations.它的设计目的不是对该值进行任意复杂的转换,或者进行嵌套序列化。 (The current JsonSerializer isn't even passed into the value provider.) (当前的JsonSerializer甚至没有传递给值提供者。)

Instead of a value provider, a custom JsonConverter can be applied by the contact resolver to do the necessary nested serialization and encryption.联系人解析器可以应用自定义JsonConverter来代替值提供程序,以执行必要的嵌套序列化和加密。 A JsonConverter has access to the current serializer, and its responsibilities include transforming JSON from and to the final data model, so it is better suited to this task. JsonConverter可以访问当前的序列化程序,其职责包括将 JSON 从和最终数据 model 转换为最终数据,因此它更适合此任务。

First, define the following attribute and interface:首先,定义以下属性和接口:

public interface IEncryptionService
{
    /// <summary>
    /// Encrypt the incoming string into another Utf8 string using whatever algorithm, password and salt are appropiate.
    /// </summary>
    /// <param name="input">The string to be encrypted</param>
    /// <returns>The encypted string</returns>
    public string Encrypt(string input);
    /// <summary>
    /// Decrypt the incoming string into another Utf8 string using whatever algorithm, password and salt are appropiate.
    /// </summary>
    /// <param name="input">The encrypted string</param>
    /// <returns>The decrypted string value</returns>
    public string Decrypt(string input);
}

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class JsonEncryptAttribute : Attribute
{
}

Here I assume that the required _encryptionKey and salt will be encapsulated inside your concrete implementation of IEncryptionService .在这里,我假设所需的_encryptionKey和 salt 将封装在您的IEncryptionService的具体实现中。

Next, define the following contract resolver:接下来,定义以下合约解析器:

public class EncryptedPropertyContractResolver : DefaultContractResolver
{
    IEncryptionService EncryptionService { get; }

    public EncryptedPropertyContractResolver(IEncryptionService encryptionService) => this.EncryptionService = encryptionService ?? throw new ArgumentNullException(nameof(encryptionService));

    protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
    {
        var property = base.CreateProperty(member, memberSerialization);
        return ApplyEncryption(property);
    }
    
    protected override JsonProperty CreatePropertyFromConstructorParameter(JsonProperty matchingMemberProperty, ParameterInfo parameterInfo)
    {
        var property = base.CreatePropertyFromConstructorParameter(matchingMemberProperty, parameterInfo);
        return ApplyEncryption(property, matchingMemberProperty);
    }
    
    JsonProperty ApplyEncryption(JsonProperty property, JsonProperty matchingMemberProperty = null)
    {
        if ((matchingMemberProperty ?? property).AttributeProvider.GetAttributes(typeof(JsonEncryptAttribute), true).Any())
        {
            if (property.ItemConverter != null)
                throw new NotImplementedException("property.ItemConverter");
            property.Converter = new EncryptingJsonConverter(EncryptionService, property.Converter);
        }
        return property;
    }
    
    class EncryptingJsonConverter : JsonConverter
    {
        IEncryptionService EncryptionService { get; }
        JsonConverter InnerConverter { get; }

        public EncryptingJsonConverter(IEncryptionService encryptionService, JsonConverter innerConverter)
        {
            this.EncryptionService = encryptionService ?? throw new ArgumentNullException(nameof(encryptionService));
            this.InnerConverter = innerConverter;
        }

        public override bool CanConvert(Type objectType) => throw new NotImplementedException(nameof(CanConvert));          

        object ReadInnerJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (InnerConverter?.CanRead == true)
                return InnerConverter.ReadJson(reader, objectType, existingValue, serializer);
            else
                return serializer.Deserialize(reader, objectType);
        }
        
        public void WriteInnerJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            if (InnerConverter?.CanWrite == true)
                InnerConverter.WriteJson(writer, value, serializer);
            else
                serializer.Serialize(writer, value);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
                return null;
            else if (reader.TokenType != JsonToken.String)
                throw new JsonSerializationException(string.Format("Unexpected token type {0}", reader.TokenType));
            var encryptedString = (string)reader.Value;
            var jsonString = EncryptionService.Decrypt(encryptedString);
            using (var subReader = new JsonTextReader(new StringReader(jsonString)))
                return ReadInnerJson(subReader.MoveToContentAndAssert(), objectType, existingValue, serializer);
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            using var textWriter = new StringWriter();
            using (var subWriter = new JsonTextWriter(textWriter))
                WriteInnerJson(subWriter, value, serializer);
            var encryptedString = EncryptionService.Encrypt(textWriter.ToString());
            writer.WriteValue(encryptedString);
        }
    }
}

public static partial class JsonExtensions
{
    public static JsonReader MoveToContentAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (reader.TokenType == JsonToken.None)       // Skip past beginning of stream.
            reader.ReadAndAssert();
        while (reader.TokenType == JsonToken.Comment) // Skip past comments.
            reader.ReadAndAssert();
        return reader;
    }

    public static JsonReader ReadAndAssert(this JsonReader reader)
    {
        if (reader == null)
            throw new ArgumentNullException();
        if (!reader.Read())
            throw new JsonReaderException("Unexpected end of JSON stream.");
        return reader;
    }
}

And now you can apply [JsonEncrypt] to your model as shown in your question, and serialize and deserialize instances of it as follows:现在您可以将[JsonEncrypt]应用于您的 model,如您的问题所示,并按如下方式序列化和反序列化它的实例:

var resolver = new EncryptedPropertyContractResolver(_encryptionService);
var settings = new JsonSerializerSettings
{
    ContractResolver = resolver,
};          
var encryptedJson = JsonConvert.SerializeObject(model, Formatting.Indented, settings);
var model2 = JsonConvert.DeserializeObject<TopLevelModel>(encryptedJson, settings);

Notes:笔记:

  • Collections for which JsonPropertyAttribute.ItemConverterType is set are not implemented.未实现设置了 JsonPropertyAttribute.ItemConverterType 的JsonPropertyAttribute.ItemConverterType

  • I have not tested encryption of parameterized constructor arguments.我还没有测试参数化构造函数 arguments 的加密。

  • This answer doesn't address how the IEncryptionService should be implemented.这个答案没有解决IEncryptionService应该如何实现。 Secure implementation of encryption algorithms is outside its scope.加密算法的安全实现在其 scope 之外。

Demo fiddle here .演示小提琴在这里

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

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