基于HTTP在互联网传输敏感数据的消息摘要、签名与加密方案

一、关键词

HTTP,HTTPS,AES,SHA-1,MD5,消息摘要,数字签名,数字加密,Java,Servlet,Bouncy Castle

 

二、名词解释

   数字摘要:是将任意长度的消息变成固定长度的短消息,它类似于一个自变量是消息的函数,也就是Hash函数。数字摘要就是采用单项Hash函数将需要加密的明文“摘要”成一串固定长度(128位)的密文这一串密文又称为数字指纹,它有固定的长度,而且不同的明文摘要成密文,其结果总是不同的,而同样的明文其摘要必定一致。

   AES:密码学中的高级加密标准(Advanced Encryption Standard,AES),又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。是一种对称加密算法。

   SHA-1:安全哈希算法(Secure Hash Algorithm)主要适用于数字签名标准 (Digital Signature Standard DSS)里面定义的数字签名算法(Digital Signature Algorithm DSA)。 SHA1有如下特性:不可以从消息摘要中复原信息;两个不同的消息不会产生同样的消息摘要。

   MD5:Message Digest Algorithm MD5(中文名为消息摘要算法第五版)为计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护。

 

三、项目背景

   某合作公司需要通过互联网向我司传递一些用户数据,但是我所在项目组的外网服务器上并无部署https,只能基于http进行数据传输。为了保护双方共同的用户数据,必须对在互联网上传输的信息进行加密处理。

 

四、方案设计

   这里涉及到两个问题,一是采用什么样的远程消息传递框架,二是如何对传输的数据进行加密。

   本人平时开发所用的语言主要是Java,对于Jsp/Servlet还比较熟悉,结合去年参加过所在公司的微信公众号开发的经验,设计出了如下方案:

   1.在客户端采用构造http post请求,把用户数据加密后放入request body中,并在http参数中放入调用方的签名;

   2.服务端接收到请求,提取参数进行签名校验,通过后从request body中提取密文进行解密,然后进行后续处理,最终生成响应返回给客户端。

   以下是具体处理的流程图:

 

   在数据加密阶段,基于性能以及效率考虑,采用了Bouncy Castle提供的AES算法,而生成签名则采用了jdk提供的SHA-1,值得注意的是,基于安全考虑,消息密文的消息摘要也被列入到参与数字签名的参数之一。

 

五、代码实现

1.AES加密工具类:

