加密与解密概述

加密与解密属于数据安全的范畴。在消息传输时,通过对消息进行特殊编码(加密),建立一种安全的交流方式,使得只有发送者所期望的接收者能够理解(解密)。这里我们定义一个场景:发送方,接收方,第三方,发送方要将信息发送给接收方,二第三方想要截取并篡改消息,然后在转发给接收方。要称得上是安全的交流方式,需要满足下面的3个条件:

  1. 完整性,消息的接收方可以确定消息在传输过程中没有被篡改过,即消息是完好无损。
  2. 保密性,第三方无法解密发送的消息(第三方可以获取传输的消息)。
  3. 可认证性,即接收方可以知道消息是由谁发送的。

下面将列出几种常用的技术,看看是否符合上面的3个条件。

散列运算

散列(英语:Hashing)是电脑科学中一种对资料的处理方法,通过某种特定的函数/算法(称为散列函数/算法)将要检索的项与用来检索的索引(称为散列,或者散列值)关联起来,生成一种便于搜索的数据结构(称为散列表)。也译为散列。旧译哈希(误以为是人名而采用了音译)。它也常用作一种资讯安全的实作方法,由一串资料中经过散列算法(Hashing algorithms)计算出来的资料指纹(data fingerprint),经常用来识别档案与资料是否有被窜改,以保证档案与资料确实是由原创者所提供。
如今,散列算法也被用来加密存在数据库中的密码(password)字串,由于散列算法所计算出来的散列值(Hash Value)具有不可逆(无法逆向演算回原本的数值)的性质,因此可有效的保护密码,我公司内的Web管理系统存储的密码字符串就是散列运算的摘要,确实很实用。

散列运算具有以下3个特点:

  1. 散列运算是不可逆的(加密是单向的)。
  2. 任何两个不相同文件的摘要是不同的。
  3. 不论原始消息大小如何,同一种散列算法得到的摘要的长度是固定的。

常见的散列运算如下图所示:

由此可见散列算法只能满足完整性的条件。

对称加密

对称密钥加密(英语:Symmetric-key algorithm)又称为对称加密、私钥加密、共享密钥加密,是密码学中的一类加密算法。这类算法在加密和解密时使用相同的密钥,或是使用两个可以简单地相互推算的密钥。实务上,这组密钥成为在两个或多个成员间的共同秘密,以便维持专属的通讯联系。与公开密钥加密相比,要求双方取得相同的密钥是对称密钥加密的主要缺点之一。

常见的对称加密算法有DES、3DES、AES、Blowfish、IDEA、RC5、RC6。

对称加密流程如下:

  1. 发送方和接收方吃有相同的密钥并严格保密。
  2. 发送方使用密钥对消息进行加密,然后发送消息。
  3. 接收方接收到消息使用同样能够的密钥进行解密。
  4. 在这一过程中第三方截取到消息,但得到的只是一堆乱码。

由此可见,对称加密可以解决保密的问题,但是要确保第三方没有非法获取到密钥。

非对称加密

非对称加密(asymmetric cryptography),又称为公开密钥加密(英语:public-key cryptography),在这种密码学方法中,需要一对密钥,一是个私人密钥,另一个则是公开密钥。这两个密钥是数学相关,用某用户密钥加密后所得的信息,只能用该用户的解密密钥才能解密。如果知道了其中一个,并不能计算出另外一个。因此如果公开了一对密钥中的一个,并不会危害到另外一个的秘密性质。称公开的密钥为公钥;不公开的密钥为私钥。
如果加密密钥是公开的,这用于客户给私钥所有者上传加密的数据,这被称作为公开密钥加密(狭义)。例如,网络银行的客户发给银行网站的账户操作的加密数据。

如果解密密钥是公开的,用私钥加密的信息,可以用公钥对其解密,用于客户验证持有私钥一方发布的数据或文件是完整准确的,接收者由此可知这条信息确实来自于拥有私钥的某人,这被称作数字签名,公钥的形式就是数字证书。例如,从网上下载的安装程序,一般都带有程序制作者的数字签名,可以证明该程序的确是该作者(公司)发布的而不是第三方伪造的且未被篡改过(身份认证/验证)。

