一. 场景介绍:

如题如何有效的,最少量的现有代码侵入从而实现客户端与服务器之间的数据交换加密呢?

二. 探究:

1.需求分析

webapi服务端 有如下接口:

public class ApiTestController : ApiController
{ // GET api/<controller>/5
public object Get(int id)
{
return "value" + id;
}
}

ApiTestController

无加密请求

GET /api/apitest?id=10

 
返回结果
 response "value10"
 
 
我们想要达到的效果为:
Get /api/apitest?aWQ9MTA=
response InZhbHVlMTAi  (解密所得 "value10")
 
或者更多其它方式加密
 

2.功能分析

 要想对现有代码不做任何修改, 我们都知道所有api controller 初始化在router确定之后, 因此我们应在router之前将GET参数和POST的参数进行加密才行.
 
看下图 webapi 生命周期:
 
 
我们看到在 路由routing 之前 有DelegationgHander 层进行消息处理.
 
因为我们要对每个请求进行参数解密处理,并且又将返回消息进行加密处理, 因此我们 瞄准 MessageProcessingHandler
 
    //
// 摘要:
// A base type for handlers which only do some small processing of request and/or
// response messages.
public abstract class MessageProcessingHandler : DelegatingHandler
{
//
// 摘要:
// Creates an instance of a System.Net.Http.MessageProcessingHandler class.
protected MessageProcessingHandler();
//
// 摘要:
// Creates an instance of a System.Net.Http.MessageProcessingHandler class with
// a specific inner handler.
//
// 参数:
// innerHandler:
// The inner handler which is responsible for processing the HTTP response messages.
protected MessageProcessingHandler(HttpMessageHandler innerHandler); //
// 摘要:
// Performs processing on each request sent to the server.
//
// 参数:
// request:
// The HTTP request message to process.
//
// cancellationToken:
// A cancellation token that can be used by other objects or threads to receive
// notice of cancellation.
//
// 返回结果:
// Returns System.Net.Http.HttpRequestMessage.The HTTP request message that was
// processed.
protected abstract HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken);
//
// 摘要:
// Perform processing on each response from the server.
//
// 参数:
// response:
// The HTTP response message to process.
//
// cancellationToken:
// A cancellation token that can be used by other objects or threads to receive
// notice of cancellation.
//
// 返回结果:
// Returns System.Net.Http.HttpResponseMessage.The HTTP response message that was
// processed.
protected abstract HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken);
//
// 摘要:
// Sends an HTTP request to the inner handler to send to the server as an asynchronous
// operation.
//
// 参数:
// request:
// The HTTP request message to send to the server.
//
// cancellationToken:
// A cancellation token that can be used by other objects or threads to receive
// notice of cancellation.
//
// 返回结果:
// Returns System.Threading.Tasks.Task`1.The task object representing the asynchronous
// operation.
//
// 异常:
// T:System.ArgumentNullException:
// The request was null.
protected internal sealed override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken);
}

MessageProcessingHandler

三. 实践:

现在我们将来 先实现2个版本的通讯加密解密功能,定为 版本1.0 base64加密, 版本1.1 Des加密
 
     /// <summary>
/// 加密解密接口
/// </summary>
public interface IMessageEnCryption
{
/// <summary>
/// 加密
/// </summary>
/// <param name="content"></param>
/// <returns></returns>
string Encode(string content);
/// <summary>
/// 解密
/// </summary>
/// <param name="content"></param>
/// <returns></returns>
string Decode(string content);
}

IMessageEnCryption

编写版本1.0 base64加密解密

     /// <summary>
/// 加解密 只做 base64
/// </summary>
public class MessageEncryptionVersion1_0 : IMessageEnCryption
{
public string Decode(string content)
{
return content?.DecryptBase64();
} public string Encode(string content)
{
return content.EncryptBase64();
}
}

MessageEncryptionVersion1_0

编写版本1.1 des加密解密

     /// <summary>
/// 数据加解密 des
/// </summary>
public class MessageEncryptionVersion1_1 : IMessageEnCryption
{
public static readonly string KEY = "fHil/4]0";
public string Decode(string content)
{
return content.DecryptDES(KEY);
} public string Encode(string content)
{
return content.EncryptDES(KEY);
}
}

MessageEncryptionVersion1_1

