如何在序列化对象时加密选定的属性?

我正在使用JSON在我的应用程序中存储某些设置。 某些设置包含敏感信息(例如密码),而其他设置不敏感。 理想情况下,我希望能够序列化我的对象,其中敏感属性自动加密,同时保持非敏感设置可读。 有没有办法使用Json.Net做到这一点? 我没有看到任何与加密相关的设置。

Json.Net没有内置加密function。 如果您希望能够在序列化过程中加密和解密,则需要编写一些自定义代码。 一种方法是将自定义IContractResolverIValueProvider结合使用。 值提供程序为您提供了一个钩子,您可以在序列化过程中转换值,而合约解析器可让您控制值提供程序的应用时间和位置。 他们可以一起为您提供所需的解决方案。

下面是您需要的代码示例。 首先,您会注意到我已经定义了一个新的[JsonEncrypt]属性; 这将用于指示您要加密的属性。 EncryptedStringPropertyResolver类扩展了Json.Net提供的DefaultContractResolver 。 我已经重写了CreateProperties()方法,以便我可以检查由基本解析器创建的JsonProperty对象,并将我的自定义EncryptedStringValueProvider的实例附加到应用了[JsonEncrypt]属性的任何字符串属性。 EncryptedStringValueProvider稍后通过相应的GetValue()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 CreateProperties(Type type, MemberSerialization memberSerialization) { IList 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); } } } } 

准备好解析器之后,下一步是将自定义[JsonEncrypt]属性应用于您希望在序列化期间加密的类中的字符串属性。 例如,这是一个可能代表用户的人为的类:

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

最后一步是将自定义解析器注入序列化过程。 为此,请创建一个新的JsonSerializerSettings实例,然后将ContractResolver属性设置为自定义解析程序的新实例。 将设置传递给JsonConvert.SerializeObject()DeserializeObject()方法,一切都应该正常工作。

这是一个往返演示:

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

输出:

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

小提琴: https : //dotnetfiddle.net/trsiQc

虽然@Brian的解决方案非常聪明,但我不喜欢自定义ContractResolver的复杂性。 我将Brian的代码转换为JsonConverter ,因此您的代码将成为

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

我发布了(相当冗长的) EncryptingJsonConverter作为一个要点 ,并在博客上发布了它 。

我的解决方案

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

(可以减少冗长)