常见的公钥加密算法有: RSA、ElGamal、背包算法、Rabin(RSA的特例)、迪菲-赫尔曼密钥交换协议中的公钥加密算法、椭圆曲线加密算法(英语:Elliptic Curve Cryptography, ECC)。使用最广泛的是RSA算法(由发明者Rivest、Shmir和Adleman姓氏首字母缩写而来)是著名的公开金钥加密算法,ElGamal是另一种常用的非对称加密算法。

现在假设这种加密方式只使用一组密钥对,根据使用发送方还是接收方的密钥对又可分为加密模式和认证模式。

加密模式下,由消息的接收方发布公钥,持有私钥。基本步骤如下所示:

  1. 接收方公布自己的公钥,任何人都可以获得。
  2. 发送方使用上述公钥对消息进行加密,然后发送。
  3. 接收方使用自己的私钥对消息进行解密。

可以看出非对称加密的加密模式可以解决保密性问题。

认证模式下,由消息的发送方发布公钥,持有私钥。基本步骤如下所示:

  1. 发送方公布自己的公钥,任何人都可以获得。
  2. 发送方使用自己的私钥对消息进行加密,然后发送。
  3. 接收方使用发送方的私钥对消息进行解密。

可以看出非对称加密的加密模式可以解决认证性问题。

得出结论

上面的技术单一使用无法全部满足完整、保密和认证的3个条件,但是通过组合使用的方式就可以达到目的了。比如说数字签名就是在上面的认证模式加上了散列运算。

