本篇博客会有两篇代码,均为C# 编写而成。其中一篇为先行的各种加密技术如何使用(转载出处会在文末注明);另一篇为利用以上加密技术做出的一个类库,可以使程序捆绑在运行此程序的硬件并限定使用时间,十分高效和耐用。
高级加密标准
密码学中的高级加密标准(Advanced Encryption Standard,AES),又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。
这个标准用来替代原先的DES(Data Encryption Standard),已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院 (NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一 [1] 。
该算法为比利时密码学家Joan Daemen和Vincent Rijmen所设计,结合两位作者的名字,以Rijdael之名命之,投稿高级加密标准的甄选流程。(Rijdael的发音近于 “Rhine doll”。)
高级加密标准算法从很多方面解决了令人担忧的问题。实际上,攻击数据加密标准的那些手段对于高级加密标准算法本身并没有效果。如果采用真正的128位加密技术甚至256位加密技术,蛮力攻击要取得成功需要耗费相当长的时间。
虽然高级加密标准也有不足的一面,但是,它仍是一个相对新的协议。因此,安全研究人员还没有那么多的时间对这种加密方法进行破解试验。我们可能会随时发现一种全新的攻击手段会攻破这种高级加密标准。至少在理论上存在这种可能性。 [2]
DES全称为Data Encryption Standard,即数据加密标准,是一种使用密钥加密的块算法,1977年被美国联邦政府的国家标准局确定为联邦资料处理标准(FIPS),并授权在非密级政府通信中使用,随后该算法在国际上广泛流传开来。需要注意的是,在某些文献中,作为算法的DES称为数据加密算法(Data Encryption Algorithm,DEA),已与作为标准的DES区分开来。
DES算法的入口参数有三个:Key、Data、Mode。其中Key为7个字节共56位,是DES算法的工作密钥;Data为8个字节64位,是要被加密或被解密的数据;Mode为DES的工作方式,有两种:加密或解密。
Base64编码的思想是是采用64个基本的ASCII码字符对数据进行重新编 码。它将需要编码的数据拆分成字节数组。
以3个字节为一组。按顺序排列24 位数据,再把这24位数据分成4组,即每组6位。再在每组的的最高位前补两个0凑足一个字节。
这样就把一个3字节为一组的数据重新编码成了4个字节。当所 要编码的数据的字节数不是3的整倍数,也就是说在分组时最后一组不够3个字节。
这时在最后一组填充1到2个0字节。并在最后编码完成后在结尾添加1到2个 “=”。
将对ABC进行BASE64编码:
1、首先取ABC对应的ASCII码值
A(65)B(66)C(67);
2、再取二进制值A(01000001)B(01000010)C(01000011);
3、然后把这三个字节的二进制码接起来(010000010100001001000011);
4、 再以6位为单位分成4个数据块,并在最高位填充两个0后形成4个字节的编码后的值,(00010000)(00010100)(00001001)(00000011),
其中蓝色部分为真实数据;
5、再把这四个字节数据转化成10进制数得(16)(20)(9)(3);
6、最后根据BASE64给出的64个基本字符表,查出对应的ASCII码字符(Q)(U)(J)(D),这里的值实际就是数据在字符表中的索引。
注:BASE64字符 表:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
Base64编码解码详细讲解:
比如我有一个从AES 128位加密技术加密过的一个有 16 个byte型元素 的 byte 数组 resultArray ,
114 246 183 248 219 202 232 40 76 96 178 55 201 18 66 78
Base64编码的结果为:
cva3+NvK6ChMYLI3yRJCTg==
114 二进制 对应 01110010
246 二进制 对应 11110110
183 二进制 对应10110111
然后组合起来 01110010 11110110 10110111 去掉空格,即 011100101111011010110111
每六个分为一组:
011100 101111 011010 110111
分完组后,每六个新组前加上两个 0 , 即
00011100 00101111 00011010 00110111
对应十进制为:
28 47 26 55
BASE64字符 表:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ 是从0到63 编号的 ,共 64个,即:
28 代表 c 47代表 v 26 代表 a 55代表 3 以此类推。
下面位上面讲到没讲到的各种加密的实现:
其中大家可以自行体会下AES加密后生成的128位 16个byte 元素 的 byte 数组 被 base64 编码为一串奇怪字符的行为,哈哈哈。
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
//using System.Web.Security;
namespace 字符串加密解密AES_DES
{
///
/// 加密工具类
///
public class EncryptHelper
{
//默认密钥
private static string AESKey = "[45/*YUIdse..e;]";
private static string DESKey = "[&HdN72]";
static void Main(string[] args)
{
//Console.WriteLine(AESEncrypt(DateTime.Now.ToString("yyyyMMddHHmmss")));
//Console.WriteLine(DESEncrypt("TED"));
//Console.WriteLine(DESDecrypt("755g1PG3T30="));
Console.WriteLine(MD5("TED"));
//Console.WriteLine(AESDecrypt("cva3 + NvK6ChMYLI3yRJCTg =="));
}
///
/// AES加密
///
public static string AESEncrypt(string value, string _aeskey = null)
{
if (string.IsNullOrEmpty(_aeskey))
{
_aeskey = AESKey;
}
byte[] keyArray = Encoding.UTF8.GetBytes(_aeskey);
byte[] toEncryptArray = Encoding.UTF8.GetBytes(value);
RijndaelManaged rDel = new RijndaelManaged();
rDel.Key = keyArray;
rDel.Mode = CipherMode.ECB;
rDel.Padding = PaddingMode.PKCS7;
ICryptoTransform cTransform = rDel.CreateEncryptor();
byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
for (int i =0; i < resultArray.Length;i++)
{
Console.Write(resultArray[i]+" ");
}
Console.WriteLine();
return Convert.ToBase64String(resultArray, 0, resultArray.Length);
//C#中的Convert.ToBase64String()方法用于将8位无符号整数数组的值转换为以基数64位编码的等效字符串表示形式。
/*
Base64编码的思想是是采用64个基本的ASCII码字符对数据进行重新编 码。它将需要编码的数据拆分成字节数组。
以3个字节为一组。按顺序排列24 位数据,再把这24位数据分成4组,即每组6位。再在每组的的最高位前补两个0凑足一个字节。
这样就把一个3字节为一组的数据重新编码成了4个字节。当所 要编码的数据的字节数不是3的整倍数,也就是说在分组时最后一组不够3个字节。
这时在最后一组填充1到2个0字节。并在最后编码完成后在结尾添加1到2个 “=”。
将对ABC进行BASE64编码:
1、首先取ABC对应的ASCII码值
。A(65)B(66)C(67);
2、再取二进制值A(01000001)B(01000010)C(01000011);
3、然后把这三个字节的二进制码接起来(010000010100001001000011);
4、 再以6位为单位分成4个数据块,并在最高位填充两个0后形成4个字节的编码后的值,(00010000)(00010100)(00001001)(00000011),
其中蓝色部分为真实数据; 5、再把这四个字节数据转化成10进制数得(16)(20)(9)(3);
6、最后根据BASE64给出的64个基本字符表,查出对应的ASCII码字符(Q)(U)(J)(D),这里的值实际就是数据在字符表中的索引。
注:BASE64字符 表:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
*/
/*
比如说这里的 resultArray , 114 246 183 248 219 202 232 40 76 96 178 55 201 18 66 78
cva3+NvK6ChMYLI3yRJCTg==
114对应 01110010
246 对应 11110110
183对应10110111
然后组合起来 01110010 11110110 10110111 即 011100101111011010110111
每六个分为一组:
011100 101111 011010 110111
分完组后,每六个新组前加上两个 0 , 即
00011100 00101111 00011010 00110111
对应十进制为:
28 47 26 55
BASE64字符 表:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ 从0到63 编号 ,共 64个,即:
28 代表 c 47代表 v 26 代表 a 55代表 3 以此类推
*/
}
/*
AES 高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法
Rijndael(读作rain-dahl)是由美国国家标准与技术协会(NIST)所选的高级加密标准(AES)的候选算法。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。
Rijndael 算法首先是一个密钥分组加密的算法,通过置换(permutations )和替换(substitutions)迭代加密,进过多轮操作形成密文。
AES算是Rijndael算法的一种特殊实现,选的分组为128bit(16字节),密钥可以使用128、192 和 256bit三种,而Rijndael使用的密钥和区块长度可以是32位的整数倍,
以128位为下限,256比特为上限。加密过程中使用的密钥是由Rijndael密钥生成方案产生。
AES加密过程是在一个4×4的字节矩阵上运作,这个矩阵又称为“状态(state)”,其初值就是一个明文区块(矩阵中一个元素大小就是明文区块中的一个Byte)。
(Rijndael加密法因支持更大的区块,其矩阵行数可视情况增加)加密时,各轮AES加密循环(除最后一轮外)均包含4个步骤:
AddRoundKey — 矩阵中的每一个字节都与该次轮秘钥(round key)做XOR运算;每个子密钥由密钥生成方案产生。
SubBytes — 通过非线性的替换函数,用查找表的方式把每个字节替换成对应的字节。
ShiftRows — 将矩阵中的每个横列进行循环式移位。
MixColumns — 为了充分混合矩阵中各个直行的操作。这个步骤使用线性转换来混合每列的四个字节。
*/
///
/// AES解密
///
public static string AESDecrypt(string value, string _aeskey = null)
{
try
{
if (string.IsNullOrEmpty(_aeskey))
{
_aeskey = AESKey;
}
byte[] keyArray = Encoding.UTF8.GetBytes(_aeskey);
byte[] toEncryptArray = Convert.FromBase64String(value);
RijndaelManaged rDel = new RijndaelManaged();
rDel.Key = keyArray;
rDel.Mode = CipherMode.ECB;
rDel.Padding = PaddingMode.PKCS7;
ICryptoTransform cTransform = rDel.CreateDecryptor();
byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
return Encoding.UTF8.GetString(resultArray);
}
catch
{
return string.Empty;
}
}
/*
解码过程就是把4个字节再还原成3个字节再根据不同的数据形式把字节数组重新整理 成数据。
*/
///
/// DES加密
///
public static string DESEncrypt(string value, string _deskey = null)
{
if (string.IsNullOrEmpty(_deskey))
{
_deskey = DESKey;
}
byte[] keyArray = Encoding.UTF8.GetBytes(_deskey);
byte[] toEncryptArray = Encoding.UTF8.GetBytes(value);
DESCryptoServiceProvider rDel = new DESCryptoServiceProvider();
rDel.Key = keyArray;
rDel.Mode = CipherMode.ECB;
rDel.Padding = PaddingMode.PKCS7;
ICryptoTransform cTransform = rDel.CreateEncryptor();
byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
return Convert.ToBase64String(resultArray, 0, resultArray.Length);
}
///
/// DES解密
///
public static string DESDecrypt(string value, string _deskey = null)
{
try
{
if (string.IsNullOrEmpty(_deskey))
{
_deskey = DESKey;
}
byte[] keyArray = Encoding.UTF8.GetBytes(_deskey);
byte[] toEncryptArray = Convert.FromBase64String(value);
DESCryptoServiceProvider rDel = new DESCryptoServiceProvider();
rDel.Key = keyArray;
rDel.Mode = CipherMode.ECB;
rDel.Padding = PaddingMode.PKCS7;
ICryptoTransform cTransform = rDel.CreateDecryptor();
byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
return Encoding.UTF8.GetString(resultArray);
}
catch
{
return string.Empty;
}
}
public static string MD5(string value)
{
byte[] result = Encoding.UTF8.GetBytes(value);
MD5 md5 = new MD5CryptoServiceProvider();
byte[] output = md5.ComputeHash(result);
return BitConverter.ToString(output).Replace("-", "");
}
public static string HMACMD5(string value, string hmacKey)
{
HMACSHA1 hmacsha1 = new HMACSHA1(Encoding.UTF8.GetBytes(hmacKey));
byte[] result = System.Text.Encoding.UTF8.GetBytes(value);
byte[] output = hmacsha1.ComputeHash(result);
return BitConverter.ToString(output).Replace("-", "");
}
///
/// base64编码
///
///
public static string Base64Encode(string value)
{
string result = Convert.ToBase64String(Encoding.Default.GetBytes(value));
return result;
}
///
/// base64解码
///
///
public static string Base64Decode(string value)
{
string result = Encoding.Default.GetString(Convert.FromBase64String(value));
return result;
}
}
}
***
***:
using System;
using System.IO;
using System.Management;
using System.Security.Cryptography;
using System.Text;
namespace BindingComputer_ConfirmingTime
{
public static class BindComputer_RestrictTime
{
private static string AESKey = "[45/*YUIdse..e;]"; //C# 常用字符串加密解密方法
//private static string DESKey = "[&HdN72]";
private static string AESEncrypt_Write_Code(string value, string _aeskey = null)
{
if (string.IsNullOrEmpty(_aeskey))
{
_aeskey = AESKey;
}
byte[] keyArray = Encoding.UTF8.GetBytes(_aeskey);
byte[] toEncryptArray = Encoding.UTF8.GetBytes(value);
RijndaelManaged rDel = new RijndaelManaged();
rDel.Key = keyArray;
rDel.Mode = CipherMode.ECB;
rDel.Padding = PaddingMode.PKCS7;
ICryptoTransform cTransform = rDel.CreateEncryptor();
byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
return Convert.ToBase64String(resultArray, 0, resultArray.Length);
}
private static string AESDecrypt_Read_Code(string value, string _aeskey = null)
{
try
{
if (string.IsNullOrEmpty(_aeskey))
{
_aeskey = AESKey;
}
byte[] keyArray = Encoding.UTF8.GetBytes(_aeskey);
byte[] toEncryptArray = Convert.FromBase64String(value);
RijndaelManaged rDel = new RijndaelManaged();
rDel.Key = keyArray;
rDel.Mode = CipherMode.ECB;
rDel.Padding = PaddingMode.PKCS7;
ICryptoTransform cTransform = rDel.CreateDecryptor();
byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
return Encoding.UTF8.GetString(resultArray);
}
catch
{
return string.Empty;
}
}
public static bool Initialize_Device_Confirm_Time(string StartUpPath, int AllowedDays)
{
FileStream read_on_off = new FileStream(StartUpPath + "/on_off/on_off.txt", FileMode.Open);
StreamReader sr_on_off = new StreamReader(read_on_off);
string on_off_line = sr_on_off.ReadLine();
sr_on_off.Close();
string DiskDriveID = string.Empty;
ManagementClass mc = new ManagementClass("Win32_DiskDrive");
ManagementObjectCollection moc = mc.GetInstances();
foreach (ManagementObject mo in moc)
{
DiskDriveID += mo.Properties["Model"].Value.ToString();
break;
}
mc.Dispose();
moc.Dispose();
//MessageBox.Show(DiskDriveID);
if (Convert.ToInt32(on_off_line) == 0) //证明是第一次运行该程序
{
//CD_Motion.MD5.WriteENpwd(DiskDriveID);
FileStream write_code = new FileStream(StartUpPath + "/on_off/ID.txt", FileMode.Create);
StreamWriter sw_write_code = new StreamWriter(write_code);
sw_write_code.WriteLine(AESEncrypt_Write_Code(DiskDriveID));
sw_write_code.Close();
FileStream write_time = new FileStream(StartUpPath + "/on_off/Time.txt", FileMode.Create);
StreamWriter sw_write_time = new StreamWriter(write_time);
sw_write_time.WriteLine(AESEncrypt_Write_Code(DateTime.Now.ToShortDateString()));
sw_write_time.Close();
FileStream write_on_off = new FileStream(StartUpPath + "/on_off/on_off.txt", FileMode.Create);
StreamWriter sw_on_off = new StreamWriter(write_on_off);
sw_on_off.WriteLine("1");
sw_on_off.Close();
}
FileStream read_code = new FileStream(StartUpPath + "/on_off/ID.txt", FileMode.Open);
StreamReader sr_code = new StreamReader(read_code);
string code_line = sr_code.ReadLine();
sr_code.Close();
string temp = AESDecrypt_Read_Code(code_line);
FileStream read_time = new FileStream(StartUpPath + "/on_off/Time.txt", FileMode.Open);
StreamReader sr_time = new StreamReader(read_time);
string time = sr_time.ReadLine();
sr_time.Close();
string temp_time = AESDecrypt_Read_Code(time);
temp_time += '/';
StringBuilder time_sb = new StringBuilder();
int temp_Year = 0;
int temp_Month = 0;
int temp_Date = 0;
int count = 0;
foreach (char c in temp_time)
{
if (c == '/')
{
if (count == 0)
{
count++;
temp_Year = Convert.ToInt32(time_sb.ToString());
time_sb.Clear();
}
else if (count == 1)
{
count++;
temp_Month = Convert.ToInt32(time_sb.ToString());
time_sb.Clear();
}
else
{
temp_Date = Convert.ToInt32(time_sb.ToString());
time_sb.Clear();
}
}
else
{
time_sb.Append(c.ToString());
}
}
if (temp != DiskDriveID || (System.DateTime.Now.Year * 365 + System.DateTime.Now.Month * 30 + System.DateTime.Now.Day - temp_Year * 365 - temp_Month * 30 - temp_Date) > AllowedDays)
{
return false;
}
else
{
return true;
}
}
}
}
大体的思路是首先获取到本地磁盘的序列号,然后交给AES加密出一个128 位 16 个 byte 元素的byte数组,然年后交给base64编码为一个定长字符串并保留在本地磁盘(启动文件夹里)。
程序在启动时,会利用base64解码定长字符串,还原出一个128 位 16 个 byte 元素的byte数组,然后交给AES 解密出文本。
确立时间也是如此,获取到第一次运行的日期值,以后每次运行程序时都会将现有时间与从磁盘中解密出的时间进行比对,即可确定运行有效期限。
给大家看下我的保存在本地启动文件夹的加密字符串,一个on_off文件夹的on_off.txt文件。
其实这个on_off.txt 就是一个后门,你可以将其里面的内容置为 0 , 这样,程序就会以再一次启动时的时间为起始时间,并且获取再次启动时的电脑的硬盘序列号,并更新在以下 txt 文件中。
这样你就可以重新获得使用时间,并得以更换程序运行的电脑,哈哈哈哈哈
肯定会有人问为什么不适用CPU序列号或者MAC地址(以太网地址(Ethernet Address)或物理地址(Physical Address),位于数据链路层,就是网络设备(比如说电脑)的网卡的地址)作为电脑的唯一ID。
首先可以明确告诉大家的是,
CPU序列号 不是唯一的,因为为了防止黑客攻击,CPU厂家 已经不再单独为每块CPU设定唯一序列号了,取而代之的好像是生产批次号。
而MAC地址更是可以随时变换的,通过软件可以快速抹掉并重写电脑的MAC地址。
但是硬盘呢,就很少有人能改得了了,这是真的。
再说如果真的有人把电脑的硬盘换掉,以实现想要运行被以上加密类库所保护的程序的运行,这是不可能的,因为程序每次运行都会读硬盘序列号啊,与保存的序列号不一致,就会返回false,自然跑不了程序了,哈哈哈哈哈。
那么给大家看下如何在你的程序的入口处使用以上类库。
首先编译以下类库,生成动态链接库,然后将此DLL引入到你的项目中,引入命名空间,该类库的方法都是静态的,不需要创建对象,并且只给外界留下一个可被访问的public 静态方法
public static bool Initialize_Device_Confirm_Time(string StartUpPath, int AllowedDays){}
以下为如何在你的i项目中加入此代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using BindingComputer_ConfirmingTime;
namespace WinDalsa
{
static class Program
{
///
/// 应用程序的主入口点。
///
[STAThread]
static void Main()
{
if (BindingComputer_ConfirmingTime.BindComputer_RestrictTime.Initialize_Device_Confirm_Time(Application.StartupPath, 100))
{
bool createNew;
using (System.Threading.Mutex m = new System.Threading.Mutex(true, "Global\\" + Application.ProductName, out createNew))
{
if (createNew)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form_Main());
}
else
{
MessageBox.Show("程序已经运行");
}
}
}
else
{
MessageBox.Show("程序已到期或者您更换了配套电脑");
}
}
}
}
以上为你的项目设置有限期100天,并且保护你的项目中途不被更换电脑。