1.目标
在不降低接口访问速度的情况下,对用户的身份和请求的参数进行验证,以保证接口的安全。通过添加身份验证和数字签名的方法提高接口安全性,防止数据被篡改和信息泄露。
2.解决方案
用户登录后获取到token,用户每次请求除了带上所需的参数外,还需带上token。此外,需要把参数和token用MD5转化成长度最大为32的字符串str,然后用AES对称加密算法把转化成的str进行加密,记为AESStr。服务器接收到传递的参数、token和加密后的AESStr后,先对token进行验证,确保身份符合。然后把参数和token用MD5转成str1,然后用AES解密AESStr成str,从而判断传递的参数是否有被修改。
本方案解决了用户身份认证和数字签名这两个问题。数字签名要解决的问题是验证数据是否被修改。
3.方案流程
3.1 MD5转换
若参数过多,数据量过大,直接用AE加密算法对参数进行加密会造成加密性能下降,加密后的字符串过长,数据传输过多而导致访问速度下降的问题。将data和token转换成长度为32的字符串,然后再用AES加密,从而解决以上问题。
MD5算法实现:
//str为要转化的参数
1 public static string GetMD5Str(string str)
2 {
3 try
4 {
5 MD5 md5 = new MD5CryptoServiceProvider();
6 //将字符串转换为字节数组
7 byte[] fromData = System.Text.Encoding.Unicode.GetBytes(str);
8 //计算字节数组的哈希值
9 byte[] toData = md5.ComputeHash(fromData);
10 string byteStr = "";
11 for (int i = 0; i < toData.Length; i++)
12 {
13 byteStr += toData[i].ToString("x");
14 }
15 return byteStr;
16 //return Convert.ToBase64String(toData);
17 }
18 catch (Exception ex)
19 {
20 throw new Exception(ex.InnerException.ToString());
21 }
22 }
3.2 AES加密
AES对称加密算法(使用默认CBC模式 PaddingMode.PKCS7):
1.调用AesManaged类,.Net Framework 3.5版本引入
/// <summary>
/// AES加密
/// </summary>
/// <param name="HashStr">被加密的明文</param>
/// <param name="Key">密钥</param>
/// <param name="Vector">向量</param>
/// <returns>密文</returns>
public static string AESEncrypt(string HashStr, string Key, string Vector)
{
try
{
byte[] bKey = new byte[32];
Array.Copy(Encoding.UTF8.GetBytes(Key.PadRight(bKey.Length)), bKey, bKey.Length);
byte[] bVector = new byte[16];
Array.Copy(Encoding.UTF8.GetBytes(Vector.PadRight(bVector.Length)), bVector, bVector.Length);
byte[] encrypted;//加密后的密文
using (AesManaged aesAlg = new AesManaged())
{
aesAlg.Key = bKey;
aesAlg.IV = bVector;
//创建对称加密对象
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
//新建内存流
using (MemoryStream msEncrypt = new MemoryStream())
{
// 把内存流对象包装成加密流对象
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(HashStr);
}
encrypted = msEncrypt.ToArray();
}
}
}
return Convert.ToBase64String(encrypted);
}
catch (Exception ex)
{
return null;
}
}
3.3 AES解密
/// <summary>
/// AES解密
/// </summary>
/// <param name="text">被解密的密文</param>
/// <param name="Key">密钥</param>
/// <param name="Vector">向量</param>
/// <returns>明文</returns>
public static string AesDecrypt(string text, string Key, string Vector)
{
try
{
byte[] cipherText = Convert.FromBase64String(text);
byte[] bKey = new byte[32];
Array.Copy(Encoding.UTF8.GetBytes(Key.PadRight(bKey.Length)), bKey, bKey.Length);
byte[] bVector = new byte[16];
Array.Copy(Encoding.UTF8.GetBytes(Vector.PadRight(bVector.Length)), bVector, bVector.Length);
string plaintext = null;
using (AesManaged aesAlg = new AesManaged())
{
aesAlg.Key = bKey;
aesAlg.IV = bVector;
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
// 明文存储区
using (MemoryStream msDecrypt = new MemoryStream(cipherText))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
return plaintext;
}
catch (Exception ex)
{
return null;
}
}
2.调用Rijndael类,.Net Framework 2.0版本引入
加密:
/// <summary>
/// AES加密
/// </summary>
/// <param name="HashStr">被加密的明文</param>
/// <param name="Key">密钥</param>
/// <param name="Vector">向量</param>
/// <returns>密文</returns>
public static string AESEncryptByRijndael(string HashStr, string Key, string Vector)
{
byte[] plainBytes = Encoding.UTF8.GetBytes(HashStr);
byte[] bKey = new byte[32];
Array.Copy(Encoding.UTF8.GetBytes(Key.PadRight(bKey.Length)), bKey, bKey.Length);
byte[] bVector = new byte[16];
Array.Copy(Encoding.UTF8.GetBytes(Vector.PadRight(bVector.Length)), bVector, bVector.Length);
byte[] Cryptograph = null; // 加密后的密文
Rijndael Aes = Rijndael.Create();
try
{
// 开辟一块内存流
using (MemoryStream Memory = new MemoryStream())
{
// 把内存流对象包装成加密流对象
using (CryptoStream Encryptor = new CryptoStream(Memory,
Aes.CreateEncryptor(bKey, bVector),
CryptoStreamMode.Write))
{
// 明文数据写入加密流
Encryptor.Write(plainBytes, 0, plainBytes.Length);
Encryptor.FlushFinalBlock();
Cryptograph = Memory.ToArray();
}
}
}
catch
{
Cryptograph = null;
}
return Convert.ToBase64String(Cryptograph);
}
解密:
/// <summary>
/// AES解密
/// </summary>
/// <param name="text">被解密的密文</param>
/// <param name="Key">密钥</param>
/// <param name="Vector">向量</param>
/// <returns>明文</returns>
public static string AESDecryptByRijndael(string text, string Key, string Vector)
{
byte[] encryptedBytes = Convert.FromBase64String(text);
byte[] bKey = new byte[32];
Array.Copy(Encoding.UTF8.GetBytes(Key.PadRight(bKey.Length)), bKey, bKey.Length);
byte[] bVector = new byte[16];
Array.Copy(Encoding.UTF8.GetBytes(Vector.PadRight(bVector.Length)), bVector, bVector.Length);
byte[] original = null; // 解密后的明文
Rijndael Aes = Rijndael.Create();
try
{
// 开辟一块内存流,存储密文
using (MemoryStream Memory = new MemoryStream(encryptedBytes))
{
// 把内存流对象包装成加密流对象
using (CryptoStream Decryptor = new CryptoStream(Memory,
Aes.CreateDecryptor(bKey, bVector),
CryptoStreamMode.Read))
{
// 明文存储区
using (MemoryStream originalMemory = new MemoryStream())
{
Byte[] Buffer = new Byte[1024];
Int32 readBytes = 0;
while ((readBytes = Decryptor.Read(Buffer, 0, Buffer.Length)) > 0)
{
originalMemory.Write(Buffer, 0, readBytes);
}
original = originalMemory.ToArray();
}
}
}
}
catch
{
original = null;
}
return Encoding.UTF8.GetString(original);
}
4. 解决方案分析
4.1性能分析
本方案用到MD5算法和AES对称加密算法,不会因参数过长而额外影响数据传输速度和验证速度。主要性能消耗是MD5运算过程,因为MD5把参数转化成了长度最大为32的字符串,所以到AES加密时,只需对长度为32的字符串进行加密,所以速度是很快的。
以下是MD5运算5000次测试结果:
运算字符串大小 | 执行次数 | 响应时间(毫秒) |
100K | 5000 | 0.14ms |
200K | 5000 | 0.32ms |
300K | 5000 | 0.54ms |
从表可以看出,MD5运算效率是非常高的。所以得出结论是本方案性能是高的。
4.2安全性分析
安全性取决于AES对称加密算法。以下给出百度百科的说法:
高级加密标准(英语:Advanced Encryption Standard,缩写:AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。
AES性能高,安全性好到目前为止是毋庸置疑的,且加密的字符串长度仅为32,加密后的字符串长度也只有44。
总结
通过对身份验证和数字签名的方法提高接口数据安全性,防止数据被篡改和信息泄露。利用MD5转化参数然后再加密的方法可以解决因参数过长而影响接口的性能的问题。AES对称加密算法从性能方面是很好的,因为只需加密最大长度为32的字符串。从安全性上分析,它也是目前公认的安全性高的一种对称加密算法。
附上数据验证方法:
public class AuthFilterAttribute : AuthorizationFilterAttribute
{
public class MyHttpActionContext : HttpActionContext
{
}
public override void OnAuthorization(HttpActionContext actionContext)
{
var responseBody = actionContext.Request.Content.ReadAsStringAsync().Result;
myData data = JsonConvert.DeserializeObject<myData>(responseBody);
string MD5Str = AESHelper.GetMD5Str(data.JsonStr);
string AESDecryptToMD5 = AESHelper.AesDecrypt(data.AES, "秘钥", "向量");
if (!MD5Str.Equals(AESDecryptToMD5))
{
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.Unauthorized, new HttpError("参数被修改,请求无效!"));
}
//上一步已经完成了数据验证,想只获取请求的参数,把加密后的字符串去掉,至今未实现
//var controllerName = actionContext.ActionDescriptor.ControllerDescriptor.ControllerName;
//var actionName = actionContext.ActionDescriptor.ActionName;
//HttpControllerContext hcc = new HttpControllerContext();
}
}
在WebApiConfig中添加引用:config.Filters.Add(new AuthFilterAttribute());
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API 配置和服务
// Web API 路由
config.MapHttpAttributeRoutes();
config.Filters.Add(new AuthFilterAttribute());//各控制器接收Filter权限限制
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{action}/{id}",
//routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}