加密解密类(苏飞论坛所有)

  1. /// <summary>
  2. /// 类说明:Assistant
  3. /// 编 码 人:苏飞
  4. /// 联系方式:361983679
  5. /// 更新网站:http://www.sufeinet.com/thread-655-1-1.html
  6. /// </summary>
  7. using System;
  8. using System.Text;
  9. using System.Security.Cryptography;
  10. using System.IO;
  11. using System.Text.RegularExpressions;
  12. using System.Collections;
  13.  
  14. namespace DotNet.Utilities
  15. {
  16. /// <summary>
  17. /// MySecurity(安全类) 的摘要说明。
  18. /// </summary>
  19. public class MySecurity
  20. {
  21. /// <summary>
  22. /// 初始化安全类
  23. /// </summary>
  24. public MySecurity()
  25. {
  26. ///默认密码
  27. key = ";
  28. }
  29. private string key; //默认密钥
  30.  
  31. private byte[] sKey;
  32. private byte[] sIV;
  33.  
  34. #region 加密字符串
  35. /// <summary>
  36. /// 加密字符串
  37. /// </summary>
  38. /// <param name="inputStr">输入字符串</param>
  39. /// <param name="keyStr">密码,可以为“”</param>
  40. /// <returns>输出加密后字符串</returns>
  41. static public string SEncryptString(string inputStr, string keyStr)
  42. {
  43. MySecurity ws = new MySecurity();
  44. return ws.EncryptString(inputStr, keyStr);
  45. }
  46. /// <summary>
  47. /// 加密字符串
  48. /// </summary>
  49. /// <param name="inputStr">输入字符串</param>
  50. /// <param name="keyStr">密码,可以为“”</param>
  51. /// <returns>输出加密后字符串</returns>
  52. public string EncryptString(string inputStr, string keyStr)
  53. {
  54. DESCryptoServiceProvider des = new DESCryptoServiceProvider();
  55. if (keyStr == "")
  56. keyStr = key;
  57. byte[] inputByteArray = Encoding.Default.GetBytes(inputStr);
  58. byte[] keyByteArray = Encoding.Default.GetBytes(keyStr);
  59. SHA1 ha = new SHA1Managed();
  60. byte[] hb = ha.ComputeHash(keyByteArray);
  61. sKey = ];
  62. sIV = ];
  63. ; i < ; i++)
  64. sKey[i] = hb[i];
  65. ; i < ; i++)
  66. sIV[i - ] = hb[i];
  67. des.Key = sKey;
  68. des.IV = sIV;
  69. MemoryStream ms = new MemoryStream();
  70. CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write);
  71. cs.Write(inputByteArray, , inputByteArray.Length);
  72. cs.FlushFinalBlock();
  73. StringBuilder ret = new StringBuilder();
  74. foreach (byte b in ms.ToArray())
  75. {
  76. ret.AppendFormat("{0:X2}", b);
  77. }
  78. cs.Close();
  79. ms.Close();
  80. return ret.ToString();
  81. }
  82. #endregion
  83.  
  84. #region 加密字符串 密钥为系统默认 0123456789
  85. /// <summary>
  86. /// 加密字符串 密钥为系统默认
  87. /// </summary>
  88. /// <param name="inputStr">输入字符串</param>
  89. /// <returns>输出加密后字符串</returns>
  90. static public string SEncryptString(string inputStr)
  91. {
  92. MySecurity ws = new MySecurity();
  93. return ws.EncryptString(inputStr, "");
  94. }
  95. #endregion
  96.  
  97. #region 加密文件
  98. /// <summary>
  99. /// 加密文件
  100. /// </summary>
  101. /// <param name="filePath">输入文件路径</param>
  102. /// <param name="savePath">加密后输出文件路径</param>
  103. /// <param name="keyStr">密码,可以为“”</param>
  104. /// <returns></returns>
  105. public bool EncryptFile(string filePath, string savePath, string keyStr)
  106. {
  107. DESCryptoServiceProvider des = new DESCryptoServiceProvider();
  108. if (keyStr == "")
  109. keyStr = key;
  110. FileStream fs = File.OpenRead(filePath);
  111. byte[] inputByteArray = new byte[fs.Length];
  112. fs.Read(inputByteArray, , (int)fs.Length);
  113. fs.Close();
  114. byte[] keyByteArray = Encoding.Default.GetBytes(keyStr);
  115. SHA1 ha = new SHA1Managed();
  116. byte[] hb = ha.ComputeHash(keyByteArray);
  117. sKey = ];
  118. sIV = ];
  119. ; i < ; i++)
  120. sKey[i] = hb[i];
  121. ; i < ; i++)
  122. sIV[i - ] = hb[i];
  123. des.Key = sKey;
  124. des.IV = sIV;
  125. MemoryStream ms = new MemoryStream();
  126. CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write);
  127. cs.Write(inputByteArray, , inputByteArray.Length);
  128. cs.FlushFinalBlock();
  129. fs = File.OpenWrite(savePath);
  130. foreach (byte b in ms.ToArray())
  131. {
  132. fs.WriteByte(b);
  133. }
  134. fs.Close();
  135. cs.Close();
  136. ms.Close();
  137. return true;
  138. }
  139. #endregion
  140.  
  141. #region 解密字符串
  142. /// <summary>
  143. /// 解密字符串
  144. /// </summary>
  145. /// <param name="inputStr">要解密的字符串</param>
  146. /// <param name="keyStr">密钥</param>
  147. /// <returns>解密后的结果</returns>
  148. static public string SDecryptString(string inputStr, string keyStr)
  149. {
  150. MySecurity ws = new MySecurity();
  151. return ws.DecryptString(inputStr, keyStr);
  152. }
  153. /// <summary>
  154. /// 解密字符串 密钥为系统默认
  155. /// </summary>
  156. /// <param name="inputStr">要解密的字符串</param>
  157. /// <returns>解密后的结果</returns>
  158. static public string SDecryptString(string inputStr)
  159. {
  160. MySecurity ws = new MySecurity();
  161. return ws.DecryptString(inputStr, "");
  162. }
  163. /// <summary>
  164. /// 解密字符串
  165. /// </summary>
  166. /// <param name="inputStr">要解密的字符串</param>
  167. /// <param name="keyStr">密钥</param>
  168. /// <returns>解密后的结果</returns>
  169. public string DecryptString(string inputStr, string keyStr)
  170. {
  171. DESCryptoServiceProvider des = new DESCryptoServiceProvider();
  172. if (keyStr == "")
  173. keyStr = key;
  174. ];
  175. ; x < inputStr.Length / ; x++)
  176. {
  177. , ), ));
  178. inputByteArray[x] = (byte)i;
  179. }
  180. byte[] keyByteArray = Encoding.Default.GetBytes(keyStr);
  181. SHA1 ha = new SHA1Managed();
  182. byte[] hb = ha.ComputeHash(keyByteArray);
  183. sKey = ];
  184. sIV = ];
  185. ; i < ; i++)
  186. sKey[i] = hb[i];
  187. ; i < ; i++)
  188. sIV[i - ] = hb[i];
  189. des.Key = sKey;
  190. des.IV = sIV;
  191. MemoryStream ms = new MemoryStream();
  192. CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write);
  193. cs.Write(inputByteArray, , inputByteArray.Length);
  194. cs.FlushFinalBlock();
  195. StringBuilder ret = new StringBuilder();
  196. return System.Text.Encoding.Default.GetString(ms.ToArray());
  197. }
  198. #endregion
  199.  
  200. #region 解密文件
  201. /// <summary>
  202. /// 解密文件
  203. /// </summary>
  204. /// <param name="filePath">输入文件路径</param>
  205. /// <param name="savePath">解密后输出文件路径</param>
  206. /// <param name="keyStr">密码,可以为“”</param>
  207. /// <returns></returns>
  208. public bool DecryptFile(string filePath, string savePath, string keyStr)
  209. {
  210. DESCryptoServiceProvider des = new DESCryptoServiceProvider();
  211. if (keyStr == "")
  212. keyStr = key;
  213. FileStream fs = File.OpenRead(filePath);
  214. byte[] inputByteArray = new byte[fs.Length];
  215. fs.Read(inputByteArray, , (int)fs.Length);
  216. fs.Close();
  217. byte[] keyByteArray = Encoding.Default.GetBytes(keyStr);
  218. SHA1 ha = new SHA1Managed();
  219. byte[] hb = ha.ComputeHash(keyByteArray);
  220. sKey = ];
  221. sIV = ];
  222. ; i < ; i++)
  223. sKey[i] = hb[i];
  224. ; i < ; i++)
  225. sIV[i - ] = hb[i];
  226. des.Key = sKey;
  227. des.IV = sIV;
  228. MemoryStream ms = new MemoryStream();
  229. CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write);
  230. cs.Write(inputByteArray, , inputByteArray.Length);
  231. cs.FlushFinalBlock();
  232. fs = File.OpenWrite(savePath);
  233. foreach (byte b in ms.ToArray())
  234. {
  235. fs.WriteByte(b);
  236. }
  237. fs.Close();
  238. cs.Close();
  239. ms.Close();
  240. return true;
  241. }
  242. #endregion
  243.  
  244. #region MD5加密
  245. /// <summary>
  246. /// 128位MD5算法加密字符串
  247. /// </summary>
  248. /// <param name="text">要加密的字符串</param>
  249. public static string MD5(string text)
  250. {
  251.  
  252. //如果字符串为空,则返回
  253. if (Tools.IsNullOrEmpty<string>(text))
  254. {
  255. return "";
  256. }
  257. //返回MD5值的字符串表示
  258. return MD5(text);
  259. }
  260.  
  261. /// <summary>
  262. /// 128位MD5算法加密Byte数组
  263. /// </summary>
  264. /// <param name="data">要加密的Byte数组</param>
  265. public static string MD5(byte[] data)
  266. {
  267. //如果Byte数组为空,则返回
  268. if (Tools.IsNullOrEmpty<byte[]>(data))
  269. {
  270. return "";
  271. }
  272.  
  273. try
  274. {
  275. //创建MD5密码服务提供程序
  276. MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
  277.  
  278. //计算传入的字节数组的哈希值
  279. byte[] result = md5.ComputeHash(data);
  280.  
  281. //释放资源
  282. md5.Clear();
  283.  
  284. //返回MD5值的字符串表示
  285. return Convert.ToBase64String(result);
  286. }
  287. catch
  288. {
  289.  
  290. //LogHelper.WriteTraceLog(TraceLogLevel.Error, ex.Message);
  291. return "";
  292. }
  293. }
  294. #endregion
  295.  
  296. #region Base64加密
  297. /// <summary>
  298. /// Base64加密
  299. /// </summary>
  300. /// <param name="text">要加密的字符串</param>
  301. /// <returns></returns>
  302. public static string EncodeBase64(string text)
  303. {
  304. //如果字符串为空,则返回
  305. if (Tools.IsNullOrEmpty<string>(text))
  306. {
  307. return "";
  308. }
  309.  
  310. try
  311. {
  312. char[] Base64Code = new char[]{'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T',
  313. 'U','V','W','X','Y','Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n',
  314. ',
  315. ','+','/','='};
  316. ;
  317. ArrayList byteMessage = new ArrayList(Encoding.Default.GetBytes(text));
  318. StringBuilder outmessage;
  319. int messageLen = byteMessage.Count;
  320. ;
  321. ;
  322. ) > )
  323. {
  324. ; i < - use; i++)
  325. byteMessage.Add(empty);
  326. page++;
  327. }
  328. outmessage = );
  329. ; i < page; i++)
  330. {
  331. ];
  332. instr[] = (];
  333. instr[] = ( + ];
  334. instr[] = ( + ];
  335. ];
  336. outstr[] = instr[] >> ;
  337. outstr[] = ((instr[] & ) ^ (instr[] >> );
  338. ].Equals(empty))
  339. outstr[] = ((instr[] & ) ^ (instr[] >> );
  340. else
  341. outstr[] = ;
  342. ].Equals(empty))
  343. outstr[] = (instr[] & 0x3f);
  344. else
  345. outstr[] = ;
  346. outmessage.Append(Base64Code[outstr[]]);
  347. outmessage.Append(Base64Code[outstr[]]);
  348. outmessage.Append(Base64Code[outstr[]]);
  349. outmessage.Append(Base64Code[outstr[]]);
  350. }
  351. return outmessage.ToString();
  352. }
  353. catch (Exception ex)
  354. {
  355. throw ex;
  356. }
  357. }
  358. #endregion
  359.  
  360. #region Base64解密
  361. /// <summary>
  362. /// Base64解密
  363. /// </summary>
  364. /// <param name="text">要解密的字符串</param>
  365. public static string DecodeBase64(string text)
  366. {
  367. //如果字符串为空,则返回
  368. if (Tools.IsNullOrEmpty<string>(text))
  369. {
  370. return "";
  371. }
  372.  
  373. //将空格替换为加号
  374. text = text.Replace(" ", "+");
  375.  
  376. try
  377. {
  378. ) != )
  379. {
  380. return "包含不正确的BASE64编码";
  381. }
  382. if (!Regex.IsMatch(text, "^[A-Z0-9/+=]*$", RegexOptions.IgnoreCase))
  383. {
  384. return "包含不正确的BASE64编码";
  385. }
  386. string Base64Code = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
  387. ;
  388. ArrayList outMessage = );
  389. char[] message = text.ToCharArray();
  390. ; i < page; i++)
  391. {
  392. ];
  393. instr[] = (]);
  394. instr[] = ( + ]);
  395. instr[] = ( + ]);
  396. instr[] = ( + ]);
  397. ];
  398. outstr[] = (] << ) ^ ((instr[] & ));
  399. ] != )
  400. {
  401. outstr[] = (] << ) ^ ((instr[] & ));
  402. }
  403. else
  404. {
  405. outstr[] = ;
  406. }
  407. ] != )
  408. {
  409. outstr[] = (] << ) ^ instr[]);
  410. }
  411. else
  412. {
  413. outstr[] = ;
  414. }
  415. outMessage.Add(outstr[]);
  416. ] != )
  417. outMessage.Add(outstr[]);
  418. ] != )
  419. outMessage.Add(outstr[]);
  420. }
  421. byte[] outbyte = (byte[])outMessage.ToArray(Type.GetType("System.Byte"));
  422. return Encoding.Default.GetString(outbyte);
  423. }
  424. catch (Exception ex)
  425. {
  426. throw ex;
  427. }
  428. }
  429. #endregion
  430. }
  431. }

