EF连接字符串加密及封装

EF连接字符串加密及封装

在实际开发中,稍有规模的团队都会碰到对线上数据库帐号权限控制的问题:比如要求对连接字符串加密,目的有两方面,其一是对数据库安全做进一步保障,其二是为了实现线上正式环境的数据库帐号对普通开发人员不可见,以避免各种误操作以及私下修改线上数据等情况。

一、EF连接字符串加密——构造函数重载

我们做如下准备工作:

1.创建一个名称为“TestProject”的解决方案

2.在解决方案中添加一个名称为“ConsoleApplication1”控制台子项目、一个名称为“Test.DB”类库子项目

3.在“Test.DB”子项目中添加“ADO.NET实体数据模型”,命名为“TestDB.edmx”,然后在弹出的“实体模型向导”中,选择从数据库生成,新建连接→连接属性,输入数据库服务器的ip、用户名、密码、指定数据库,然后选择“是,在连接字符串中包含敏感数据”,将App.Config中的实体链接另存为TestEntities,然后勾选数据库中的表

创建成功以后如图:


打开TestEntities.Context.cs

可以看到如下代码:

    public partial class TestEntities : DbContext
    {
        public TestEntities()
            : base("name=TestEntities")
        {
        }
    
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            throw new UnintentionalCodeFirstException();
        }
    
        public virtual DbSet<p2p_token> p2p_token { get; set; }
    }

这里的TestEntities实体对象继承自DbContext对象,构造函数继承自父类DbContext,如果VS里安装了.net reflector插件F12转到定义就可以看到DbContext类里读取的是App.Config中的<connectionStrings>节的配置。

我们考虑的连接字符串加密是:使用对称加密算法把连接字符串进行加密后放入App.Config配置文件中,创建数据库实体对象时先对连接字符串解密,然后使用解密后的连接字符串进行创建。

由于创建数据库实体对象TestEntities时向导自动创建无参的构造函数调用的实际是父类DbContext中的构造方法,我们没办法对父类DbContext做修改,那么就只能对TestEntities类的构造函数进行重载,但是该类是是通过向导自动生成的,直接在类里修改明显不合适(重新自动生成时我们做的修改会被覆盖掉),但是可以看到TestEntities类修饰符是带有partial关键字的,即该类是一个分部类,我们可以新建一个同名分部类来对该类进行扩展:


在Test.DB子项目下新建类TestEntities,内容如下:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Test.DB
{
    public partial class TestEntities : DbContext
    {
        public TestEntities(string sqlconn)
            : base(sqlconn)
        {

        }
    }
}

该类包含一个有参构造函数,在使用该构造函数创建EF实体对象时不会直接从App.Config中读取,而是由我们指定传入。

我们在控制台项目ConsoleApplication1中使用nuget添加对EF的引用,然后引用Test.DB、System.Configuration,然后测试


即,使用我们重载的方法创建的数据库实体对象可以正常进行查询操作(ConsoleApplication1子项目下的App.Config也无需配置<connectionStrings>节,因为该构造方法此时不从配置文件中读取连接字符串)。

此时我们连接字符串加密的准备工作已完成。

二、EF连接字符串加密——加密与封装

我们使用上篇.net reactor的使用中创建的项目来加密解密连接字符串。但是怎么达到封装的目的呢?

为了达到封装的目的,加密类方法的修饰符要做下调整,由public调整为internal,即限制本项目内使用:  

using System;  
using System.Collections.Generic;  
using System.IO;  
using System.Linq;  
using System.Security.Cryptography;  
using System.Text;  
using System.Threading.Tasks;  
  
