![](/img/trans.png)
[英]Using Json.NET, how can I encrypt selected properties of any type when serializing my objects?
[英]How can I encrypt selected properties when serializing my objects?
我正在使用 JSON 在我的应用程序中存储某些设置。 一些设置包含敏感信息(例如密码),而其他设置不敏感。 理想情况下,我希望能够序列化我的对象,其中敏感属性自动加密,同时保持非敏感设置可读。 有没有办法使用 Json.Net 来做到这一点? 我没有看到任何与加密相关的设置。
Json.Net没有内置加密功能。 如果您希望能够在序列化过程中加密和解密,则需要编写一些自定义代码。 一种方法是将自定义IContractResolver
与IValueProvider
结合使用。 值提供程序为您提供了一个钩子,您可以在其中转换序列化过程中的值,而合同解析程序可让您控制值提供程序的应用时间和位置。 他们可以一起为您提供所需的解决方案。
下面是您需要的代码示例。 首先,您会注意到我已经定义了一个新的[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<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);
}
}
}
}
准备好解析器之后,下一步是将自定义[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<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);
}
}
}
输出:
----- 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; }
}
我的解决方案
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);
}
(可以减少冗长)
在计划文本上使用 AES class ( https://docs.microsoft.com/fr-fr/dotnet/api/system.security.cryptography.aes?view=net-6.0 )怎么样? class 反之亦然? 是否足够安全?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.