Java代码   收藏代码
  1. import org.apache.commons.lang.StringUtils;  
  2. import org.apache.log4j.Logger;  
  3. import org.bouncycastle.crypto.CipherParameters;  
  4. import org.bouncycastle.crypto.engines.AESFastEngine;  
  5. import org.bouncycastle.crypto.modes.CBCBlockCipher;  
  6. import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;  
  7. import org.bouncycastle.crypto.params.KeyParameter;  
  8. import org.bouncycastle.crypto.params.ParametersWithIV;  
  9. import org.bouncycastle.util.encoders.Hex;  
  10.   
  11. /** 
  12.  * AES encryption and decryption tool. 
  13.  *  
  14.  * @author ben 
  15.  * @creation 2014年3月20日 
  16.  */  
  17. public class AESTool {  
  18.     protected static final Logger log = Logger.getLogger(AESTool.class);  
  19.   
  20.     private byte[] initVector = { 0x320x370x360x350x340x330x320x31,  
  21.             0x380x270x360x350x330x230x320x31 };  
  22.   
  23.     /** 
  24.      * FIXME For demo only, should rewrite this method in your product environment! 
  25.      *  
  26.      * @param appid 
  27.      * @return 
  28.      */  
  29.     public String findKeyById(String appid) {  
  30.         // Fake key.  
  31.         String key = "123456789012345678901234567890~!";  
  32.         return key;  
  33.     }  
  34.   
  35.     /** 
  36.      * Encrypt the content with a given key using aes algorithm. 
  37.      *  
  38.      * @param content 
  39.      * @param key 
  40.      *          must contain exactly 32 characters 
  41.      * @return 
  42.      * @throws Exception  
  43.      */  
  44.     public String encrypt(String content, String key) throws Exception {  
  45.         if (key == null) {  
  46.             throw new IllegalArgumentException("Key cannot be null!");  
  47.         }  
  48.         String encrypted = null;  
  49.         byte[] keyBytes = key.getBytes();  
  50.         if (keyBytes.length != 32 && keyBytes.length != 24  
  51.                 && keyBytes.length != 16) {  
  52.             throw new IllegalArgumentException(  
  53.                     "Key length must be 128/192/256 bits!");  
  54.         }  
  55.         byte[] encryptedBytes = null;  
  56.         encryptedBytes = encrypt(content.getBytes(), keyBytes, initVector);  
  57.         encrypted = new String(Hex.encode(encryptedBytes));  
  58.         return encrypted;  
  59.     }  
  60.   
  61.     /** 
  62.      * Decrypt the content with a given key using aes algorithm. 
  63.      *  
  64.      * @param content 
  65.      * @param key 
  66.      *          must contain exactly 32 characters 
  67.      * @return 
  68.      * @throws Exception  
  69.      */  
  70.     public String decrypt(String content, String key) throws Exception {  
  71.         if (key == null) {  
  72.             throw new IllegalArgumentException("Key cannot be null!");  
  73.         }  
  74.         String decrypted = null;  
  75.         byte[] encryptedContent = Hex.decode(content);  
  76.         byte[] keyBytes = key.getBytes();  
  77.         byte[] decryptedBytes = null;  
  78.         if (keyBytes.length != 32 && keyBytes.length != 24  
  79.                 && keyBytes.length != 16) {  
  80.             throw new IllegalArgumentException(  
  81.                     "Key length must be 128/192/256 bits!");  
  82.         }  
  83.         decryptedBytes = decrypt(encryptedContent, keyBytes, initVector);  
  84.         decrypted = new String(decryptedBytes);  
  85.         return decrypted;  
  86.     }  
  87.   
  88.     /** 
  89.      * Encrypt data. 
  90.      *  
  91.      * @param plain 
  92.      * @param key 
  93.      * @param iv 
  94.      * @return 
  95.      * @throws Exception 
  96.      */  
  97.     public byte[] encrypt(byte[] plain, byte[] key, byte[] iv) throws Exception {  
  98.         PaddedBufferedBlockCipher aes = new PaddedBufferedBlockCipher(  
  99.                 new CBCBlockCipher(new AESFastEngine()));  
  100.         CipherParameters ivAndKey = new ParametersWithIV(new KeyParameter(key),  
  101.                 iv);  
  102.         aes.init(true, ivAndKey);  
  103.         return cipherData(aes, plain);  
  104.     }  
  105.   
  106.     /** 
  107.      * Decrypt data. 
  108.      *  
  109.      * @param cipher 
  110.      * @param key 
  111.      * @param iv 
  112.      * @return 
  113.      * @throws Exception 
  114.      */  
  115.     public byte[] decrypt(byte[] cipher, byte[] key, byte[] iv)  
  116.             throws Exception {  
  117.         PaddedBufferedBlockCipher aes = new PaddedBufferedBlockCipher(  
  118.                 new CBCBlockCipher(new AESFastEngine()));  
  119.         CipherParameters ivAndKey = new ParametersWithIV(new KeyParameter(key),  
  120.                 iv);  
  121.         aes.init(false, ivAndKey);  
  122.         return cipherData(aes, cipher);  
  123.     }  
  124.   
  125.     /** 
  126.      * Encrypt or decrypt data. 
  127.      *  
  128.      * @param cipher 
  129.      * @param data 
  130.      * @return 
  131.      * @throws Exception 
  132.      */  
  133.     private byte[] cipherData(PaddedBufferedBlockCipher cipher, byte[] data)  
  134.             throws Exception {  
  135.         int minSize = cipher.getOutputSize(data.length);  
  136.         byte[] outBuf = new byte[minSize];  
  137.         int length1 = cipher.processBytes(data, 0, data.length, outBuf, 0);  
  138.         int length2 = cipher.doFinal(outBuf, length1);  
  139.         int actualLength = length1 + length2;  
  140.         byte[] result = new byte[actualLength];  
  141.         System.arraycopy(outBuf, 0, result, 0, result.length);  
  142.         return result;  
  143.     }  
  144.   
  145.     public static void main(String[] args) throws Exception {  
  146.         AESTool aesTool = new AESTool();  
  147.         String appid = "canairport001";  
  148.         String key = aesTool.findKeyById(appid);  
  149.         String xml = "<root><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name></root>";  
  150.         String encrypted = aesTool.encrypt(xml, key);  
  151.         System.out.println("encrypted: \n" + encrypted);  
  152.         System.out.println("encrypted length: \n" + encrypted.length());  
  153.         String decrypted = aesTool.decrypt(encrypted, key);  
  154.         System.out.println("decrypted: \n" + decrypted);  
  155.         System.out.println("decrypted length: \n" + decrypted.length());  
  156.         boolean isSuccessful = StringUtils.equals(decrypted, xml);  
  157.         System.out.println(isSuccessful);  
  158.     }  
  159. }  

 