namespace PP.Encrypt  
{  
    //密码生成:https://suijimimashengcheng.51240.com/  
    internal class SymmetricMethod  
    {  
        private SymmetricAlgorithm mobjCryptoService;  
        private string Key;  
        /// <summary>     
        /// 对称加密类的构造函数     
        /// </summary>     
        internal SymmetricMethod()  
        {  
            mobjCryptoService = new RijndaelManaged();  
            Key = "FefZ$@pAedzg#HjT!QcM7JQqwOcAkCm7x2pZjBUMSocM9v6#%AP9HZg7OZ^ogG!x";  
        }  
        /// <summary>     
        /// 获得密钥     
        /// </summary>     
        /// <returns>密钥</returns>     
        private byte[] GetLegalKey()  
        {  
            string sTemp = Key;  
            mobjCryptoService.GenerateKey();  
            byte[] bytTemp = mobjCryptoService.Key;  
            int KeyLength = bytTemp.Length;  
            if (sTemp.Length > KeyLength)  
                sTemp = sTemp.Substring(0, KeyLength);  
            else if (sTemp.Length < KeyLength)  
                sTemp = sTemp.PadRight(KeyLength, ' ');  
            return ASCIIEncoding.ASCII.GetBytes(sTemp);  
        }  
        /// <summary>     
        /// 获得初始向量IV     
        /// </summary>     
        /// <returns>初试向量IV</returns>     
        private byte[] GetLegalIV()  
        {  
            string sTemp = "XUYXqW8QF2fqyytf0ZwU6Vv1cbNI3qU!zVzohQ0ptAug#&uJ3b^rEKkrckH1LE3i";  
            mobjCryptoService.GenerateIV();  
            byte[] bytTemp = mobjCryptoService.IV;  
            int IVLength = bytTemp.Length;  
            if (sTemp.Length > IVLength)  
                sTemp = sTemp.Substring(0, IVLength);  
            else if (sTemp.Length < IVLength)  
                sTemp = sTemp.PadRight(IVLength, ' ');  
            return ASCIIEncoding.ASCII.GetBytes(sTemp);  
        }  
        /// <summary>     
        /// 加密方法     
        /// </summary>     
        /// <param name="Source">待加密的串</param>     
        /// <returns>经过加密的串</returns>     
        internal string Encrypto(string Source)  
        {  
            byte[] bytIn = UTF8Encoding.UTF8.GetBytes(Source);  
            MemoryStream ms = new MemoryStream();  
            mobjCryptoService.Key = GetLegalKey();  
            mobjCryptoService.IV = GetLegalIV();  
            ICryptoTransform encrypto = mobjCryptoService.CreateEncryptor();  
            CryptoStream cs = new CryptoStream(ms, encrypto, CryptoStreamMode.Write);  
            cs.Write(bytIn, 0, bytIn.Length);  
            cs.FlushFinalBlock();  
            ms.Close();  
            byte[] bytOut = ms.ToArray();  
            return Convert.ToBase64String(bytOut);  
        }  
        /// <summary>     
        /// 解密方法     
        /// </summary>     
        /// <param name="Source">待解密的串</param>     
        /// <returns>经过解密的串</returns>     
        internal string Decrypto(string Source)  
        {  
            byte[] bytIn = Convert.FromBase64String(Source);  
            MemoryStream ms = new MemoryStream(bytIn, 0, bytIn.Length);  
            mobjCryptoService.Key = GetLegalKey();  
            mobjCryptoService.IV = GetLegalIV();  
            ICryptoTransform encrypto = mobjCryptoService.CreateDecryptor();  
            CryptoStream cs = new CryptoStream(ms, encrypto, CryptoStreamMode.Read);  
            StreamReader sr = new StreamReader(cs);  
            return sr.ReadToEnd();  
        }  
    }  
}  

我们再创建一个类DBService,用于创建EF数据库实体对象对象:


该类内容如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace PP.Encrypt
{
    public class DBService
    {
        public T GetPPEntities<T>(string encrypt_conn_str) where T : class
        {
            var conn_str = new SymmetricMethod().Decrypto(encrypt_conn_str);
            return (T)Activator.CreateInstance(typeof(T), conn_str); 
        }
    }
}

此时,我们生成PP.Encrypt项目,将bin/Debug(bin/Release)目录下的PP.Encrypt.dll文件复制出来(此时可以使用上一篇文章中的方法对该dll进行加密混淆等保护处理),在我们第一步创建的TestProject解决方案中引用该dll即可使用。

我们在PP.Encrypt解决方案中再添加一个控制台子项目ConsoleApplication1,用于加密连接字符串,将SymmetricMethod类复制一份到新建的ConsoleApplication1中:

在控制台项目Program中对连接字符串进行加密:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var conn_str = @"metadata=res://*/PPDB.csdl|res://*/PPDB.ssdl|res://*/PPDB.msl;provider=System.Data.SqlClient;provider connection string='data source=.;initial catalog=TestDB;persist security info=True;user id=test;password=123456;multipleactiveresultsets=True;application name=EntityFramework'";
            var en_conn_str = new SymmetricMethod().Encrypto(conn_str);
            Console.Write(en_conn_str);
            Console.Read();
        }
    }

我们将加密的结果en_conn_str放到TestProject解决方案ConsoleApplication1项目的配置文件中,为了和原来的字符串进行区分,我们放置到<appSettings>节中:



三、测试验证

我们在TestProject解决方案的控制台项目中引用该dll



可以看到对外暴漏的方法正是我们想要暴漏出去的。

接下来,我们使用该方法创建EF实体对象:


调试从数据库获取数据成功。

接下来,控制台项目配置文件中的<connectionStrings>节就可以干掉了,因为读的是<appSettings>节中的加密字符串,当然Test.DB子项目App.Config中的<connectionStrings>节还是要保留着的,用于从数据库更新表结构。


至此,EF连接字符串加密及封装已经介绍完毕。如有错误,请留言指正。










猜你喜欢

转载自blog.csdn.net/shujudeliu/article/details/80692403