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

时间:2018-01-30 01:28:49

标签: c# mongodb mongodb-.net-driver

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

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

这样做,我遇到了一些有趣的挑战:

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

  2. 有没有办法可以从文档/对象中读取特定属性?对于例如说我想传递一个对称的加密密钥并读取它来加密数据,同时序列化当前元素?有什么方法可以做到吗?

  3. 以下是我到目前为止所做的事情:

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

      }

    4. 在我的单元测试中,我收到一条错误,指出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));
          }
      

1 个答案:

答案 0 :(得分:0)

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

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

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

您的实现仅定位要序列化的指定属性。它创造另一种财产是没有意义的。

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

如果只是通过调用方法解密属性,它真正给你的代码提供了多少额外的安全性?

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

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