附上加密解密的基本的一个封装类

     public static class EncrypExtends
{ //默认密钥向量
private static byte[] Keys = { 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF };
internal static string Key = "*@&$(@#H"; //// <summary>
/// DES加密字符串
/// </summary>
/// <param name="encryptString">待加密的字符串</param>
/// <param name="encryptKey">加密密钥,要求为8位</param>
/// <returns>加密成功返回加密后的字符串,失败返回源串</returns>
public static string EncryptDES(this string encryptString, string encryptKey)
{
try
{
byte[] rgbKey = Encoding.UTF8.GetBytes(encryptKey.Substring(, ));
byte[] rgbIV = Keys;
byte[] inputByteArray = Encoding.UTF8.GetBytes(encryptString);
DESCryptoServiceProvider dCSP = new DESCryptoServiceProvider();
MemoryStream mStream = new MemoryStream();
CryptoStream cStream = new CryptoStream(mStream, dCSP.CreateEncryptor(rgbKey, rgbIV), CryptoStreamMode.Write);
cStream.Write(inputByteArray, , inputByteArray.Length);
cStream.FlushFinalBlock();
return Convert.ToBase64String(mStream.ToArray());
}
catch
{
return encryptString;
}
}
//// <summary>
/// DES解密字符串
/// </summary>
/// <param name="decryptString">待解密的字符串</param>
/// <param name="decryptKey">解密密钥,要求为8位,和加密密钥相同</param>
/// <returns>解密成功返回解密后的字符串,失败返源串</returns>
public static string DecryptDES(this string decryptString, string key)
{
try
{
byte[] rgbKey = Encoding.UTF8.GetBytes(key.Substring(, ));
byte[] rgbIV = Keys;
byte[] inputByteArray = Convert.FromBase64String(decryptString);
DESCryptoServiceProvider DCSP = new DESCryptoServiceProvider();
MemoryStream mStream = new MemoryStream();
CryptoStream cStream = new CryptoStream(mStream, DCSP.CreateDecryptor(rgbKey, rgbIV), CryptoStreamMode.Write);
cStream.Write(inputByteArray, , inputByteArray.Length);
cStream.FlushFinalBlock();
return Encoding.UTF8.GetString(mStream.ToArray());
}
catch
{
return decryptString;
}
}
public static string EncryptBase64(this string encryptString)
{
return Convert.ToBase64String(Encoding.UTF8.GetBytes(encryptString));
}
public static string DecryptBase64(this string encryptString)
{
return Encoding.UTF8.GetString(Convert.FromBase64String(encryptString));
}
public static string DecodeUrl(this string cryptString)
{
return System.Web.HttpUtility.UrlDecode(cryptString);
}
public static string EncodeUrl(this string cryptString)
{
return System.Web.HttpUtility.UrlEncode(cryptString);
}
}

EncrypExtends

OK! 到此我们前题工作已经完成了80%,开始进行HTTP请求的 消息进和出的加密解密功能的实现.

我们暂时将加密的版本信息定义为 HTTP header头中 以 api_version 的value 来判别分别是用何种方式加密解密

header例:

  api_version: 1.0

  api_version: 1.1

     /// <summary>
/// API消息请求处理
/// </summary>
public class JoyMessageHandler : MessageProcessingHandler
{ /// <summary>
/// 接收到request时 处理
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected override HttpRequestMessage ProcessRequest(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Content.IsMimeMultipartContent())
return request;
// 获取请求头中 api_version版本号
var ver = System.Web.HttpContext.Current.Request.Headers.GetValues("api_version")?.FirstOrDefault();
// 根据api_version版本号获取加密对象, 如果为null 则不需要加密
var encrypt = MessageEncryptionCreator.GetInstance(ver); if (encrypt != null)
{
// 读取请求body中的数据
string baseContent = request.Content.ReadAsStringAsync().Result;
// 获取加密的信息
// 兼容 body: 加密数据 和 body: code=加密数据
baseContent = baseContent.Match("(code=)*(?<code>[\\S]+)", );
// URL解码数据
baseContent = baseContent.DecodeUrl();
// 用加密对象解密数据
baseContent = encrypt.Decode(baseContent); string baseQuery = string.Empty;
if (!request.RequestUri.Query.IsNullOrEmpty())
{
// 同 body
// 读取请求 url query数据
baseQuery = request.RequestUri.Query.Substring();
baseQuery = baseQuery.Match("(code=)*(?<code>[\\S]+)", );
baseQuery = baseQuery.DecodeUrl();
baseQuery = encrypt.Decode(baseQuery);
}
// 将解密后的 URL 重置URL请求
request.RequestUri = new Uri($"{request.RequestUri.AbsoluteUri.Split('?')[0]}?{baseQuery}");
// 将解密后的BODY数据 重置
request.Content = new StringContent(baseContent);
} return request;
} /// <summary>
/// 处理将要向客户端response时
/// </summary>
/// <param name="response"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected override HttpResponseMessage ProcessResponse(HttpResponseMessage response, CancellationToken cancellationToken)
{
//var isMediaType = response.Content.Headers.ContentType.MediaType.Equals(mediaTypeName, StringComparison.OrdinalIgnoreCase);
var ver = System.Web.HttpContext.Current.Request.Headers.GetValues("api_version")?.FirstOrDefault();
var encrypt = MessageEncryptionCreator.GetInstance(ver);
if (encrypt != null)
{
if (response.StatusCode == HttpStatusCode.OK)
{
var result = response.Content.ReadAsStringAsync().Result;
// 返回消息 进行加密
var encodeResult = encrypt.Encode(result);
response.Content = new StringContent(encodeResult);
}
} return response;
} }