.Net中的加密与解密的更多相关文章

  1. AES —— JAVA中对称加密和解密

    package demo.security; import java.io.IOException; import java.io.UnsupportedEncodingException; impo ...

  2. 在ASP.NET MVC环境中使用加密与解密

    在.NET Framework 4.5的NET框架中,在程序中加密与解密很方便.现在均学习ASP.NET MVC程序了,因此Insus.NET也在此写个学习的例子.在需要时可以参考与查阅. 写一个Ut ...

  3. nodejs中aes-128-cbc加密和解密

    和java程序进行交互的时候,java那边使用AES 128位填充模式:AES/CBC/PKCS5Padding加密方法,在nodejs中采用对应的aes-128-cbc加密方法就能对应上,因为有使用 ...

  4. Android中文件加密和解密的实现

    最近项目中需要用到加解密功能,言外之意就是不想让人家在反编译后通过不走心就能获取文件里一些看似有用的信息,但考虑到加解密的简单实现,这里并不使用AES或DES加解密 为了对android中assets ...

  5. php中rsa加密及解密和签名及验签

    加密的内容长度限制为密钥长度少位,如位的密钥最多加密的内容为个长度. 公钥加密 $public_content=file_get_contents(公钥路径); $public_key=openssl ...

  6. C#中AES加密和解密

    /// AES加密 /// </summary> /// <param name="inputdata">输入的数据</param> /// & ...

  7. php和java中的加密和解密

    遇到的java代码如下: Cipher cipher=Cipher.getInstance("DESede/CBC/PKCS5Padding"); 在php中使用des算法 始终校 ...

  8. 探讨.NET Core中实现AES加密和解密以及.NET Core为我们提供了什么方便!

    前言 对于数据加密和解密每次我都是从网上拷贝一份,无需有太多了解,由于在.net core中对加密和解密目前全部是统一了接口,只是做具体的实现,由于遇到过问题,所以将打算基本了解下其原理,知其然足矣, ...

  9. 在C#中使用RSA进行加密和解密

    这篇文章向您展示了如何在c#.net Windows窗体应用程序中使用RSA算法对字符串进行加密和解密.RSA是由Ron Rivest,Adi Shamir和Leonard Adleman开发的非对称 ...

