using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Collections.ObjectModel;
using System.Security.Cryptography;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
public partial class TopLevelModel
public string PrivateText { get; set; }
public string Note { get; set; }
public IEnumerable<InternalFlatStringModel> FlatStringModels { get; set; }
public class InternalFlatStringModel
public string PrivateText { get; set; }
public string Note { get; set; }
public partial class TopLevelModel
[JsonConverter(typeof(RequiresConverterConverter))]
public RequiresConverter RequiresConverter { get; set; }
public class RequiresConverter
public RequiresConverter(string value) { this.value = value; }
public string GetValue() => value;
class RequiresConverterConverter : JsonConverter<RequiresConverter>
public override void WriteJson(JsonWriter writer, RequiresConverter value, JsonSerializer serializer)
writer.WriteValue(value.GetValue());
public override RequiresConverter ReadJson(JsonReader reader, Type objectType, RequiresConverter existingValue, bool hasExistingValue, JsonSerializer serializer)
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
else if (reader.TokenType != JsonToken.String)
throw new JsonSerializationException(string.Format("Unexpected token type {0}", reader.TokenType));
return new RequiresConverter((string)reader.Value);
public interface IEncryptionService
public string Encrypt(string input);
public string Decrypt(string input);
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class JsonEncryptAttribute : Attribute
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);
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);
return serializer.Deserialize(reader, objectType);
public void WriteInnerJson(JsonWriter writer, object value, JsonSerializer serializer)
if (InnerConverter?.CanWrite == true)
InnerConverter.WriteJson(writer, value, serializer);
serializer.Serialize(writer, value);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
if (reader.MoveToContentAndAssert().TokenType == JsonToken.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)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None)
while (reader.TokenType == JsonToken.Comment)
public static JsonReader ReadAndAssert(this JsonReader reader)
throw new ArgumentNullException();
throw new JsonReaderException("Unexpected end of JSON stream.");
public class DummyEncryptionService : IEncryptionService
public string Encrypt(string input) =>
Convert.ToBase64String(Encoding.UTF8.GetBytes(input));
public string Decrypt(string input) =>
Encoding.UTF8.GetString(Convert.FromBase64String(input));
public class AesEncryptionService : IEncryptionService
readonly private byte[] encryptionKeyBytes;
public AesEncryptionService(string encryptionKey)
if (encryptionKey == null)
throw new ArgumentNullException("encryptionKey");
using (SHA256Managed sha = new SHA256Managed())
this.encryptionKeyBytes =
sha.ComputeHash(Encoding.UTF8.GetBytes(encryptionKey));
public string Encrypt(string input)
byte[] buffer = Encoding.UTF8.GetBytes(input);
using (MemoryStream inputStream = new MemoryStream(buffer, false))
using (MemoryStream outputStream = new MemoryStream())
using (AesManaged aes = new AesManaged { Key = encryptionKeyBytes })
outputStream.Write(iv, 0, iv.Length);
ICryptoTransform encryptor = aes.CreateEncryptor(encryptionKeyBytes, iv);
using (CryptoStream cryptoStream = new CryptoStream(outputStream, encryptor, CryptoStreamMode.Write))
inputStream.CopyTo(cryptoStream);
return Convert.ToBase64String(outputStream.ToArray());
public string Decrypt(string input)
byte[] buffer = Convert.FromBase64String(input);
using (MemoryStream inputStream = new MemoryStream(buffer, false))
using (MemoryStream outputStream = new MemoryStream())
using (AesManaged aes = new AesManaged { Key = encryptionKeyBytes })
byte[] iv = new byte[16];
int bytesRead = inputStream.Read(iv, 0, 16);
throw new CryptographicException("IV is missing or invalid.");
ICryptoTransform decryptor = aes.CreateDecryptor(encryptionKeyBytes, iv);
using (CryptoStream cryptoStream = new CryptoStream(inputStream, decryptor, CryptoStreamMode.Read))
cryptoStream.CopyTo(outputStream);
string decryptedValue = Encoding.UTF8.GetString(outputStream.ToArray());
public static void Test()
var model = new TopLevelModel
FlatStringModels = new List<TopLevelModel.InternalFlatStringModel>
new() { PrivateText = "secret", Note = "public" },
RequiresConverter = new RequiresConverter("secret"),
var plainJson = JsonConvert.SerializeObject(model, Formatting.Indented);
var _encryptionService = new AesEncryptionService("test password");
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);
Console.WriteLine(plainJson);
Console.WriteLine(encryptedJson);
Assert.IsTrue(!encryptedJson.Contains("secret"));
var plainJson2 = JsonConvert.SerializeObject(model2, Formatting.Indented);
Assert.AreEqual(plainJson, plainJson2);
public static void Main()
Console.WriteLine("Environment version: {0} ({1})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , GetNetCoreVersion());
Console.WriteLine("{0} version: {1}", typeof(JsonSerializer).Assembly.GetName().Name, typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");
public static string GetNetCoreVersion()
var assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly;
var assemblyPath = assembly.Location.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App");
if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2)
return assemblyPath[netCoreAppIndex + 1];