EF的连接字符串在App.config中是裸奔的,所以有时候我们想把它加密,起码不能被人直接一眼看穿。
-
实现让EF可以自定义使用连接字符串
EF的连接字符串是它自动从App.config中读取的,显然无法读取加密过的连接字符串,我们这时候需要人工介入,将加密后的字符串保存在App.config中,然后读取出来,解密,再传递给EF相关功能。
假定我们在项目中已经创建了一个EF实体数据模型名为ModelInfoStore,它在解决方案浏览器中显示如下:
1.1 双击打开ModelInfoStore.Context.cs文件,可以看到如下的构造函数,在EF中要注意大量的使用的是分部类(Partial Class),这样我们扩展的部分写在另一个文件中,每次在更新模型的时候,我们扩展的部分不会被自动覆盖掉:
1.2 在项目中添加一个分部类名为InfoStoreEntities,文件名可以自定:
类的全部代码如下:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace InfoStore
{
public partial class InfoStoreEntities:DbContext
{
public InfoStoreEntities(string connStr)
: base(connStr)
{
}
}
}
实际上就是复制了上面图2中的构造函数,然后加了一个参数string connStr。
- 制作和读取加密字串
2.1 第一步需要有个加密和解密的工具,这里为了示例,先摆一个对称加密解密的类,方便演示和操作:
using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using System.IO;
namespace InfoStore
{ ///
/// AES加密解密
/// 达叔傻乐([email protected])
///
public class AesHelper
{
public string Key { set; get; }
public string IV { set; get; }
private static string _defaultKey = "JbNp%Rq?Z3`/cS%uk':k}X?p/ ,.3hR9";
private static string _defaultIV = ")&fn@C~;aF8DB-";
///
/// 构造函数
///
/// 32位密钥
/// 16位向量
public AesHelper(string key = "", string iv = "")
{
Key = FormatKeyIV(key);
IV = FormatKeyIV(iv, KeyIV.IV);
}
private enum KeyIV
{
Key,
IV
}
///
/// 对输入的字符串KEY或者IV进行格式化,使之永远是32位或者16位,过长的截短,过短的补长
///
///
///
///
private string FormatKeyIV(string original = "", KeyIV keyOrIV = KeyIV.Key)
{
if (string.IsNullOrWhiteSpace(original)) original = "";
int targetLength = keyOrIV == KeyIV.Key ? 32 : 16;
string targetPatten = keyOrIV == KeyIV.Key ? _defaultKey : _defaultIV;
int originalLength = original.Length;
string result = "";
if (originalLength >= targetLength)
{
result = original.Substring(0, targetLength);
}
else if (originalLength < targetLength)
{
result = original + targetPatten.Substring(originalLength);
}
return result;
}
public string EncryptString(string Value)
{
return EncryptString(Value, Key, IV);
}
public bool EncryptString(string Value, out string encryptedString)
{
return EncryptString(Value, Key, IV, out encryptedString);
}
public string DecryptString(string Value)
{
string x = DecryptString(Value, Key, IV);
return x;
}
public bool DecryptString(string Value, out string decryptedString)
{
bool x= DecryptString(Value, Key, IV, out decryptedString);
return x;
}
#region AES加密解密功能
///
/// AES加密字符串
///
/// 需要加密的源字符串
/// 32位密钥
/// 16位向量
/// 输出的加密后的字符串,如果加密失败其内容为错误信息
/// 加密成功返回true否则返回false
public static bool EncryptString(string Value, string key, string iv, out string encryptedString)
{
if (Value is null) Value = "";
Rijndael aes = Rijndael.Create();
try
{
byte[] bKey = Encoding.UTF8.GetBytes(key);
byte[] bIV = Encoding.UTF8.GetBytes(iv);
byte[] byteArray = Encoding.UTF8.GetBytes(Value);
using (MemoryStream mStream = new MemoryStream())
{
using (CryptoStream cStream = new CryptoStream(mStream, aes.CreateEncryptor(bKey, bIV), CryptoStreamMode.Write))
{
cStream.Write(byteArray, 0, byteArray.Length);
cStream.FlushFinalBlock();
encryptedString = Convert.ToBase64String(mStream.ToArray());
return true;
}
}
}
catch (Exception ex)
{
encryptedString = "Error on encryption: " + ex.Message;
return false;
}
finally
{
aes.Clear();
}
}
///
/// AES加密
///
/// 需要加密的字符串
/// 32位密钥
/// 16位向量
/// 返回加密后的字符串,如果失败返回“Error on encryption: ”开头的错误信息
public static string EncryptString(string Value, string key, string iv)
{
if (Value is null) Value = "";
Rijndael aes = Rijndael.Create();
try
{
byte[] bKey = Encoding.UTF8.GetBytes(key);
byte[] bIV = Encoding.UTF8.GetBytes(iv);
byte[] byteArray = Encoding.UTF8.GetBytes(Value);
using (MemoryStream mStream = new MemoryStream())
{
using (CryptoStream cStream = new CryptoStream(mStream, aes.CreateEncryptor(bKey, bIV), CryptoStreamMode.Write))
{
cStream.Write(byteArray, 0, byteArray.Length);
cStream.FlushFinalBlock();
string encryptedString = Convert.ToBase64String(mStream.ToArray());
return encryptedString;
}
}
}
catch (Exception ex)
{
string errorMessage = "Error on encryption: " + ex.Message;
return errorMessage;
}
finally
{
aes.Clear();
}
}
///
/// AES加密字符串
///
/// 需要解密的已经加密的字符串
/// 32位密钥
/// 16位向量
/// 输出的解密后的字符串,如果解密失败其内容为错误信息
/// 解密成功返回true否则返回false
public static bool DecryptString(string encryptedString, string key, string iv, out string decryptedString)
{
if (encryptedString is null) encryptedString = "";
Rijndael aes = Rijndael.Create();
try
{
byte[] bKey = Encoding.UTF8.GetBytes(key);
byte[] bIV = Encoding.UTF8.GetBytes(iv);
byte[] byteArray = Convert.FromBase64String(encryptedString);
using (MemoryStream mStream = new MemoryStream())
{
using (CryptoStream cStream = new CryptoStream(mStream, aes.CreateDecryptor(bKey, bIV), CryptoStreamMode.Write))
{
cStream.Write(byteArray, 0, byteArray.Length);
cStream.FlushFinalBlock();
decryptedString = Encoding.UTF8.GetString(mStream.ToArray());
return true;
}
}
}
catch (Exception ex)
{
decryptedString = "Error on decryption: " + ex.Message;
return false;
}
finally
{
aes.Clear();
}
}
///
/// AES解密字符串
///
/// 需要解密的加密后的字符串
/// 32位密钥
/// 16位向量
/// 返回解密后的字符串,如果失败返回“Error on decryption: ”开头的错误信息
public static string DecryptString(string encryptedString, string key, string iv)
{
if (encryptedString is null) encryptedString = "";
Rijndael aes = Rijndael.Create();
try
{
byte[] bKey = Encoding.UTF8.GetBytes(key);
byte[] bIV = Encoding.UTF8.GetBytes(iv);
byte[] byteArray = Convert.FromBase64String(encryptedString);
using (MemoryStream mStream = new MemoryStream())
{
using (CryptoStream cStream = new CryptoStream(mStream, aes.CreateDecryptor(bKey, bIV), CryptoStreamMode.Write))
{
cStream.Write(byteArray, 0, byteArray.Length);
cStream.FlushFinalBlock();
string decryptedString = Encoding.UTF8.GetString(mStream.ToArray());
return decryptedString;
}
}
}
catch (Exception ex)
{
string errorMessage = "Error on decryption: " + ex.Message;
return errorMessage;
}
finally
{
aes.Clear();
}
}
#endregion AES加密解密功能
}
}
2.2 在主窗口的构造函数里来一个临时写点代码来生成加密连接字符串:
public MainWindow()
{
InitializeComponent();
WindowStartupLocation = WindowStartupLocation.CenterScreen;
/*临时用来加密原始连接字符串,用完可删除
*/
string connStr = "metadata=res://*/ModelInfoStore.csdl|res://*/ModelInfoStore.ssdl|res://*/ModelInfoStore.msl;provider=System.Data.SqlClient;provider connection string=\"data source=localhost;initial catalog=InfoStore;persist security info=True;user id=InfoManager;password=abcd1234;MultipleActiveResultSets=True;App=EntityFramework\"";
AesHelper ah = new AesHelper();
string encConnStr = ah.EncryptString(connStr);
Clipboard.SetText(encConnStr);
Close();
}
一运行,加密后的连接字符串就到剪贴板了,用完后上面那段临时用途的代码就没用了,删除。
2.3 打开App.config,加一段如下:
value里的内容是刚才我们生成的加密字符串。
原本EF里自动生成的红框中的connectionStrings节就可以删除了。不过我这里特意用的integrated security,不包含用户名和密码信息,所以就不删除了,便于对EF配置的修改,比如更新模型等。
2.4 添加一个类,我这里命名为IsHelper,里面如下函数:
public static string GetConnectionString()
{
var encConnStr = ConfigurationSettings.AppSettings["EFConnectionString"];
AesHelper ah = new AesHelper();
string decConnStr = ah.DecryptString(encConnStr);
return decConnStr;
}
如图5:
- 使用加密的连接字符串进行数据查询,非常简单:
public static void RemoveEntry(CardCategory entry)
{
using (var ise = new InfoStoreEntities(IsHelper.GetConnectionString()))
{
//remove the entry
ise.Entry(entry).State = System.Data.Entity.EntityState.Deleted;
ise.SaveChanges();
}
}
跟原来的不同是在实例化InfoStoreEntities的时候带上连接字符串,由IsHelper.GetConnectionString()取得。
重要注意事项
本文只是演示了一个改EF连接字符串的过程和方法,在生产环境中,实际该方法并不是非常安全,因为.NET的程序的反编译太过简单,特别是这种把加密用的类以及加密用的密钥之类都写在源代码中的方式,在想破解它的程序员面前不堪一击。所以,要真正实现加密,最好用CS的开发模式,把涉及加密部分都放在服务端,不让普通用户看到。
达叔傻乐([email protected])