2.数字签名工具类: 

Java代码   收藏代码
  1. import java.security.MessageDigest;  
  2. import java.security.NoSuchAlgorithmException;  
  3. import java.util.ArrayList;  
  4. import java.util.Collections;  
  5. import java.util.List;  
  6.   
  7. import org.apache.commons.lang.StringUtils;  
  8. import org.apache.log4j.Logger;  
  9.   
  10. /** 
  11.  * @author lixuanbin 
  12.  * @creation 2013-1-30 
  13.  */  
  14. public class SignatureUtil {  
  15.     protected static Logger log = Logger.getLogger(SignatureUtil.class);  
  16.   
  17.     private static final char[] hexArray = "0123456789ABCDEF".toCharArray();  
  18.   
  19.     private String encryptionAlgorithm = "SHA-1";  
  20.   
  21.     public String bytesToHexString(byte[] bytes) {  
  22.         char[] hexChars = new char[bytes.length * 2];  
  23.         for (int j = 0; j < bytes.length; j++) {  
  24.             int v = bytes[j] & 0xFF;  
  25.             hexChars[j * 2] = hexArray[v >>> 4];  
  26.             hexChars[j * 2 + 1] = hexArray[v & 0x0F];  
  27.         }  
  28.         return new String(hexChars);  
  29.     }  
  30.   
  31.     public byte[] hexStringToBytes(String s) {  
  32.         int len = s.length();  
  33.         byte[] data = new byte[len / 2];  
  34.         for (int i = 0; i < len; i += 2) {  
  35.             data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character  
  36.                     .digit(s.charAt(i + 1), 16));  
  37.         }  
  38.         return data;  
  39.     }  
  40.   
  41.     /** 
  42.      * 使用指定算法生成消息摘要,默认是md5 
  43.      *  
  44.      * @param strSrc 
  45.      *            , a string will be encrypted; <br/> 
  46.      * @param encName 
  47.      *            , the algorithm name will be used, dafault to "MD5"; <br/> 
  48.      * @return 
  49.      */  
  50.     public String digest(String strSrc, String encName) {  
  51.         MessageDigest md = null;  
  52.         String strDes = null;  
  53.         byte[] bt = strSrc.getBytes();  
  54.         try {  
  55.             if (encName == null || encName.equals("")) {  
  56.                 encName = "MD5";  
  57.             }  
  58.             md = MessageDigest.getInstance(encName);  
  59.             md.update(bt);  
  60.             strDes = bytesToHexString(md.digest()); // to HexString  
  61.         } catch (NoSuchAlgorithmException e) {  
  62.             log.error("Invalid algorithm: " + encName);  
  63.             return null;  
  64.         }  
  65.         return strDes;  
  66.     }  
  67.   
  68.     /** 
  69.      * 根据appid、token、lol以及时间戳来生成签名 
  70.      *  
  71.      * @param appid 
  72.      * @param token 
  73.      * @param lol 
  74.      * @param millis 
  75.      * @return 
  76.      */  
  77.     public String generateSignature(String appid, String token, String lol,  
  78.             long millis) {  
  79.         String timestamp = String.valueOf(millis);  
  80.         String signature = null;  
  81.         if (StringUtils.isNotBlank(token) && StringUtils.isNotBlank(timestamp)  
  82.                 && StringUtils.isNotBlank(appid)) {  
  83.             List<String> srcList = new ArrayList<String>();  
  84.             srcList.add(timestamp);  
  85.             srcList.add(appid);  
  86.             srcList.add(token);  
  87.             srcList.add(lol);  
  88.             // 按照字典序逆序拼接参数  
  89.             Collections.sort(srcList);  
  90.             Collections.reverse(srcList);  
  91.             StringBuilder sb = new StringBuilder();  
  92.             for (int i = 0; i < srcList.size(); i++) {  
  93.                 sb.append(srcList.get(i));  
  94.             }  
  95.             signature = digest(sb.toString(), encryptionAlgorithm);  
  96.             srcList.clear();  
  97.             srcList = null;  
  98.         }  
  99.         return signature;  
  100.     }  
  101.   
  102.     /** 
  103.      * 验证签名: <br/> 
  104.      * 1.根据appid获取该渠道的token;<br/> 
  105.      * 2.根据appid、token、lol以及时间戳计算一次签名;<br/> 
  106.      * 3.比较传过来的签名以及计算出的签名是否一致; 
  107.      * @param signature 
  108.      * @param appid 
  109.      * @param lol 
  110.      * @param millis 
  111.      * @return 
  112.      */  
  113.     public boolean isValid(String signature, String appid, String lol,  
  114.             long millis) {  
  115.         String token = findTokenById(appid);  
  116.         String calculatedSignature = generateSignature(appid, token, lol,  
  117.                 millis);  
  118.         log.info("calculated signature: \n" + calculatedSignature);  
  119.         if (StringUtils.equals(calculatedSignature, signature)) {  
  120.             return true;  
  121.         } else {  
  122.             return false;  
  123.         }  
  124.     }  
  125.   
  126.     /** 
  127.      * FIXME For demo only, should be a different string in production. 
  128.      * @param appid 
  129.      * @return 
  130.      */  
  131.     public String findTokenById(String appid) {  
  132.         String token = "#@!1234567890!@#";  
  133.         return token;  
  134.     }  
  135.   
  136.     public static void main(String[] args) {  
  137.         SignatureUtil generator = new SignatureUtil();  
  138.         String xmlString = "<root><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name><name>test</name></root>";  
  139.         System.out.println(xmlString.getBytes().length);  
  140.         String digest = generator.digest(xmlString, "MD5");  
  141.         System.out.println(digest);  
  142.         System.out.println(digest.getBytes().length);  
  143.         String appid = "canairport001";  
  144.         String token = generator.findTokenById(appid);  
  145.         long millis = System.currentTimeMillis();  
  146.         String signature = generator.generateSignature(appid, token, digest,  
  147.                 millis);  
  148.         System.out.println(signature);  
  149.         boolean isValid = generator.isValid(signature, appid, digest, millis);  
  150.         System.out.println(isValid);  
  151.     }  
  152. }  
 

 3.发送方代码:

Java代码   收藏代码
  1. import java.io.IOException;  
  2. import java.util.HashMap;  
  3. import java.util.Iterator;  
  4. import java.util.Map;  
  5. import java.util.Map.Entry;  
  6.   
  7. import org.apache.commons.lang.StringUtils;  
  8. import org.apache.http.HttpEntity;  
  9. import org.apache.http.HttpHost;  
  10. import org.apache.http.HttpResponse;  
  11. import org.apache.http.HttpStatus;  
  12. import org.apache.http.auth.AuthScope;  
  13. import org.apache.http.auth.UsernamePasswordCredentials;  
  14. import org.apache.http.client.ClientProtocolException;  
  15. import org.apache.http.client.methods.HttpPost;  
  16. import org.apache.http.conn.params.ConnRoutePNames;  
  17. import org.apache.http.entity.StringEntity;  
  18. import org.apache.http.impl.client.DefaultHttpClient;  
  19. import org.apache.http.message.BasicHeader;  
  20. import org.apache.http.protocol.HTTP;  
  21. import org.apache.http.util.EntityUtils;  
  22. import org.apache.log4j.Logger;  
  23.   
  24. /** 
  25.  * @author ben 
  26.  * @creation 2014年6月9日 
  27.  */  
  28. public class HttpclientUtil {  
  29.     protected static final Logger log = Logger.getLogger(HttpclientUtil.class);  
  30.   
  31.     /** 
  32.      * 根据传入的uri和参数map拼接成实际uri 
  33.      *  
  34.      * @param uri 
  35.      * @param paraMap 
  36.      * @return 
  37.      */  
  38.     public String buildUri(String uri, Map<String, String> paraMap) {  
  39.         StringBuilder sb = new StringBuilder();  
  40.         uri = StringUtils.trim(uri);  
  41.         uri = StringUtils.removeEnd(uri, "/");  
  42.         uri = StringUtils.removeEnd(uri, "?");  
  43.         sb.append(uri);  
  44.         if (paraMap != null && !paraMap.isEmpty()) {  
  45.             sb.append("?");  
  46.             Iterator<Entry<String, String>> iterator = paraMap.entrySet()  
  47.                     .iterator();  
  48.             while (iterator.hasNext()) {  
  49.                 Map.Entry<String, String> pair = iterator.next();  
  50.                 try {  
  51.                     String keyString = pair.getKey();  
  52.                     String valueString = pair.getValue();  
  53.                     sb.append(keyString);  
  54.                     sb.append("=");  
  55.                     sb.append(valueString);  
  56.                     sb.append("&");  
  57.                 } catch (Exception e) {  
  58.                     log.error(e, e);  
  59.                 }  
  60.             }  
  61.         }  
  62.         return StringUtils.removeEnd(sb.toString(), "&");  
  63.     }  
  64.   
  65.     /** 
  66.      * Post an xml string to a specific host. 
  67.      *  
  68.      * @param targetHost 
  69.      * @param targetPort 
  70.      * @param protocol 
  71.      * @param proxyHost 
  72.      * @param proxyPort 
  73.      * @param proxyUser 
  74.      * @param proxyPassword 
  75.      * @param uri 
  76.      * @param paraMap 
  77.      * @param xml 
  78.      * @param charset 
  79.      * @return 
  80.      * @throws ClientProtocolException 
  81.      * @throws IOException 
  82.      */  
  83.     public String postXmlString(String targetHost, int targetPort,  
  84.             String protocol, String proxyHost, int proxyPort, String proxyUser,  
  85.             String proxyPassword, String uri, Map<String, String> paraMap,  
  86.             String xml, String charset) throws ClientProtocolException,  
  87.             IOException {  
  88.         String result = null;  
  89.         DefaultHttpClient httpclient = new DefaultHttpClient();  
  90.         if (StringUtils.isNotBlank(proxyHost) && proxyPort > 0) {  
  91.             // 设置上网代理  
  92.             AuthScope authScope = new AuthScope(proxyHost, proxyPort);  
  93.             if (StringUtils.isNotBlank(proxyUser)  
  94.                     && StringUtils.isNotBlank(proxyPassword)) {  
  95.                 // 设置上网代理的用户名和密码  
  96.                 UsernamePasswordCredentials upc = new UsernamePasswordCredentials(  
  97.                         proxyUser, proxyPassword);  
  98.                 httpclient.getCredentialsProvider().setCredentials(authScope,  
  99.                         upc);  
  100.             }  
  101.             HttpHost proxy = new HttpHost(proxyHost, proxyPort);  
  102.             httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY,  
  103.                     proxy);  
  104.         }  
  105.         HttpHost host = new HttpHost(targetHost, targetPort, protocol);  
  106.         uri = buildUri(uri, paraMap);  
  107.         log.info("post uri: " + uri);  
  108.         log.info("post content: " + xml);  
  109.         HttpPost post = new HttpPost(uri);  
  110.         StringEntity se = new StringEntity(xml,  
  111.                 StringUtils.isNotBlank(charset) ? charset : "utf-8");  
  112.         se.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE,  
  113.                 "application/xml"));  
  114.         post.setEntity(se);  
  115.         HttpResponse response = httpclient.execute(host, post);  
  116.         if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {  
  117.             HttpEntity entity = response.getEntity();  
  118.             if (entity != null) {  
  119.                 result = EntityUtils.toString(entity);  
  120.                 log.info("post result: " + result);  
  121.             }  
  122.         } else {  
  123.             log.error("post failed, status code: "  
  124.                     + response.getStatusLine().getStatusCode());  
  125.         }  
  126.         return result;  
  127.     }  
  128.   
  129.     public static void main(String[] args) throws Exception {  
  130.         AESTool aes = new AESTool();  
  131.         SignatureUtil signatureUtil = new SignatureUtil();  
  132.         String appid = "canairport001";  
  133.         String token = signatureUtil.findTokenById(appid);  
  134.         String key = aes.findKeyById(appid);  
  135.         long millis = System.currentTimeMillis();  
  136.         String xml = "<dependency><groupId>commons-lang</groupId><artifactId>commons-lang</artifactId><version>2.5</version></dependency>";  
  137.         xml = aes.encrypt(xml, key);  
  138.         String lol = signatureUtil.digest(xml, "MD5");  
  139.         String signature = signatureUtil.generateSignature(appid, token, lol,  
  140.                 millis);  
  141.         log.info("lol: \n" + lol);  
  142.         log.info("signature: \n" + signature);  
  143.         String uri = "http://127.0.0.1:8080/demo/psginfo.do";  
  144.         Map<String, String> paraMap = new HashMap<String, String>();  
  145.         paraMap.put("s", signature);  
  146.         paraMap.put("a", appid);  
  147.         paraMap.put("t", String.valueOf(millis));  
  148.         paraMap.put("l", lol);  
  149.         paraMap.put("o""test");  
  150.         HttpclientUtil util = new HttpclientUtil();  
  151.         try {  
  152.             String result = util.postXmlString("127.0.0.1"8080"http"null,  
  153.                     0nullnull, uri, paraMap, xml, "utf-8");  
  154.             result = aes.decrypt(result, key);  
  155.             System.out.println(result);  
  156.         } catch (ClientProtocolException e) {  
  157.             e.printStackTrace();  
  158.         } catch (IOException e) {  
  159.             e.printStackTrace();  
  160.         }  
  161.     }  
  162. }  

 

