使用 NHibernate 加密和解密数据

Posted

技术标签:

【中文标题】使用 NHibernate 加密和解密数据【英文标题】:Encrypting and decrypting data using NHibernate 【发布时间】:2010-10-25 06:36:56 【问题描述】:

我正在编写一个可公开访问的 Web 应用程序,其中将包含个人用户数据,例如姓名和出生日期,并且我需要以一种对可能访问原始数据的人来说很难的形式加密这些数据要解密的数据。我正在使用Fluent NHibernate、mysql 和 C# 3.5。

    用户信息的行业标准加密和解密应该使用什么方法?加密方法不应依赖于数据库。

    我如何告诉 nHibernate 对具有简单属性(如 StorageType = StorageType.Encrypted)的某些映射类进行透明加密/解密。我不介意生成的数据库表是否只有一列或两列,或者每个加密字段都有一列。根据我的发现,我应该从IUserDataType 创建我自己的数据类型,并在构造函数中加密数据。这是正确的吗?

【问题讨论】:

关于这方面的一些注释在 Ayende 的博客上:ayende.com/blog/3472/entities-dependencies-best-practices 【参考方案1】:

在真正的 Blue Peter 时尚中,这是我之前创建的一款。它依赖于提供者模式来获取加密算法,但你可以用任何你想要的替换它。

这会在您的域对象中公开一个字符串属性,但会将其保存为表示加密形式的二进制(字节数组)。在我的提供者模式代码中,Encrypt 接受一个字符串并返回一个字节数组,而 Decrypt 则相反。

[Serializable]
public class EncryptedStringType : PrimitiveType

    public EncryptedStringType() : this(new BinarySqlType()) 

    public EncryptedStringType(SqlType sqlType) : base(sqlType) 

    public override string Name
    
        get  return "String"; 
    

    public override Type ReturnedClass
    
        get  return typeof (string); 
    

    public override Type PrimitiveClass
    
        get  return typeof (string); 
    

    public override object DefaultValue
    
        get  return null; 
    

    public override void Set(IDbCommand cmd, object value, int index)
    
        if (cmd == null) throw new ArgumentNullException("cmd");
        if (value == null)
        
            ((IDataParameter)cmd.Parameters[index]).Value = null;
        
        else
        
            ((IDataParameter)cmd.Parameters[index]).Value = EncryptionManager.Provider.Encrypt((string)value);
        
    

    public override object Get(IDataReader rs, int index)
    
        if (rs == null) throw new ArgumentNullException("rs");
        var encrypted = rs[index] as byte[];
        if (encrypted == null) return null;
        return EncryptionManager.Provider.Decrypt(encrypted);
    

    public override object Get(IDataReader rs, string name)
    
        return Get(rs, rs.GetOrdinal(name));
    

    public override object FromStringValue(string xml)
    
        if (xml == null)
        
            return null;
        

        if (xml.Length % 2 != 0)
        
            throw new ArgumentException(
                "The string is not a valid xml representation of a binary content.",
                "xml");
        

        var bytes = new byte[xml.Length / 2];
        for (int i = 0; i < bytes.Length; i++)
        
            string hexStr = xml.Substring(i * 2, (i + 1) * 2);
            bytes[i] = (byte)(byte.MinValue
                              + byte.Parse(hexStr, NumberStyles.HexNumber, CultureInfo.InvariantCulture));
        

        return EncryptionManager.Provider.Decrypt(bytes);
    

    public override string ObjectToSQLString(object value, Dialect dialect)
    
        var bytes = value as byte[];
        if (bytes == null)
        
            return "NULL";
        
        var builder = new StringBuilder();
        for (int i = 0; i < bytes.Length; i++)
        
            string hexStr = (bytes[i] - byte.MinValue).ToString("x", CultureInfo.InvariantCulture);
            if (hexStr.Length == 1)
            
                builder.Append('0');
            
            builder.Append(hexStr);
        
        return builder.ToString();
    

【讨论】:

这种事情的实现是什么?在 Fluent NHibernate 中,你会使用 CustomType() 方法吗? 我也很好奇如何使用它。 想一想:blogs.msdn.com/b/alejacma/archive/2008/10/23/… 会是一个答案。【参考方案2】:

我将创建一个 EncryptionService,它使用您想要的任何密钥来加密字符串。然后我会在您的实体中创建 2 个属性。 NHibernate 与之交互的一个(加密值)和您(或其他开发人员)与之交互的另一个将自动加密这些值。

