MongoDB 序列化 C# - 添加其他加密字段属性



我正在尝试用 c# 编写一个 MongoDb 序列化程序,它允许我通过[Encrypt()]属性装饰属性,然后在运行时它将允许我生成一个名为PropertyName_Encrypted的附加属性,该属性将包含加密值。

在反序列化时,将在父属性中设置加密属性值,以便属性的默认 GET 始终返回加密值。然后,用户将在对象上调用可选的Decrypt()方法来获取解密的值。

在此过程中,我遇到了一些有趣的挑战:

  1. 序列化当前元素时如何向文档添加其他属性?如何获取当前元素的名称?

  2. 有没有办法从文档/对象中读取特定属性?例如,假设我想传递一个对称加密密钥并读取该密钥以在序列化当前元素时加密数据?有什么办法可以做到这一点吗?

以下是我到目前为止所做的工作:

  1. 我已经构建了一个加密属性,如下所示:

    [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;
    }
    }
    
  2. 我在启动时读取此属性,并将加密序列化程序添加到使用此属性修饰的属性中。这样做的代码是这样的:

    var assemblies = AppDomain.CurrentDomain.GetAssemblies()
    .Where(x => x.FullName.StartsWith("MongoCustomSerializer"))
    .ToList();
    var mapper = new Mapper();
    foreach (var assembly in assemblies)
    {
    mapper.Map(assembly);
    }
    
  3. 映射器只需检查文档中的哪些属性具有 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 类映射已被冻结。即使我想出一种方法来绕过它,这个 EncryptionSerializer 类将如何工作到我可以编写附加属性的位置?

很想看看是否有人可以提供帮助!

更新1- 我能够处理冻结错误。看起来 LookupClassMap 冻结了成员和类映射信息。

链接中的此更改使我能够处理该问题:

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

您是否使用服务来保存/检索实际调用数据库的项目?

我认为您应该将写入/读取加密值的责任转移到调用服务(即存储库实现)而不是 BsonSerializer。

对我来说,加密/解密是持久性层的一部分,并且在需要时不会在应用程序中处理,这对我来说是有意义的。

您的实现仅面向要序列化的指定属性。它创建另一个属性是没有意义的。

第二个想法是,您建议的基于Decrypt()更改值的属性的方法可能不是一个好主意,因为它会使您的代码不可预测且难以阅读。让你的属性变得简单。

如果无论如何都可以通过调用方法来解密属性,那么代码中确实会给您带来什么额外的安全性?

如果您仍然需要Decrypt()建议您创建返回解密值(如GetUnencryptedCode()等)的解密方法,它也可以是一个扩展方法,但仍然不是一个可读属性。

您还应该根据您的用例考虑使用SecureString

最新更新