4.服务端代码:

Java代码   收藏代码
  1. import java.io.BufferedReader;  
  2. import java.io.IOException;  
  3. import java.io.InputStream;  
  4. import java.io.InputStreamReader;  
  5. import java.io.PrintWriter;  
  6. import java.io.UnsupportedEncodingException;  
  7.   
  8. import javax.servlet.ServletException;  
  9. import javax.servlet.annotation.WebServlet;  
  10. import javax.servlet.http.HttpServlet;  
  11. import javax.servlet.http.HttpServletRequest;  
  12. import javax.servlet.http.HttpServletResponse;  
  13.   
  14. import org.apache.commons.lang.StringUtils;  
  15. import org.apache.log4j.Logger;  
  16.   
  17. import co.speedar.wechat.util.AESTool;  
  18. import co.speedar.wechat.util.SignatureUtil;  
  19.   
  20. /** 
  21.  * Servlet implementation class PsginfoServlet 
  22.  */  
  23. @WebServlet(urlPatterns = { "/psginfo.do" }, loadOnStartup = 1)  
  24. public class PsginfoServlet extends HttpServlet {  
  25.     protected static final Logger log = Logger.getLogger(PsginfoServlet.class);  
  26.     private static final long serialVersionUID = 6536688299231165548L;  
  27.   
  28.     private SignatureUtil signatureUtil = new SignatureUtil();  
  29.   
  30.     private AESTool aes = new AESTool();  
  31.   
  32.     /** 
  33.      * @see HttpServlet#HttpServlet() 
  34.      */  
  35.     public PsginfoServlet() {  
  36.         super();  
  37.     }  
  38.   
  39.     /** 
  40.      * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse 
  41.      *      response) 
  42.      */  
  43.     protected void doGet(HttpServletRequest request,  
  44.             HttpServletResponse response) throws ServletException, IOException {  
  45.         String echostr = request.getParameter("e");  
  46.         log.info("echostr before echo: " + echostr);  
  47.         String signature = request.getParameter("s");  
  48.         String appid = request.getParameter("a");  
  49.         String timestamp = request.getParameter("t");  
  50.         String lol = request.getParameter("l");  
  51.         long millis = Long.valueOf(timestamp);  
  52.         // Need to check signature in product mode.  
  53.         if (signatureUtil.isValid(signature, appid, lol, millis)) {  
  54.             PrintWriter writer = response.getWriter();  
  55.             log.info("echostr after echo: " + echostr);  
  56.             writer.print(echostr);  
  57.             writer.flush();  
  58.             writer.close();  
  59.         }  
  60.     }  
  61.   
  62.     /** 
  63.      * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse 
  64.      *      response) 
  65.      */  
  66.     protected void doPost(HttpServletRequest request,  
  67.             HttpServletResponse response) throws ServletException, IOException {  
  68.         // Get request parameters.  
  69.         String signature = request.getParameter("s");  
  70.         String appid = request.getParameter("a");  
  71.         String timestamp = request.getParameter("t");  
  72.         String lol = request.getParameter("l");  
  73.         String operation = request.getParameter("o");  
  74.         long millis = Long.valueOf(timestamp);  
  75.   
  76.         // Get xml data.  
  77.         String encoding = StringUtils  
  78.                 .isNotBlank(request.getCharacterEncoding()) ? request  
  79.                 .getCharacterEncoding() : "utf-8";  
  80.         String requestXmlString = getXmlStringFromHttpRequest(request);  
  81.         String digest = signatureUtil.digest(requestXmlString, "MD5");  
  82.   
  83.         // Check signature and digest.  
  84.         if (StringUtils.equals(digest, lol)) {  
  85.             if (signatureUtil.isValid(signature, appid, lol, millis)) {  
  86.                 try {  
  87.                     String key = aes.findKeyById(appid);  
  88.                     requestXmlString = aes.decrypt(requestXmlString, key);  
  89.                     log.info("received xml data:\n" + requestXmlString);  
  90.                     // 校验xml合法性并执行相应动作  
  91.                     String responseXmlString = doSomeThing(requestXmlString,  
  92.                             operation);  
  93.                     responseXmlString = aes.encrypt(responseXmlString, key);  
  94.                     log.info("responsed xml data:\n" + responseXmlString);  
  95.                     response.setCharacterEncoding(encoding);  
  96.                     PrintWriter writer = response.getWriter();  
  97.                     writer.print(responseXmlString);  
  98.                     writer.flush();  
  99.                     writer.close();  
  100.                 } catch (Exception e) {  
  101.                     log.error(e, e);  
  102.                 }  
  103.             } else {  
  104.                 log.error("invalid signature");  
  105.             }  
  106.         } else {  
  107.             log.error("invalid digest.");  
  108.         }  
  109.     }  
  110.   
  111.     /** 
  112.      * TODO Write your own business here. 
  113.      *  
  114.      * @param xml 
  115.      * @param operation 
  116.      * @return 
  117.      */  
  118.     private String doSomeThing(String xml, String operation) {  
  119.         return "done";  
  120.     }  
  121.   
  122.     /** 
  123.      * Extract xml string form http request. 
  124.      *  
  125.      * @param request 
  126.      * @return 
  127.      * @throws IOException 
  128.      */  
  129.     private String getXmlStringFromHttpRequest(HttpServletRequest request) {  
  130.         String requestXmlString = "";  
  131.         try {  
  132.             InputStream inputStream = request.getInputStream();  
  133.             String encoding = StringUtils.isNotBlank(request  
  134.                     .getCharacterEncoding()) ? request.getCharacterEncoding()  
  135.                     : "utf-8";  
  136.             requestXmlString = getXmlStringFromInputStream(inputStream,  
  137.                     encoding);  
  138.             encoding = null;  
  139.             inputStream.close();  
  140.             inputStream = null;  
  141.         } catch (IOException e) {  
  142.             log.error(e, e);  
  143.         }  
  144.   
  145.         return requestXmlString;  
  146.     }  
  147.   
  148.     /** 
  149.      * Extract xml string from the inputStream. 
  150.      *  
  151.      * @param inputStream 
  152.      * @param charsetName 
  153.      * @return 
  154.      */  
  155.     private String getXmlStringFromInputStream(InputStream inputStream,  
  156.             String charsetName) {  
  157.         String resultXmlString = "";  
  158.         String tempString = null;  
  159.         BufferedReader bufferedReader;  
  160.         try {  
  161.             bufferedReader = new BufferedReader(new InputStreamReader(  
  162.                     inputStream, charsetName));  
  163.             tempString = bufferedReader.readLine();  
  164.             while (tempString != null) {  
  165.                 resultXmlString += tempString;  
  166.                 tempString = bufferedReader.readLine();  
  167.             }  
  168.             tempString = null;  
  169.             bufferedReader.close();  
  170.             bufferedReader = null;  
  171.         } catch (UnsupportedEncodingException e) {  
  172.             log.error(e, e);  
  173.         } catch (IOException e) {  
  174.             log.error(e, e);  
  175.         }  
  176.         return StringUtils.trim(resultXmlString);  
  177.     }  
  178.   
  179. }  

 

