我正在尝试在c#中编写一个MongoDb序列化程序,它允许我通过 [Encrypt()] 属性来修饰属性,然后在运行时它将允许我生成一个名为<的附加属性strong> PropertyName_Encrypted ,其中包含加密值。
在反序列化时,将在父属性中设置加密属性值,以便属性的默认GET始终返回加密值。然后,用户将在对象上调用可选的 Decrypt()方法以获取解密值。
这样做,我遇到了一些有趣的挑战:
在序列化当前元素时,如何向文档中添加其他属性?如何获取当前元素的名称?
有没有办法可以从文档/对象中读取特定属性?对于例如说我想传递一个对称的加密密钥并读取它来加密数据,同时序列化当前元素?有什么方法可以做到吗?
以下是我到目前为止所做的事情:
我已经构建了一个加密属性,如下所示:
[AttributeUsage(AttributeTargets.Property)]
public class EncryptAttribute : Attribute
{
private readonly EncryptedFieldType _fieldType;
private readonly bool _tokenizeDisplay;
private readonly string _encryptedFieldName;
/// <summary>
///
/// </summary>
/// <param name="fieldType">The field type to encrypt. Useful if display needs to show some formatting. If no formatting is necessary, simply set to "Other".</param>
/// <param name="tokenizeDisplay">If set to true, will persist the tokenized value in the original field for display purposes.</param>
/// <param name="encryptedFieldName">Optional. If set, will save the encrypted value in the field name specified. By default all encrypted field values are stored in the corresponding _Encrypted field name. So EmailAddress field if encrypted, would have value under EmailAddress_Encrypted.</param>
public EncryptAttribute(EncryptedFieldType fieldType, bool tokenizeDisplay, string encryptedFieldName = "")
{
_fieldType = fieldType;
_tokenizeDisplay = tokenizeDisplay;
_encryptedFieldName = encryptedFieldName;
}
}
我在启动时读取了此属性,并将加密序列化程序添加到使用此属性修饰的属性中。执行此操作的代码是这样的:
var assemblies = AppDomain.CurrentDomain.GetAssemblies()
.Where(x => x.FullName.StartsWith("MongoCustomSerializer"))
.ToList();
var mapper = new Mapper();
foreach (var assembly in assemblies)
{
mapper.Map(assembly);
}
映射器只是检查文档中哪些属性具有Encrypt属性以添加序列化程序:
public sealed class Mapper
{
public void Map(Assembly assembly)
{
var encryptableTypes = assembly.GetTypes().Where(p =>
typeof(IEncryptable).IsAssignableFrom(p) && p.IsClass && !p.IsInterface && !p.IsValueType &&
!p.IsAbstract).ToList();
if (encryptableTypes.Any())
{
foreach (var encryptableType in encryptableTypes)
{
Map(encryptableType);
}
}
}
private void Map(Type documentType)
{
var properties =
documentType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
if (properties.Length <= 0)
{
return;
}
foreach (var property in properties)
{
RegisterEncrpytionSerializer(property, typeof(EncryptAttribute), documentType);
}
}
private void RegisterEncrpytionSerializer(PropertyInfo property, Type encryptAttributeType, Type documentType)
{
var encryptAttributes = property.GetCustomAttributes(encryptAttributeType, false).ToList();
if (!encryptAttributes.Any()) return;
var memberMap = BsonClassMap.LookupClassMap(documentType).GetMemberMap(property.Name);
memberMap?.SetSerializer(new EncryptionSerializer());
}
}
在我的单元测试中,我收到一条错误,指出Bson Class Map已经被冻结。即使我想找到一种绕过它的方法,这个EncryptionSerializer类如何工作到我可以写一个额外属性的地方?
很想看看有人可以提供帮助! Cheerz!
更新1 - 我能够解决FREEZE错误。似乎LookupClassMap会冻结成员和类地图信息。
This change from the link让我可以解决这个问题:
private void RegisterEncrpytionSerializer(PropertyInfo property, Type encryptAttributeType, Type documentType)
{
var encryptAttributes = property.GetCustomAttributes(encryptAttributeType, false).ToList();
if (!encryptAttributes.Any()) return;
var classMapDefinition = typeof(BsonClassMap<>);
var classMapType = classMapDefinition.MakeGenericType(documentType);
var classMap = (BsonClassMap)Activator.CreateInstance(classMapType);
classMap.AutoMap();
var memberMap = classMap.GetMemberMap(property.Name);
memberMap?.SetSerializer(new KeyVaultEncryptionSerializer(memberMap.ElementName));
}
答案 0 :(得分:0)
您是否正在使用服务来保存/检索实际调用数据库的项目?
我相信您应该将编写/读取加密值的责任转移到调用服务(即存储库实现)而不是BsonSerializer。
对我来说,加密/解密是持久层的一部分是有意义的,并且在需要时应用程序中没有处理。
您的实现仅定位要序列化的指定属性。它创造另一种财产是没有意义的。
第二个想法是,您建议的方法具有基于Decrypt()
更改值的属性可能不是一个好主意,因为它会使您的代码变得不可预测且难以阅读。让你的属性变得简单。
如果只是通过调用方法解密属性,它真正给你的代码提供了多少额外的安全性?
如果您仍然需要Decrypt()
建议您创建解密方法,并返回解密后的值GetUnencryptedCode()
等,那么它也可能是一个扩展方法但仍然不可读属性。
根据您的使用案例,您还应该考虑使用SecureString
。