簡體   English   中英

使用 Json.NET,如何在序列化對象時加密任何類型的選定屬性?

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

我想將Brian Rogers這個答案概括為如何在序列化我的對象時加密選定的屬性? 使 Json.NET 自動加密已應用屬性[JsonEncrypt]的任何類型的屬性 - 而不僅僅是該答案支持的字符串值屬性。 如何實施?

目前我正在嘗試通過調用自定義序列化程序和提供程序來實現基於屬性的加密/解密,如下所示:

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

模型在哪里:

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

提供者:

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

問題是永遠不會為嵌套的 object InternalFlatStringModel 調用 SetValue。 獲取 System.Runtime.Serialization.SerializationException:轉換值 %encrypted object% 時出錯。

Brian Roger 的自定義合約解析器使用自定義IValueProvider注入邏輯來加密和解密字符串值屬性。 您希望通過對 JSON 進行嵌套序列化或反序列化,然后加密或解密生成的 JSON 字符串,將其概括為加密和解密任何類型的屬性 不幸的是,自定義值提供者並不真正適合此目的。 值提供者的職責是從數據庫中設置和獲取值。 它的設計目的不是對該值進行任意復雜的轉換,或者進行嵌套序列化。 (當前的JsonSerializer甚至沒有傳遞給值提供者。)

聯系人解析器可以應用自定義JsonConverter來代替值提供程序,以執行必要的嵌套序列化和加密。 JsonConverter可以訪問當前的序列化程序,其職責包括將 JSON 從和最終數據 model 轉換為最終數據,因此它更適合此任務。

首先,定義以下屬性和接口:

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

在這里,我假設所需的_encryptionKey和 salt 將封裝在您的IEncryptionService的具體實現中。

接下來,定義以下合約解析器:

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

現在您可以將[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);

筆記:

  • 未實現設置了 JsonPropertyAttribute.ItemConverterType 的JsonPropertyAttribute.ItemConverterType

  • 我還沒有測試參數化構造函數 arguments 的加密。

  • 這個答案沒有解決IEncryptionService應該如何實現。 加密算法的安全實現在其 scope 之外。

演示小提琴在這里

暫無
暫無

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

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