15

I'm using JSON to store certain settings within my application. Some of the settings contain sensitive information (e.g. 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? I did not see any setting related to encryption.

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
user626528
  • 13,999
  • 30
  • 78
  • 146
  • 3
    You do it by writing code that does it. Now, please edit your question explaining what is going wrong when you try to do it. – Scott Chamberlain Mar 22 '15 at 18:30
  • @Scott Chamberlain, what is going wrong: all properties are serialized "as is" and I don't have any choice. – user626528 Mar 23 '15 at 02:22
  • @user626528 - I think Scott's point was that we're not a code writing service. You need to have a go writing code and when you get stuck then ask a question (and post your code). – Enigmativity Mar 25 '15 at 02:19
  • @Enigmativity, all what I needed is explanation what route to take. – user626528 Mar 25 '15 at 04:33
  • @user626528 - I think you're still missing the point. – Enigmativity Mar 25 '15 at 06:40
  • 6
    @Enigmativity, I think **you** are missing the point. I didn't ask for coding, I asked for explanation of general idea how to do what I need. – user626528 Mar 25 '15 at 08:22
  • Check this out with a complete custom encryption solution with newtonsoftof certain properties in an event sourcing context, as the mechanism is also valid for your question https://www.eventstore.com/blog/protecting-sensitive-data-in-event-sourced-systems-with-crypto-shredding-1 – diegosasw Jul 08 '22 at 22:38

3 Answers3

47

Json.Net does not have built-in encryption. 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. 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; this will be used to indicate which properties you want to be encrypted. The EncryptedStringPropertyResolver class extends the DefaultContractResolver provided by Json.Net. 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. The EncryptedStringValueProvider later handles the actual encryption/decryption of the target string properties via the respective GetValue() and SetValue() methods.

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. 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. Pass the settings to the JsonConvert.SerializeObject() or DeserializeObject() methods and everything should just work.

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

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • "why not just encrypt the entire JSON" - because I'm using JSON to store settings and encrypting files completely would be quite inconvenient. – user626528 Mar 25 '15 at 04:32
  • I'm facing issue with encrypting when i tried with int or DateTime type. – Velkumar Jul 20 '16 at 07:44
  • @Velkumar The best way to get help is to ask a new question describing in detail what that issue is and where you are getting while stuck trying to solve it. See [How do I ask a good question?](http://stackoverflow.com/help/how-to-ask) in the help center. You can add a link back to this question or answer to provide context if you need to. The comments area is not intended for asking new questions. – Brian Rogers Jul 20 '16 at 14:34
  • @Velkumar that's because you can "only" encrypt `string` or `byte[]` data types. To encrypt DateTime or numbers a new approach is required where, for example, you can tweak the contract resolver to handle these data types in a different manner. Otherwise all your sensitive information will need to be in a `string` – diegosasw Jul 12 '19 at 09:08
  • @BrianRogers do you know what could cause the `SetValue(object target, object value)` not to be invoked? If I have only one JsonSettings with a custom contract resolver that sets a cstom value provider on every property. I use the settings when serializing and deserializing and I can see that the `GetValue` being executed when serializing, but the `SetValue` is never executed when deserializing. Is there any catch with this value providers for deserialization? – diegosasw Jul 12 '19 at 19:50
9

Though @Brian's solution is quite clever, I don't like the complexity of a custom ContractResolver. I converted Brian's code to a JsonConverter, so your code would become

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.

Thomas Freudenberg
  • 5,048
  • 1
  • 35
  • 44
5

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)

user626528
  • 13,999
  • 30
  • 78
  • 146