JoyMessageHandler

最后在 webapiconfig 中将我们的消息处理添加到容器中
 
     public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API 配置和服务
// 将 Web API 配置为仅使用不记名令牌身份验证。
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType)); // Web API 路由
config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
); // 添加自定义消息处理
config.MessageHandlers.Add(new JoyMessageHandler()); }
}

WebApiConfig

编写单元测试:
  [TestMethod()]
public void GetTest()
{
var id = ;
var resultSuccess = $"\"value{id}\"";
//不加密
Trace.WriteLine($"without encryption.");
var url = $"api/ApiTest?id={id}";
Trace.WriteLine($"get url : {url}");
var response = http.GetAsync(url).Result;
var result = response.Content.ReadAsStringAsync().Result;
Assert.AreEqual(result, resultSuccess);
Trace.WriteLine($"result : {result}"); //使用 方案1加密
Trace.WriteLine($"encryption case one."); url = $"api/ApiTest?code=" + $"id={id}".EncryptBase64().EncodeUrl(); Trace.WriteLine($"get url : {url}"); http.DefaultRequestHeaders.Clear();
http.DefaultRequestHeaders.Add("api_version", "1.0");
response = http.GetAsync(url).Result; result = response.Content.ReadAsStringAsync().Result; Trace.WriteLine($"result : {result}"); result = result.DecryptBase64(); Trace.WriteLine($"DecryptBase64 : {result}"); Assert.AreEqual(result, resultSuccess); //使用 方案2 加密通讯
Trace.WriteLine($"encryption case one."); url = $"api/ApiTest?code=" + $"id={id}".EncryptDES(MessageEncryptionVersion1_1.KEY).EncodeUrl(); Trace.WriteLine($"get url : {url}"); http.DefaultRequestHeaders.Clear();
http.DefaultRequestHeaders.Add("api_version", "1.1");
response = http.GetAsync(url).Result; result = response.Content.ReadAsStringAsync().Result; Trace.WriteLine($"result : {result}"); result = result.DecryptDES(MessageEncryptionVersion1_1.KEY); Trace.WriteLine($"DecryptBase64 : {result}"); Assert.AreEqual(result, resultSuccess);
}

ApiTestControllerTests

至此为止功能实现完毕..
 

四.思想延伸

要想更加安全的方案,可以将给每位用户生成不同的 private key , 利用AES加密解密
 
 
 
 
本Demo开源地址:
oschina
github
 

