在实际开发中,稍有规模的团队都会碰到对线上数据库帐号权限控制的问题:比如要求对连接字符串加密,目的有两方面,其一是对数据库安全做进一步保障,其二是为了实现线上正式环境的数据库帐号对普通开发人员不可见,以避免各种误操作以及私下修改线上数据等情况。
我们做如下准备工作:
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 { get; set; }
}
这里的TestEntities实体对象继承自DbContext对象,构造函数继承自父类DbContext,如果VS里安装了.net reflector插件F12转到定义就可以看到DbContext类里读取的是App.Config中的
我们考虑的连接字符串加密是:使用对称加密算法把连接字符串进行加密后放入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也无需配置
此时我们连接字符串加密的准备工作已完成。
我们使用上篇.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;
///
/// 对称加密类的构造函数
///
internal SymmetricMethod()
{
mobjCryptoService = new RijndaelManaged();
Key = "FefZ$@pAedzg#HjT!QcM7JQqwOcAkCm7x2pZjBUMSocM9v6#%AP9HZg7OZ^ogG!x";
}
///
/// 获得密钥
///
/// 密钥
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);
}
///
/// 获得初始向量IV
///
/// 初试向量IV
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);
}
///
/// 加密方法
///
/// 待加密的串
/// 经过加密的串
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);
}
///
/// 解密方法
///
/// 待解密的串
/// 经过解密的串
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(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项目的配置文件中,为了和原来的字符串进行区分,我们放置到
我们在TestProject解决方案的控制台项目中引用该dll
可以看到对外暴漏的方法正是我们想要暴漏出去的。
接下来,我们使用该方法创建EF实体对象:
调试从数据库获取数据成功。
接下来,控制台项目配置文件中的