Comment chiffrer les propriétés sélectionnées lors de la sérialisation de mes objets?

j'utilise JSON pour stocker certains paramètres dans mon application. Certains paramètres contiennent des informations sensibles (par exemple des mots de passe) tandis que d'autres ne sont pas sensibles. Idéalement, je voudrais être capable de sérialiser mes objets où les propriétés sensibles sont cryptées automatiquement tout en gardant les paramètres non sensibles lisibles. Y a-t-il un moyen de le faire en utilisant Json.Net Je ne sais pas. Je n'ai vu aucun paramètre lié au cryptage.

4
demandé sur Brian Rogers 2015-03-22 19:23:14

3 réponses

Json.Net ne possède pas de cryptage intégré. Si vous voulez être capable de chiffrer et déchiffrer pendant le processus de sérialisation, vous aurez besoin d'écrire du code personnalisé. Une approche consiste à utiliser un IContractResolver personnalisé en conjonction avec un IValueProvider . Le fournisseur de valeur vous donne un crochet où vous pouvez transformer les valeurs dans le processus de sérialisation, tandis que le résolveur de contrat vous donne le contrôle sur quand et où le fournisseur de valeur s'applique. Ensemble, ils peuvent vous donner la solution que vous recherchez.

ci-dessous est un exemple du code dont vous avez besoin. Tout d'abord, vous remarquerez que j'ai défini un nouvel attribut [JsonEncrypt] , qui sera utilisé pour indiquer les propriétés que vous voulez crypter. La classe EncryptedStringPropertyResolver prolonge la classe DefaultContractResolver fournie par Json.Net. J'ai dépassé la méthode CreateProperties() pour pouvoir inspecter les objets JsonProperty créés par la base résolvez et attachez une instance de ma coutume EncryptedStringValueProvider à n'importe quelles propriétés de chaîne qui ont l'attribut [JsonEncrypt] appliqué. Le EncryptedStringValueProvider gère plus tard le chiffrement/déchiffrement réel des propriétés de la chaîne de caractères cible via les méthodes respectives GetValue() et 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);
            }
        }

    }
}

une fois que vous avez le résolveur en place, l'étape suivante est d'appliquer l'attribut personnalisé [JsonEncrypt] aux propriétés de chaîne dans vos classes que vous souhaitez être chiffré lors de la sérialisation. Par exemple, voici une classe construite qui pourrait représenter un utilisateur:

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

La dernière étape consiste à injecter le résolveur personnalisé dans le processus de sérialisation. Pour ce faire, créez une nouvelle instance JsonSerializerSettings , puis définissez la propriété ContractResolver à une nouvelle instance du résolveur personnalisé. Passez les paramètres aux méthodes JsonConvert.SerializeObject() ou DeserializeObject() et tout devrait fonctionner.

voici un démonstration aller-retour:

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

sortie:

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

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

violon: https://dotnetfiddle.net/trsiQc

24
répondu Brian Rogers 2015-03-27 14:26:46

bien que la solution de @Brian soit assez intelligente, Je n'aime pas la complexité d'une coutume ContractResolver . J'ai converti le code de Brian en un JsonConverter , pour que votre code devienne

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

j'ai posté le (assez long) EncryptingJsonConverter comme un Gist et aussi blogué à ce sujet .

2
répondu Thomas Freudenberg 2017-02-11 12:50:39

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

(peut être rendu moins verbeux)

1
répondu user626528 2015-08-28 16:04:51