见:http://kockerbeck.blogspot.com/2009/08/fluent-nhibernate-encrypting-values.html

下面是一个示例 EncryptionService、用户实体和 UserMap。

public class User

   private readonly EncryptionService _encryptionService =
                   new EncryptionService();
   public virtual int Id  get; set; 
   public virtual DateTime? DateOfBirth
   
     get
     
       return _encryptionService.DecryptObject<DateTime?>(DateOfBirthEncrypted);
     
     set
     
       DateOfBirthEncrypted= _encryptionService.EncryptString(value.Value
                                   .ToString("yyyy-MM-dd HH:mm:ss"));
     
   
   [Obsolete("Use the 'DateOfBirth' property -- this property is only to be used by NHibernate")]
   public virtual string DateOfBirthEncrypted  get; set; 



public sealed class UserMap : ClassMap<User>

  public UserMap()
  
    WithTable("dbo.[User]");
    Id(x => x.Id, "[ID]");
    Map(x => x.DateOfBirthEncrypted, "DOB");
  

还有加密服务:

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace Services

    public class EncryptionService : IEncryptionService 
    
        /// <summary>
        /// Decrypts a string 
        /// </summary>
        /// <param name="encryptedString"></param>
        /// <returns></returns>
        public String DecryptString(string encryptedString)
        
            if (String.IsNullOrEmpty(encryptedString)) return String.Empty;

            try
            
                using (TripleDESCryptoServiceProvider cypher = new TripleDESCryptoServiceProvider())
                
                    PasswordDeriveBytes pdb = new PasswordDeriveBytes("ENTERAKEYHERE", new byte[0]);
                    cypher.Key = pdb.GetBytes(16);
                    cypher.IV = pdb.GetBytes(8);

                    using (MemoryStream ms = new MemoryStream())
                    
                        using (CryptoStream cs = new CryptoStream(ms, cypher.CreateDecryptor(), CryptoStreamMode.Write))
                        
                            byte[] data = Convert.FromBase64String(encryptedString);
                            cs.Write(data, 0, data.Length);
                            cs.Close();

                            return Encoding.Unicode.GetString(ms.ToArray());
                        
                    
                
            
            catch
            
                return String.Empty;
            
        

        /// <summary>
        /// Encrypts a string
        /// </summary>
        /// <param name="decryptedString"
        /// <returns></returns>
        public String EncryptString(string decryptedString)
        
            if (String.IsNullOrEmpty(decryptedString)) return String.Empty;

            using (TripleDESCryptoServiceProvider cypher = new TripleDESCryptoServiceProvider())
            
                PasswordDeriveBytes pdb = new PasswordDeriveBytes("ENTERAKEYHERE", new byte[0]);

                cypher.Key = pdb.GetBytes(16);
                cypher.IV = pdb.GetBytes(8);

                using (MemoryStream ms = new MemoryStream())
                
                    using (CryptoStream cs = new CryptoStream(ms, cypher.CreateEncryptor(), CryptoStreamMode.Write))
                    
                        byte[] data = Encoding.Unicode.GetBytes(decryptedString);

                        cs.Write(data, 0, data.Length);
                        cs.Close();

                        return Convert.ToBase64String(ms.ToArray());
                    
                
            
        

        /// <summary>
        /// Decrypts a given value as type of T, if unsuccessful the defaultValue is used
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="value"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        public T DecryptObject<T>(object value, T defaultValue)
        
            if (value == null) return defaultValue;

            try
            
            Type conversionType = typeof(T);

            // Some trickery for Nullable Types
            if (conversionType.IsGenericType && conversionType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
            
                conversionType = new NullableConverter(conversionType).UnderlyingType;
            

            return (T)Convert.ChangeType(DecryptString(Convert.ToString(value)), conversionType);
            
            catch 
            
                // Do nothing
            

            return defaultValue;
        
    

【讨论】:

以上是关于使用 NHibernate 加密和解密数据的主要内容,如果未能解决你的问题,请参考以下文章

NHibernate教程(19) —— 一级缓存

插入时 NHibernate 组件非空属性

具有多个结果集的NHibernate存储过程[重复]

NHibernate3剖析:Query篇之NHibernate.Linq增强查询

如何结合 NHibernate Fluent 和 WPF-NHibernate 工具包中的 VmWrapper-Classes?

NHibernate3剖析:Mapping篇之集合映射基础:List映射