随机推荐

  1. ES5 bind方法

    function getConfig(colors,size,otherOptions){ console.log(colors,size,otherOptions); } var defaultCo ...

  2. 【转】PowerShell入门(七):管道——在命令行上编程

    转至:http://www.cnblogs.com/ceachy/archive/2013/02/22/PowerShell_Pipeline.html 管道对于Shell来说是个化腐朽为神奇的东西, ...

  3. WPF:常见问题

    1.自定义Main函数 背景: wpf 默认的Main函数在 App.g.cs文件中,在App.xmal.cs内自定义Main函数后冲突. 解决方法: 法一: 1)新建class1.cs类,在其中设置 ...

  4. js-统计选项个数

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  5. 异常问题解决Error:Execution failed for task ':app:processDebugManifest'

    Error:Execution failed for task ':app:processDebugManifest' www.MyException.Cn  网友分享于:2015-12-28  浏览 ...

  6. 通过Mac远程调试iPhone/iPad上的网页(转)

    我们知道在 Mac/PC 上的浏览器都有 Web 检查器这类的工具(如最著名的 Firebug)对前端开发进行调试,而在 iPhone/iPad 由于限于屏幕的大小和触摸屏的使用习惯,直接对网页调试非 ...

  7. jQuery验证元素是否为空的两种常用方法

    这篇文章主要介绍了jQuery验证元素是否为空的两种常用方法,实例分析了两种常用的判断为空技巧,非常具有实用价值,需要的朋友可以参考下 本文实例讲述了jQuery验证元素是否为空的两种常用方法.分享给 ...

  8. Python virtualenv安装库报错SSL: CERTIFICATE_VERIFY_FAILED

    Python virtualenv安装库报错SSL: CERTIFICATE_VERIFY_FAILED 问题描述 使用pip按照virtualenv报错,如下: pip install virtua ...

  9. 使用R进行倾向得分匹配

    pacman::p_load(knitr, wakefield, MatchIt, tableone, captioner)set.seed(1234)library(wakefield)df.pat ...

  10. DSO激活时,生成主数据SID时报错:原因,主数据允许小写字母没有勾上

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...