`
lixuanbin
  • 浏览: 135930 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

基于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加密工具类:

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.engines.AESFastEngine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.util.encoders.Hex;

/**
 * AES encryption and decryption tool.
 * 
 * @author ben
 * @creation 2014年3月20日
 */
public class AESTool {
	protected static final Logger log = Logger.getLogger(AESTool.class);

	private byte[] initVector = { 0x32, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31,
			0x38, 0x27, 0x36, 0x35, 0x33, 0x23, 0x32, 0x31 };

	/**
	 * FIXME For demo only, should rewrite this method in your product environment!
	 * 
	 * @param appid
	 * @return
	 */
	public String findKeyById(String appid) {
		// Fake key.
		String key = "123456789012345678901234567890~!";
		return key;
	}

	/**
	 * Encrypt the content with a given key using aes algorithm.
	 * 
	 * @param content
	 * @param key
	 * 			must contain exactly 32 characters
	 * @return
	 * @throws Exception 
	 */
	public String encrypt(String content, String key) throws Exception {
		if (key == null) {
			throw new IllegalArgumentException("Key cannot be null!");
		}
		String encrypted = null;
		byte[] keyBytes = key.getBytes();
		if (keyBytes.length != 32 && keyBytes.length != 24
				&& keyBytes.length != 16) {
			throw new IllegalArgumentException(
					"Key length must be 128/192/256 bits!");
		}
		byte[] encryptedBytes = null;
		encryptedBytes = encrypt(content.getBytes(), keyBytes, initVector);
		encrypted = new String(Hex.encode(encryptedBytes));
		return encrypted;
	}

	/**
	 * Decrypt the content with a given key using aes algorithm.
	 * 
	 * @param content
	 * @param key
	 * 			must contain exactly 32 characters
	 * @return
	 * @throws Exception 
	 */
	public String decrypt(String content, String key) throws Exception {
		if (key == null) {
			throw new IllegalArgumentException("Key cannot be null!");
		}
		String decrypted = null;
		byte[] encryptedContent = Hex.decode(content);
		byte[] keyBytes = key.getBytes();
		byte[] decryptedBytes = null;
		if (keyBytes.length != 32 && keyBytes.length != 24
				&& keyBytes.length != 16) {
			throw new IllegalArgumentException(
					"Key length must be 128/192/256 bits!");
		}
		decryptedBytes = decrypt(encryptedContent, keyBytes, initVector);
		decrypted = new String(decryptedBytes);
		return decrypted;
	}

	/**
	 * Encrypt data.
	 * 
	 * @param plain
	 * @param key
	 * @param iv
	 * @return
	 * @throws Exception
	 */
	public byte[] encrypt(byte[] plain, byte[] key, byte[] iv) throws Exception {
		PaddedBufferedBlockCipher aes = new PaddedBufferedBlockCipher(
				new CBCBlockCipher(new AESFastEngine()));
		CipherParameters ivAndKey = new ParametersWithIV(new KeyParameter(key),
				iv);
		aes.init(true, ivAndKey);
		return cipherData(aes, plain);
	}

	/**
	 * Decrypt data.
	 * 
	 * @param cipher
	 * @param key
	 * @param iv
	 * @return
	 * @throws Exception
	 */
	public byte[] decrypt(byte[] cipher, byte[] key, byte[] iv)
			throws Exception {
		PaddedBufferedBlockCipher aes = new PaddedBufferedBlockCipher(
				new CBCBlockCipher(new AESFastEngine()));
		CipherParameters ivAndKey = new ParametersWithIV(new KeyParameter(key),
				iv);
		aes.init(false, ivAndKey);
		return cipherData(aes, cipher);
	}

	/**
	 * Encrypt or decrypt data.
	 * 
	 * @param cipher
	 * @param data
	 * @return
	 * @throws Exception
	 */
	private byte[] cipherData(PaddedBufferedBlockCipher cipher, byte[] data)
			throws Exception {
		int minSize = cipher.getOutputSize(data.length);
		byte[] outBuf = new byte[minSize];
		int length1 = cipher.processBytes(data, 0, data.length, outBuf, 0);
		int length2 = cipher.doFinal(outBuf, length1);
		int actualLength = length1 + length2;
		byte[] result = new byte[actualLength];
		System.arraycopy(outBuf, 0, result, 0, result.length);
		return result;
	}

	public static void main(String[] args) throws Exception {
		AESTool aesTool = new AESTool();
		String appid = "canairport001";
		String key = aesTool.findKeyById(appid);
		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>";
		String encrypted = aesTool.encrypt(xml, key);
		System.out.println("encrypted: \n" + encrypted);
		System.out.println("encrypted length: \n" + encrypted.length());
		String decrypted = aesTool.decrypt(encrypted, key);
		System.out.println("decrypted: \n" + decrypted);
		System.out.println("decrypted length: \n" + decrypted.length());
		boolean isSuccessful = StringUtils.equals(decrypted, xml);
		System.out.println(isSuccessful);
	}
}

 

2.数字签名工具类: 

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

/**
 * @author lixuanbin
 * @creation 2013-1-30
 */
public class SignatureUtil {
	protected static Logger log = Logger.getLogger(SignatureUtil.class);

	private static final char[] hexArray = "0123456789ABCDEF".toCharArray();

	private String encryptionAlgorithm = "SHA-1";

	public String bytesToHexString(byte[] bytes) {
		char[] hexChars = new char[bytes.length * 2];
		for (int j = 0; j < bytes.length; j++) {
			int v = bytes[j] & 0xFF;
			hexChars[j * 2] = hexArray[v >>> 4];
			hexChars[j * 2 + 1] = hexArray[v & 0x0F];
		}
		return new String(hexChars);
	}

	public byte[] hexStringToBytes(String s) {
		int len = s.length();
		byte[] data = new byte[len / 2];
		for (int i = 0; i < len; i += 2) {
			data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character
					.digit(s.charAt(i + 1), 16));
		}
		return data;
	}

	/**
	 * 使用指定算法生成消息摘要,默认是md5
	 * 
	 * @param strSrc
	 *            , a string will be encrypted; <br/>
	 * @param encName
	 *            , the algorithm name will be used, dafault to "MD5"; <br/>
	 * @return
	 */
	public String digest(String strSrc, String encName) {
		MessageDigest md = null;
		String strDes = null;
		byte[] bt = strSrc.getBytes();
		try {
			if (encName == null || encName.equals("")) {
				encName = "MD5";
			}
			md = MessageDigest.getInstance(encName);
			md.update(bt);
			strDes = bytesToHexString(md.digest()); // to HexString
		} catch (NoSuchAlgorithmException e) {
			log.error("Invalid algorithm: " + encName);
			return null;
		}
		return strDes;
	}

	/**
	 * 根据appid、token、lol以及时间戳来生成签名
	 * 
	 * @param appid
	 * @param token
	 * @param lol
	 * @param millis
	 * @return
	 */
	public String generateSignature(String appid, String token, String lol,
			long millis) {
		String timestamp = String.valueOf(millis);
		String signature = null;
		if (StringUtils.isNotBlank(token) && StringUtils.isNotBlank(timestamp)
				&& StringUtils.isNotBlank(appid)) {
			List<String> srcList = new ArrayList<String>();
			srcList.add(timestamp);
			srcList.add(appid);
			srcList.add(token);
			srcList.add(lol);
			// 按照字典序逆序拼接参数
			Collections.sort(srcList);
			Collections.reverse(srcList);
			StringBuilder sb = new StringBuilder();
			for (int i = 0; i < srcList.size(); i++) {
				sb.append(srcList.get(i));
			}
			signature = digest(sb.toString(), encryptionAlgorithm);
			srcList.clear();
			srcList = null;
		}
		return signature;
	}

	/**
	 * 验证签名: <br/>
	 * 1.根据appid获取该渠道的token;<br/>
	 * 2.根据appid、token、lol以及时间戳计算一次签名;<br/>
	 * 3.比较传过来的签名以及计算出的签名是否一致;
	 * @param signature
	 * @param appid
	 * @param lol
	 * @param millis
	 * @return
	 */
	public boolean isValid(String signature, String appid, String lol,
			long millis) {
		String token = findTokenById(appid);
		String calculatedSignature = generateSignature(appid, token, lol,
				millis);
		log.info("calculated signature: \n" + calculatedSignature);
		if (StringUtils.equals(calculatedSignature, signature)) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * FIXME For demo only, should be a different string in production.
	 * @param appid
	 * @return
	 */
	public String findTokenById(String appid) {
		String token = "#@!1234567890!@#";
		return token;
	}

	public static void main(String[] args) {
		SignatureUtil generator = new SignatureUtil();
		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>";
		System.out.println(xmlString.getBytes().length);
		String digest = generator.digest(xmlString, "MD5");
		System.out.println(digest);
		System.out.println(digest.getBytes().length);
		String appid = "canairport001";
		String token = generator.findTokenById(appid);
		long millis = System.currentTimeMillis();
		String signature = generator.generateSignature(appid, token, digest,
				millis);
		System.out.println(signature);
		boolean isValid = generator.isValid(signature, appid, digest, millis);
		System.out.println(isValid);
	}
}
 

 3.发送方代码:

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.apache.log4j.Logger;

/**
 * @author ben
 * @creation 2014年6月9日
 */
public class HttpclientUtil {
	protected static final Logger log = Logger.getLogger(HttpclientUtil.class);

	/**
	 * 根据传入的uri和参数map拼接成实际uri
	 * 
	 * @param uri
	 * @param paraMap
	 * @return
	 */
	public String buildUri(String uri, Map<String, String> paraMap) {
		StringBuilder sb = new StringBuilder();
		uri = StringUtils.trim(uri);
		uri = StringUtils.removeEnd(uri, "/");
		uri = StringUtils.removeEnd(uri, "?");
		sb.append(uri);
		if (paraMap != null && !paraMap.isEmpty()) {
			sb.append("?");
			Iterator<Entry<String, String>> iterator = paraMap.entrySet()
					.iterator();
			while (iterator.hasNext()) {
				Map.Entry<String, String> pair = iterator.next();
				try {
					String keyString = pair.getKey();
					String valueString = pair.getValue();
					sb.append(keyString);
					sb.append("=");
					sb.append(valueString);
					sb.append("&");
				} catch (Exception e) {
					log.error(e, e);
				}
			}
		}
		return StringUtils.removeEnd(sb.toString(), "&");
	}

	/**
	 * Post an xml string to a specific host.
	 * 
	 * @param targetHost
	 * @param targetPort
	 * @param protocol
	 * @param proxyHost
	 * @param proxyPort
	 * @param proxyUser
	 * @param proxyPassword
	 * @param uri
	 * @param paraMap
	 * @param xml
	 * @param charset
	 * @return
	 * @throws ClientProtocolException
	 * @throws IOException
	 */
	public String postXmlString(String targetHost, int targetPort,
			String protocol, String proxyHost, int proxyPort, String proxyUser,
			String proxyPassword, String uri, Map<String, String> paraMap,
			String xml, String charset) throws ClientProtocolException,
			IOException {
		String result = null;
		DefaultHttpClient httpclient = new DefaultHttpClient();
		if (StringUtils.isNotBlank(proxyHost) && proxyPort > 0) {
			// 设置上网代理
			AuthScope authScope = new AuthScope(proxyHost, proxyPort);
			if (StringUtils.isNotBlank(proxyUser)
					&& StringUtils.isNotBlank(proxyPassword)) {
				// 设置上网代理的用户名和密码
				UsernamePasswordCredentials upc = new UsernamePasswordCredentials(
						proxyUser, proxyPassword);
				httpclient.getCredentialsProvider().setCredentials(authScope,
						upc);
			}
			HttpHost proxy = new HttpHost(proxyHost, proxyPort);
			httpclient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY,
					proxy);
		}
		HttpHost host = new HttpHost(targetHost, targetPort, protocol);
		uri = buildUri(uri, paraMap);
		log.info("post uri: " + uri);
		log.info("post content: " + xml);
		HttpPost post = new HttpPost(uri);
		StringEntity se = new StringEntity(xml,
				StringUtils.isNotBlank(charset) ? charset : "utf-8");
		se.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE,
				"application/xml"));
		post.setEntity(se);
		HttpResponse response = httpclient.execute(host, post);
		if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {
			HttpEntity entity = response.getEntity();
			if (entity != null) {
				result = EntityUtils.toString(entity);
				log.info("post result: " + result);
			}
		} else {
			log.error("post failed, status code: "
					+ response.getStatusLine().getStatusCode());
		}
		return result;
	}

	public static void main(String[] args) throws Exception {
		AESTool aes = new AESTool();
		SignatureUtil signatureUtil = new SignatureUtil();
		String appid = "canairport001";
		String token = signatureUtil.findTokenById(appid);
		String key = aes.findKeyById(appid);
		long millis = System.currentTimeMillis();
		String xml = "<dependency><groupId>commons-lang</groupId><artifactId>commons-lang</artifactId><version>2.5</version></dependency>";
		xml = aes.encrypt(xml, key);
		String lol = signatureUtil.digest(xml, "MD5");
		String signature = signatureUtil.generateSignature(appid, token, lol,
				millis);
		log.info("lol: \n" + lol);
		log.info("signature: \n" + signature);
		String uri = "http://127.0.0.1:8080/demo/psginfo.do";
		Map<String, String> paraMap = new HashMap<String, String>();
		paraMap.put("s", signature);
		paraMap.put("a", appid);
		paraMap.put("t", String.valueOf(millis));
		paraMap.put("l", lol);
		paraMap.put("o", "test");
		HttpclientUtil util = new HttpclientUtil();
		try {
			String result = util.postXmlString("127.0.0.1", 8080, "http", null,
					0, null, null, uri, paraMap, xml, "utf-8");
			result = aes.decrypt(result, key);
			System.out.println(result);
		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

 

4.服务端代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import co.speedar.wechat.util.AESTool;
import co.speedar.wechat.util.SignatureUtil;

/**
 * Servlet implementation class PsginfoServlet
 */
@WebServlet(urlPatterns = { "/psginfo.do" }, loadOnStartup = 1)
public class PsginfoServlet extends HttpServlet {
	protected static final Logger log = Logger.getLogger(PsginfoServlet.class);
	private static final long serialVersionUID = 6536688299231165548L;

	private SignatureUtil signatureUtil = new SignatureUtil();

	private AESTool aes = new AESTool();

	/**
	 * @see HttpServlet#HttpServlet()
	 */
	public PsginfoServlet() {
		super();
	}

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doGet(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		String echostr = request.getParameter("e");
		log.info("echostr before echo: " + echostr);
		String signature = request.getParameter("s");
		String appid = request.getParameter("a");
		String timestamp = request.getParameter("t");
		String lol = request.getParameter("l");
		long millis = Long.valueOf(timestamp);
		// Need to check signature in product mode.
		if (signatureUtil.isValid(signature, appid, lol, millis)) {
			PrintWriter writer = response.getWriter();
			log.info("echostr after echo: " + echostr);
			writer.print(echostr);
			writer.flush();
			writer.close();
		}
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doPost(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		// Get request parameters.
		String signature = request.getParameter("s");
		String appid = request.getParameter("a");
		String timestamp = request.getParameter("t");
		String lol = request.getParameter("l");
		String operation = request.getParameter("o");
		long millis = Long.valueOf(timestamp);

		// Get xml data.
		String encoding = StringUtils
				.isNotBlank(request.getCharacterEncoding()) ? request
				.getCharacterEncoding() : "utf-8";
		String requestXmlString = getXmlStringFromHttpRequest(request);
		String digest = signatureUtil.digest(requestXmlString, "MD5");

		// Check signature and digest.
		if (StringUtils.equals(digest, lol)) {
			if (signatureUtil.isValid(signature, appid, lol, millis)) {
				try {
					String key = aes.findKeyById(appid);
					requestXmlString = aes.decrypt(requestXmlString, key);
					log.info("received xml data:\n" + requestXmlString);
					// 校验xml合法性并执行相应动作
					String responseXmlString = doSomeThing(requestXmlString,
							operation);
					responseXmlString = aes.encrypt(responseXmlString, key);
					log.info("responsed xml data:\n" + responseXmlString);
					response.setCharacterEncoding(encoding);
					PrintWriter writer = response.getWriter();
					writer.print(responseXmlString);
					writer.flush();
					writer.close();
				} catch (Exception e) {
					log.error(e, e);
				}
			} else {
				log.error("invalid signature");
			}
		} else {
			log.error("invalid digest.");
		}
	}

	/**
	 * TODO Write your own business here.
	 * 
	 * @param xml
	 * @param operation
	 * @return
	 */
	private String doSomeThing(String xml, String operation) {
		return "done";
	}

	/**
	 * Extract xml string form http request.
	 * 
	 * @param request
	 * @return
	 * @throws IOException
	 */
	private String getXmlStringFromHttpRequest(HttpServletRequest request) {
		String requestXmlString = "";
		try {
			InputStream inputStream = request.getInputStream();
			String encoding = StringUtils.isNotBlank(request
					.getCharacterEncoding()) ? request.getCharacterEncoding()
					: "utf-8";
			requestXmlString = getXmlStringFromInputStream(inputStream,
					encoding);
			encoding = null;
			inputStream.close();
			inputStream = null;
		} catch (IOException e) {
			log.error(e, e);
		}

		return requestXmlString;
	}

	/**
	 * Extract xml string from the inputStream.
	 * 
	 * @param inputStream
	 * @param charsetName
	 * @return
	 */
	private String getXmlStringFromInputStream(InputStream inputStream,
			String charsetName) {
		String resultXmlString = "";
		String tempString = null;
		BufferedReader bufferedReader;
		try {
			bufferedReader = new BufferedReader(new InputStreamReader(
					inputStream, charsetName));
			tempString = bufferedReader.readLine();
			while (tempString != null) {
				resultXmlString += tempString;
				tempString = bufferedReader.readLine();
			}
			tempString = null;
			bufferedReader.close();
			bufferedReader = null;
		} catch (UnsupportedEncodingException e) {
			log.error(e, e);
		} catch (IOException e) {
			log.error(e, e);
		}
		return StringUtils.trim(resultXmlString);
	}

}

 

5.maven配置:

<dependency> 
    <groupId>org.bouncycastle</groupId> 
    <artifactId>bcprov-jdk16</artifactId> 
    <version>1.46</version> 
</dependency>
<dependency>
    <groupId>commons-lang</groupId>
    <artifactId>commons-lang</artifactId>
    <version>2.5</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.2.5</version>
</dependency>
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpmime</artifactId>
    <version>4.2.5</version>
</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

 

 
  • 大小: 92.6 KB
4
0
分享到:
评论
8 楼 xukunddp 2016-03-09  
文章质量不错
7 楼 lixuanbin 2015-11-12  
暂不存在 写道
请问加密的appid是什么用的,然后密匙的生成是怎么弄得,服务器端解密通过appid查找密匙是什么意思??

用于标识并验证请求来源,秘钥是双方协商好的用于加密解密数据,token用于生成签名。
6 楼 暂不存在 2015-11-11  
请问加密的appid是什么用的,然后密匙的生成是怎么弄得,服务器端解密通过appid查找密匙是什么意思??
5 楼 lixuanbin 2014-12-16  
weiqiang.yang 写道
路过,String.getBytes()推荐指定个固定的编码,否则在不同的运行环境下可能会得出不一样的结果,测试代码如下

System.out.println("using default :" + Hex.encodeHexString("中国".getBytes()));
System.out.println("using utf8 :" + Hex.encodeHexString("中国".getBytes(Charset.forName("utf8"))));
System.out.println("using gbk :" + Hex.encodeHexString("中国".getBytes(Charset.forName("gbk"))));


export LANG=zh_CN.gbk
# 输出
using default :d6d0b9fa
using utf8 :e4b8ade59bbd
using gbk :d6d0b9fa


export LANG=zh_CN.utf8
# 输出
using default :e4b8ade59bbd
using utf8 :e4b8ade59bbd
using gbk :d6d0b9fa


默认使用了环境的编码设置

我在servlet端的处理是取浏览器编码设置,如果为空则使用utf-8。当然,像vivi你这样双方都显式指定传输编码是最稳妥了。
4 楼 weiqiang.yang 2014-11-22  
路过,String.getBytes()推荐指定个固定的编码,否则在不同的运行环境下可能会得出不一样的结果,测试代码如下

System.out.println("using default :" + Hex.encodeHexString("中国".getBytes()));
System.out.println("using utf8 :" + Hex.encodeHexString("中国".getBytes(Charset.forName("utf8"))));
System.out.println("using gbk :" + Hex.encodeHexString("中国".getBytes(Charset.forName("gbk"))));


export LANG=zh_CN.gbk
# 输出
using default :d6d0b9fa
using utf8 :e4b8ade59bbd
using gbk :d6d0b9fa


export LANG=zh_CN.utf8
# 输出
using default :e4b8ade59bbd
using utf8 :e4b8ade59bbd
using gbk :d6d0b9fa


默认使用了环境的编码设置
3 楼 lixuanbin 2014-06-24  
liubey 写道
另外楼主你这最后拿httpclient发送的时候是按xml的请求头去发送字符串吗?
反正我知道不管是post还是get就算字符串被base64后传送过程中也会产生空格。用XML请求头肯定没事

请求头采用的是application/xml,另外在servlet处理的时候,也使用了StringUtils进行trim处理头尾的空格。
2 楼 liubey 2014-06-17  
另外楼主你这最后拿httpclient发送的时候是按xml的请求头去发送字符串吗?
反正我知道不管是post还是get就算字符串被base64后传送过程中也会产生空格。用XML请求头肯定没事
1 楼 liubey 2014-06-16  
不错,谢谢博主,这个文章质量很高,尤其是加密那块,不仅要有所传输信息的消息摘要,还要有相关信息的签名,这思路很好。

相关推荐

    江南天安医疗行业敏感数据解决方案

    本方案通过云服务器密码机和云密码服务管理系统构建密码资源池,提供基础的密码服务,在...通过安全接入系统实现数据传输加密服务;通过数字签名验证系统实现数据电子签名服务;通过敏感数据加密系统实现数据加密服务。

    优秀商用密码应用方案与建设方案并案例汇编.zip

    数据完整性保护:采用国际通用的消息摘要算法对商业数据进行完整性校验,确保数据在传输过程中不被篡改。 数字签名与身份认证:采用数字签名技术对商业数据进行签名,保证数据的真实性和不可否认性。同时,采用...

    RSA加密解密签名加签验签RsaUtils工具类

    公钥加密私钥解密与私钥加签公钥验签是基于非对称加密技术的不同应用方式,两者在用途上有明显区别: 公钥加密私钥解密: 目的:主要服务于信息的保密传输,确保只有预定的接收者才能解密信息。 流程: 发送方获取...

    DES数据加密

    又如,dbms的一些软件包总是包含一些加密方法以使复制文件这一功能对一些敏感数据是无效的,或者需要用户的密码。所有这些加密算法都要有高效的加密和解密能力。 幸运的是,在所有的加密算法中最简单的一种就是...

    sql数据库相关加密知识

    只对用户的敏感数据加密可以提高数据库访问速度。这样有利于用户在效率与安全性之间进行自主选择。 (5)多级密钥管理模式: 主密钥和主密钥变量保存在安全区域,二级密钥受主密钥变量加密保护,数据加密的密钥...

    VC 密聊程序 RSA消息加密发送例子.rar

     本 "密聊"程序采用 2048bit 的 RSA 数据加密算法对数据进行加密和数字签名,由于 RSA 加密少量的信息需要大量的时间,适合用来传递密钥或对重要而敏感且较少的信息通讯.实现的消息通讯安全功能包括  1. 身份验证,...

    java封装好的md5

    当您需要对敏感数据进行安全传输时,MD5是一种常用的加密方式。在Java中,MD5已经封装好了,您可以直接使用它来加密您的数据。封装好的MD5可以让您在开发过程中更加便捷地使用MD5算法,从而提高开发效率。

    网络安全技术与方案.doc

    计算机网络技术与应用 ——网络安全技术与方案 学 校 天津理工大学 学 生 学 院 专 业 学 号 网络安全技术与方案 学生: 班级: 摘要: 随着互联网的快速发展,网络安全问题已经不容忽视。网络安全技术是指致力于解 ...

    安全通讯工具"密聊"源代码

    "密聊"采用 2048bit 的 RSA 数据加密算法对数据进行加密和数字签名,由于 RSA 加密少量的信息需要大量的时间,适合用来传递密钥或对重要而敏感且较少的信息通讯.实现的消息通讯安全功能包括 1. 身份验证,使收件人确信...

    Java安全性编程实例(徐迎晓)

    # 第五章和第六章介绍基于摘要和签名技术的数字证书。这是Java安全中确定身份的主要技术。其中第五章介绍了数字证书的创建、签发、验证和维护等,第六章介绍了多个证书组成的证书链(CertPath)的创建和验证。 # 第...

    安全通讯工具&quot;密聊&quot;源代码

    采用 2048bit 的 RSA 数据加密算法对数据进行加密和数字签名,由于 RSA 加密少量的信息需要大量的时间,适合用来传递密钥或对重要而敏感且较少的信息通讯.实现的消息通讯安全功能包括1. 身份验证,使收件人确信发件人...

    物联网安全技术框架-《物联网安全导论》.pptx

    对这样的威胁最有效的解决方法就是数据加密,即以加密格式存储和传输敏感数据。 物联网安全技术框架-《物联网安全导论》全文共75页,当前为第4页。 2.1 常用信息安全技术简介 2. 身份验证 身份验证是指通过一定的手

    接口说明书

    2、只对敏感数据加密 vi. 消息数量约定:默认一个消息一个订单,单据生成和下发并行,生成多少下发多少。当涉及批量处理时,默认最多处理500个。 vii. 时间约定:时间格式使用 YYYY-MM-DD HH:mm:ss 的格式,精确到...

    GP-卡规范V2.1.1

    D.3.5. 关键敏感数据的加密和解密 158 D.4. 安全通道的APDU命令 159 D.4.1. INITIALIZE UPDATE命令 159 D.4.1.1. 定义和范围 159 D.4.1.2. 命令消息 160 D.4.1.3. 引用控制参数P1——密钥版本号 160 D.4.1.4. 引用...

    商用密码体系简介.pdf

    数据安全保障:商用密码技术广泛应用于电子商务、电子政务、移动支付、在线银行、云计算服务等各种商业和公共服务领域,通过加密手段确保用户数据在传输和存储过程中的机密性,防止数据被非法获取和利用。...

    libssl1.1-1.1.0g-3.x86_64.rpm

    SSL也即Secure Socket Layer,是由网景公司为了传输敏感数据而提出的协议。SSL使用私钥加密传输的数据,防止被窃听。SSL最普遍的使用适合和浏览器结合使用,但是其他很多的应用也可以使用SSL。一般而言,使用了SSL的...

    网络安全设计方案.doc

    数据加密:数据加密是在数据传输、存储过程中防止非法窃取、篡改信息的有效手段。 安全审计: 是识别与防止网络攻击行为、追查网络泄密行为的重要措施之一。具体包括两方面的内 容,一是采用网络监控与入侵防范系统...

    网络安全设计方案(2).doc

    数据加密:数据加密是在数据传输、存储过程中防止非法窃取、篡改信息的有效手段。 安全审计: 是识别与防止网络攻击行为、追查网络泄密行为的重要措施之一。具体包括两方面的内 容,一是采用网络监控与入侵防范系统...

    XXX网络安全设计方案.txt

    因此,对于 XXX 网络这样带有重要信息传输的网络,数据在链路上传输必须加密。 并通过数字签名及认证技术来保数据在网上传输的真实性、机密性、可靠性及完整性。 2.3.2 内部的安全风险分析 主要是保证 XXX 网络结构...

    浅谈黑客与网络安全-.docx

    然而,在另一方面网络监听也给以太网的平安带来了极大的隐患,许多的网络入侵往往都伴随着以太网内的网络监听行为上,从而造成口令失窃,敏感数据被截取等等连锁性平安事件。 2.6 欺骗攻击 欺骗攻击是攻击者创造一个...

Global site tag (gtag.js) - Google Analytics