5.maven配置:

Xml代码   收藏代码
  1. <dependency>   
  2.     <groupId>org.bouncycastle</groupId>   
  3.     <artifactId>bcprov-jdk16</artifactId>   
  4.     <version>1.46</version>   
  5. </dependency>  
  6. <dependency>  
  7.     <groupId>commons-lang</groupId>  
  8.     <artifactId>commons-lang</artifactId>  
  9.     <version>2.5</version>  
  10. </dependency>  
  11. <dependency>  
  12.     <groupId>org.apache.httpcomponents</groupId>  
  13.     <artifactId>httpclient</artifactId>  
  14.     <version>4.2.5</version>  
  15. </dependency>  
  16. <dependency>  
  17.     <groupId>org.apache.httpcomponents</groupId>  
  18.     <artifactId>httpmime</artifactId>  
  19.     <version>4.2.5</version>  
  20. </dependency>  

 

六、结语

   在本方案设计实现过程中,消息传递的框架采用的是Java开发者所熟悉的Servlet技术,摘要、签名、加密所采用的算法,以及所依赖的第三方jar也是比较有口碑又大众化的货,对于有类似需要的开发者来说,本方案具有一定的参考意义。远程传递消息框架以及生成签名的环节,主要是模仿了微信公众平台的消息交互方式以及生成签名的思路,而有所创新的一小点是,把消息密文的MD5值也参与到了签名运算中,增加了被仿冒的难度,同时也便于服务方校验消息在传递过程中是否有被第三方所篡改。

   基于简化工程配置的考虑,本示例项目中没有使用spring,您可以在您的生产项目中把本示例中的代码改造成春哥的单例业务bean。密钥、token建议别直接写到春哥的context配置文件中,而是写在您的生产容器的环境变量中,防止被窃取。

   另外,在本方案中生成签名的参数您可以酌情增减并调换顺序,替换签名所采用的算法,或者根据您的实际需要“个性化”一下您的加密算法,以期达到更好的安全效果。

   Last but not the least,在密钥以及token交换的阶段,请采取您所认可的安全有效的方式进行,譬如面对面,微信,qq,微薄私信,电话,短信,邮件(可以参考本人之前写过的一篇文章:http://lixuanbin.iteye.com/blog/1544344

 

七、参考资料

【Java加密与解密的艺术】——作者:梁栋,出版日期:2010年12月,ISBN:978-7-111-29762-8

http://stackoverflow.com/questions/4243650/aes-encryption-decryption-with-bouncycastle-example-in-j2me

http://stackoverflow.com/questions/6729834/need-solution-for-wrong-iv-length-in-aes

http://baike.baidu.com/view/941329.htm?fr=aladdin

http://baike.baidu.com/subview/133041/5358738.htm?fr=aladdin

http://baike.baidu.com/view/1228622.htm?fr=aladdin

http://baike.baidu.com/view/7636.htm?fr=aladdin

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值