WebApi实现通讯加密的更多相关文章

  1. WebApi实现通讯加密 (转)

    http://www.cnblogs.com/jonneydong/p/WebApi_Encryption.html 一. 场景介绍: 如题如何有效的,最少量的现有代码侵入从而实现客户端与服务器之间的 ...

  2. WebApi服务Uri加密及验证的两种方式

    最近的一个项目要求服务端与UI层分离,业务层以WebApi方式向外提供所有业务服务,服务在数据保密性方面提出了要求,主要包括: 1:客户端认证: 2:服务请求超时(默认5分钟): 3:服务Get请求的 ...

  3. ECC(Ellipse Curve Cryptography)+AES(Advanced Encryption Standard)前端通讯加密模拟(使用eccrypto-js)

    前置知识 不了解对称加密与非对称加密的小伙伴可以看看下面的文章,想详细学习与区块链有关的加密算法可以戳这里 对称与非对称加密 https://blog.csdn.net/u013320868/arti ...

  4. Self Host WebApi服务传输层SSL加密(服务器端+客户端调用)

    接上篇<WebApi服务URI加密及验证的两种方式>,在实际开发中,仅对URI进行加密是不够的,在传输层采用SSL加密也是必须的. 如果服务寄宿于IIS,那对传输层加密非常简单仅需要配置一 ...

  5. SQLite学习笔记(十)&&加密

    随着移动互联网的发展,手机使用越来越广泛,sqlite作为手机端存储的一种解决方案,使用也非常普遍.但是sqlite本身安全特性却比较弱,比如不支持用户权限,只要能获取到数据库文件就能进行访问:另外也 ...

  6. .NET中的DES对称加密

    DES是一种对称加密(Data Encryption Standard)算法,于1977年得到美国政府的正式许可,是一种用56位密钥来加密64位数据的方法.一般密码长度为8个字节,其中56位加密密钥, ...

  7. SQLite XXTea加密学习

    这几天优化数据库读写,移植了xxtea加密到最新的数据库sqlite 3.12.2里,一些好文章放在这里.移植后,数据库读写性能异常优秀! 这几天又发现,数据库还是发生了无法写入情况,数据库崩溃掉了. ...

  8. 单片机上使用TEA加密通信(转)

    源:单片机上使用TEA加密通信 本文博客链接:http://blog.csdn.net/jdh99,作者:jdh,转载请注明. 环境: 主机:WIN7 开发环境:MDK4.72 单片机:STM32 说 ...

  9. Nginx集群之SSL证书的WebApi微服务

    目录 1       大概思路... 1 2       Nginx集群之SSL证书的WebApi微服务... 1 3       HTTP与HTTPS(SSL协议)... 1 4       Ope ...

随机推荐

  1. NPM 简单实用说明

    https://nodejs.org/en/download/ 下载并按照,直接下一步就可以,最后会在C:\Program Files 新建一个nodejs文件夹,里面有一个node.exe.双击就启 ...

  2. eQTL

    首先QTL是数量性状位点,比如身高是一个数量性状,其对应的控制基因的位点就是一个数量性状位点,而eQTL就是控制数量性状表达位点,即能控制数量性状基因(如身高基因)表达水平高低的那些基因的位点. 数量 ...

  3. MYSQL外键的使用以及优缺点

    主键和索引是不可少的,不仅可以优化数据检索速度,开发人员还省不其它的工作, 矛盾焦点:数据库设计是否需要外键.这里有两个问题:一个是如何保证数据库数据的完整性和一致性:二是第一条对性能的影响. 正方观 ...

  4. CentOS6.5+mysql5.1源码安装过程

    一:先安装依赖包(不然配置的时候会报错的!) yum -y install ncurses* libtermcap* gcc-c++* 新建mysql用户 [root@HE1Packages]# gr ...

  5. javascript获取整数随机数

    javascript获取整数随机数 // 从4到10的随机数,|0是生成正整数 var numBars = Math.random()*6+4|0; 也可以通过右移0位(或者左移0位)进行取整 var ...

  6. hyperLink的定制

    在iReport中增加hyperLink,点击之后没有反应:需要重新写一遍net.sf.jasperreports.view.JRViewer; 修改其中的gotoHyperlink方法: case ...

  7. MyBatis 使用foreach与其他方式的时候参数传递方式

    Mapper文件: <select id="selectPersonByIds" parameterType="map" resultMap=" ...

  8. MVC + AngularJS 初体验(实现表单操作)

    AngularJS AngularJS 通过新的属性和表达式扩展了 HTML. AngularJS 可以构建一个单一页面应用程序(SPAs:Single Page Applications). Ang ...

  9. libMF阅读记录(一):首先要编译通过

    libMF是林智仁老师开发的一个用于推荐系统的矩阵分解库,下载地址:libMF 测试用的数据集是MovieLen,一个给电影评分的数据集,下载在此:ML 最近在阅读libMF的源代码,并且准备开发其M ...

  10. oracle_角色

    一. 每个Oracle用户都有一个名字和口令,并拥有一些由其创建的表.视图和其他资源. Oracle角色(role)就是一组权限(privilege) (或者是每个用户根据其状态和条件所需的访问类型) ...