转载自http://news.newhua.com/news/2010/1118/108512.shtml
打算写这个类用于spark插件加密对话消息用。
RSA的Java实现不能一次加密很大的字符,自己处理了一下,见下面的代码。
Base64编码类用的是一个Public domain Base64 for javahttp://iharder.sourceforge.net/current/java/base64/
其他的保存公钥到文件等简单的实现,就不详细说了,看代码吧。
import java.security.*; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.HashMap; import java.util.Map; import javax.crypto.*; import java.io.*; public class Encryptor { private static final String KEY_FILENAME = "c:\\mykey.dat"; private static final String OTHERS_KEY_FILENAME = "c:\\Otherskey.dat"; // private static final int KEY_SIZE = 1024; // private static final int BLOCK_SIZE = 117; // private static final int OUTPUT_BLOCK_SIZE = 128; private static final int KEY_SIZE = 2048; //RSA key 是多少位的 private static final int BLOCK_SIZE = 245; //一次RSA加密操作所允许的最大长度 //这个值与 KEY_SIZE 已经padding方法有关。因为 1024的key的输出是128,2048key输出是256字节 //可能11个字节用于保存padding信息了,所以最多可用的就只有245字节了。 private static final int OUTPUT_BLOCK_SIZE = 256; private SecureRandom secrand; private Cipher rsaCipher; private KeyPair keys; private Map<String, Key> allUserKeys; public Encryptor() throws Exception { try { allUserKeys = new HashMap<String, Key>(); secrand = new SecureRandom(); //SunJCE Provider 中只支持ECB mode,试了一下只有PKCS1PADDING可以直接还原原始数据, //NOPadding导致解压出来的都是blocksize长度的数据,还要自己处理 //参见http://java.sun.com/javase/6/docs/technotes/guides/security/SunProviders.html#SunJCEProvider // //另外根据 Open-JDK-6.b17-src(http://www.docjar.com/html/api/com/sun/crypto/provider/RSACipher.java.html) // 中代码的注释,使用RSA来加密大量数据不是一种标准的用法。所以现有实现一次doFinal调用之进行一个RSA操作, //如果用doFinal来加密超过的一个操作所允许的长度数据将抛出异常。 //根据keysize的长度,典型的1024个长度的key和PKCS1PADDING一起使用时 //一次doFinal调用只能加密117个byte的数据。(NOPadding 和1024 keysize时128个字节长度) //(2048长度的key和PKCS1PADDING 最多允许245字节一次) //想用来加密大量数据的只能自己用其他办法实现了。可能RSA加密速度比较慢吧,要用AES才行 rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); throw e; } ObjectInputStream in; try { in = new ObjectInputStream(new FileInputStream(KEY_FILENAME)); } catch (FileNotFoundException e) { if (false == GenerateKeys()) { throw e; } LoadKeys(); return; } keys = (KeyPair) in.readObject(); in.close(); LoadKeys(); } /* * 生成自己的公钥和私钥 */ private Boolean GenerateKeys() { try { KeyPairGenerator keygen = KeyPairGenerator.getInstance("RSA"); // secrand = new SecureRandom(); // sedSeed之后会造成 生成的密钥都是一样的 // secrand.setSeed("chatencrptor".getBytes()); // 初始化随机产生器 //key长度至少512长度,不过好像说现在用2048才算比较安全的了 keygen.initialize(KEY_SIZE, secrand); // 初始化密钥生成器 keys = keygen.generateKeyPair(); // 生成密钥组 AddKey("me", EncodeKey(keys.getPublic())); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); return false; } ObjectOutputStream out; try { out = new ObjectOutputStream(new FileOutputStream(KEY_FILENAME)); } catch (IOException e) { e.printStackTrace(); return false; } try { out.writeObject(keys); } catch (IOException e) { e.printStackTrace(); return false; } finally { try { out.close(); } catch (IOException e) { e.printStackTrace(); return false; } } return true; } public String EncryptMessage(String toUser, String Message) throws IOException { Key pubkey = allUserKeys.get(toUser); if ( pubkey == null ) { throw new IOException("NoKeyForThisUser") ; } try { //PublicKey pubkey = keys.getPublic(); rsaCipher.init(Cipher.ENCRYPT_MODE, pubkey, secrand); //System.out.println(rsaCipher.getBlockSize()); 返回0,非block 加密算法来的? //System.out.println(Message.getBytes("utf-8").length); //byte[] encryptedData = rsaCipher.doFinal(Message.getBytes("utf-8")); byte[] data = Message.getBytes("utf-8"); int blocks = data.length / BLOCK_SIZE ; int lastBlockSize = data.length % BLOCK_SIZE ; byte [] encryptedData = new byte[ (lastBlockSize == 0 ? blocks : blocks + 1)* OUTPUT_BLOCK_SIZE]; for (int i=0; i < blocks; i++) { //int thisBlockSize = ( i + 1 ) * BLOCK_SIZE > data.length ? data.length - i * BLOCK_SIZE : BLOCK_SIZE ; rsaCipher.doFinal(data,i * BLOCK_SIZE, BLOCK_SIZE, encryptedData ,i * OUTPUT_BLOCK_SIZE); } if (lastBlockSize != 0 ){ rsaCipher.doFinal(data, blocks * BLOCK_SIZE, lastBlockSize,encryptedData ,blocks * OUTPUT_BLOCK_SIZE); } //System.out.println(encrypted.length); 如果要机密的数据不足128/256字节,加密后补全成为变为256长度的。 //数量比较小时,Base64.GZIP产生的长度更长,没什么优势 //System.out.println(Base64.encodeBytes(encrypted,Base64.GZIP).length()); //System.out.println(Base64.encodeBytes(encrypted).length()); //System.out.println (rsaCipher.getOutputSize(30)); //这个getOutputSize 只对 输入小于最大的block时才能得到正确的结果。其实就是补全 数据为128/256 字节 return Base64.encodeBytes(encryptedData); } catch (InvalidKeyException e) { e.printStackTrace(); throw new IOException("InvalidKey") ; }catch (ShortBufferException e) { e.printStackTrace(); throw new IOException("ShortBuffer") ; } catch (UnsupportedEncodingException e) { e.printStackTrace(); throw new IOException("UnsupportedEncoding") ; } catch (IllegalBlockSizeException e) { e.printStackTrace(); throw new IOException("IllegalBlockSize") ; } catch (BadPaddingException e) { e.printStackTrace(); throw new IOException("BadPadding") ; }finally { //catch 中 return 或者throw之前都会先调用一下这里 } } public String DecryptMessage(String Message) throws IOException { byte[] decoded = Base64.decode(Message); PrivateKey prikey = keys.getPrivate(); try { rsaCipher.init(Cipher.DECRYPT_MODE, prikey, secrand); int blocks = decoded.length / OUTPUT_BLOCK_SIZE; ByteArrayOutputStream decodedStream = new ByteArrayOutputStream(decoded.length); for (int i =0 ;i < blocks ; i ++ ) { decodedStream.write (rsaCipher.doFinal(decoded,i * OUTPUT_BLOCK_SIZE, OUTPUT_BLOCK_SIZE)); } return new String(decodedStream.toByteArray(), "UTF-8"); } catch (InvalidKeyException e) { e.printStackTrace(); throw new IOException("InvalidKey"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); throw new IOException("UnsupportedEncoding"); } catch (IllegalBlockSizeException e) { e.printStackTrace(); throw new IOException("IllegalBlockSize"); } catch (BadPaddingException e) { e.printStackTrace(); throw new IOException("BadPadding"); } finally { // catch 中 return 或者throw之前都会先调用一下这里。 } } public boolean AddKey(String user, String key) { PublicKey publickey; try { publickey = DecodePublicKey(key); } catch (Exception e) { return false; } allUserKeys.put(user, publickey); SaveKeys(); return true; } private boolean LoadKeys() { BufferedReader input; try { input = new BufferedReader(new InputStreamReader( new FileInputStream(OTHERS_KEY_FILENAME))); } catch (FileNotFoundException e1) { // e1.printStackTrace(); return false; } try { allUserKeys.clear(); String line; while ((line = input.readLine()) != null) { String[] temp = line.split("\\|"); String user = temp[0]; PublicKey key = DecodePublicKey(temp[1]); allUserKeys.put(user, key); } } catch (Exception e) { return false; } finally { try { input.close(); } catch (Exception e) { return false; } } return true; } private boolean SaveKeys() { FileWriter output; try { output = new FileWriter(OTHERS_KEY_FILENAME); } catch (IOException e1) { // 1.printStackTrace(); return false; } try { for (String user : allUserKeys.keySet()) { Key key = allUserKeys.get(user); output.write(user + "|" + EncodeKey(key) + "\n"); } } catch (IOException e1) { // 1.printStackTrace(); return false; } finally { try { output.close(); } catch (Exception e) { return false; } } return true; } /** * 解密base64编码得到公钥 * * @param key * 密钥字符串(经过base64编码) * @throws Exception */ public static PublicKey DecodePublicKey(String key) throws Exception { byte[] keyBytes; keyBytes = Base64.decode(key); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey publicKey = keyFactory.generatePublic(keySpec); return publicKey; } /** * 解密base64编码得到私钥 * * @param key * 密钥字符串(经过base64编码) * @throws Exception */ public static PrivateKey DecodePrivateKey(String key) throws Exception { byte[] keyBytes; keyBytes = Base64.decode(key); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory.generatePrivate(keySpec); return privateKey; } /** * 编码key为base64字符串 * * @return */ public static String EncodeKey(Key key) { byte[] keyBytes = key.getEncoded(); // System.out.print(key.getFormat()) ; String s = Base64.encodeBytes(keyBytes); return s; } } ===============测试类============================ import java.util.Iterator ; import java.security.Security ; import java.security.Provider ; public class Test { /** * @param args */ public static void main(String[] args) { // Provider [ ] providers = Security.getProviders () ; // for ( int i = 0 ; i < providers.length ; i++ ) // { // String name = providers[i].getName () ; // String info = providers[i].getInfo () ; // double version = providers[i].getVersion () ; // System.out.println ("-------------------------------------" ) ; // System.out.println ( "name: " + name ) ; // System.out.println ( "info: " + info ) ; // System.out.println ( "version: " + version ) ; // // for ( Iterator iter = providers[i].keySet().iterator () ; iter.hasNext () ; ) // { // String key = (String) iter.next () ; // System.out.println ( "\t" + key + // "\t" + // providers[i].getProperty ( key ) ) ; // } // // System.out.println ( // // "-------------------------------------" ); // } // // int i = 123/ 256; // System.out.println(i); // i = 257/ 256; // System.out.println(i); // i = 512/ 256; // System.out.println(i); // i = 513/ 256; // System.out.println(i); //String str = "widebriht的测试,哈哈^_^"; //String str = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4dYTv3xxgsu2SmgqJzPWzSOziMDnOV+tAxQGumNAJKcBxOlD5hQlqTGRnUoqIp8z3brioFJBtMs8wAeA+pfpJhFyqodI9frr0I3Y4PUgwRlL7ozGA9hu9CBOADQNKE7g7UgOTQ/joE26YoYPCHKx/vYQncOe45axOCqcXBWViw20WgPr1g0qj9CarQLM7e4dUNMCYcX0ZGvqIy7Js2FjuLsNaK249k9y2fjIqqu8FzhjvIEDCtrMNyTvV5coV5rDGTEC5KfJft0oeCTXrhcgzMBdaG9DXFQezQIepMdPAT+kga/qJ69b95I02LMwyCDhwPmH873UNQAmdMpJ7bZTTwIDAQAB"; String str = "MIIBIjANBgkqhkiG9w0BMIIBIjANBgkqhkiG9w0BMIIBIjANBgkqhkiG9w0BMIIBIjANBgkqhkiG9w0BMIIBIjANBgkqhkiG9w0BMIIBIjANBgkqhkiG9w0BMIIBIjANBgkqhkiG9w0BMIIBIjANBgkqhkiG9w0BMIIBIjANBgkqhkiG9w0BMIIBIjANBgkqhkiG9w0BMIIBIjANBgkqhkiG9w0BMIIBIjANBgkqhkiG9w0Bddddd"; try { Encryptor ddd = new Encryptor(); str = ddd.EncryptMessage("me", str); // System.out.println(str.length()); String str2 = "jhahahh my god "; str2 = ddd.EncryptMessage("me", str2); str2 = ddd.DecryptMessage(str2); str = ddd.DecryptMessage(str); } catch (Exception e) { if (e.getMessage() == "NoKeyForThisUser") { System.out.println("NoKeyForThisUser"); } } // str = ddd.DecryptMessage( // "inI5y3PfFXt0oNWXuYLBbTkIaaxZFzgWuMfYTqpT91b51UUQN0T8iK6l8XnS+WShC/ZGL9noX6vO8sDmiJ+z1I7/WTgvOOW5XE+G7+edGzh6bfRGPxhG16MJpl2y/FsIErsKgZmybERE8F0IP551/b0xGmrZhyj09/NwZln3qUg="); //System.out.println(str); } }
1.设置成为开发者模式
登录微信工作平台,选择高级功能-进入开发模式,成为开发者。需要做如下图配置。URL配置的信息是指,微信的后台服务器把您的用户消息发送到该URL处理。Token是你和微信之间的一个密码,用来验证消息是否是从微信的服务发送而来,而不是其他来攻击你的系统。
2.实现GET方法
从文档中知道,我们需要实现POST和GET方法,GET方法用于验证微信和你的通讯验证,POST用于消息处理。
新建Servlet HelloWeChat,先实现其中的GET方法
// TODO 为了简单起见,先不对消息来源进行校验
response.setContentType("text/html;charset=UTF-8");
PrintWriter pw = response.getWriter();
String echo = request.getParameter("echostr");
echo = new String(echo.getBytes("ISO-8859-1"),"UTF-8");
pw.println(echo);
}
3.实现POST方法
POST方法首先接收到微信公众平台传送过来的XML,从中提取消息发送人和消息内容。更加消息发送内容,你可以增加自己的处理逻辑,最后拼装成回复消息XML,返回给微信公众平台。
response.setContentType("text/html;charset=UTF-8");
PrintWriter pw = response.getWriter();
String wxMsgXml = IOUtils.toString(request.getInputStream(),"utf-8");
WeChatTextMessage textMsg = null;
try {
textMsg = getWeChatTextMessage(wxMsgXml);
} catch (Exception e) {
e.printStackTrace();
}
StringBuffer replyMsg = new StringBuffer();
if(textMsg != null){
//增加你所需要的处理逻辑,这里只是简单重复消息
replyMsg.append("您给我的消息是:");
replyMsg.append(textMsg.getContent());
}
else{
replyMsg.append(":)不是文本的消息,我暂时看不懂");
}
String returnXml = getReplyTextMessage(replyMsg.toString(), textMsg.getFromUserName());
pw.println(returnXml);
}
关于调试,这里推荐一个工具Fiddler,你可以模拟微信的POST消息到你的本地,而不必每次部署到服务器上进行调试。关于Fiddler的POST数据使用方法,可以参考下图标注内容。
4.部署并测试
完成第一步,并和你的公众帐号好进行对话,回复消息没有问题的话,那就恭喜你了。
5.依赖库
使用maven的同学,添加以下依赖即可。非maven用户,找到这些库添加到buider path中即可。
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.3</version>
</dependency>
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
/**
* Servlet implementation class HelloWeChat
*/
public class HelloWeChat extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public HelloWeChat() {
super();
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO 为了简单起见,先不对消息来源进行校验
response.setContentType("text/html;charset=UTF-8");
PrintWriter pw = response.getWriter();
String echo = request.getParameter("echostr");
echo = new String(echo.getBytes("ISO-8859-1"),"UTF-8");
pw.println(echo);
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter pw = response.getWriter();
String wxMsgXml = IOUtils.toString(request.getInputStream(),"utf-8");
WeChatTextMessage textMsg = null;
try {
textMsg = getWeChatTextMessage(wxMsgXml);
} catch (Exception e) {
e.printStackTrace();
}
StringBuffer replyMsg = new StringBuffer();
if(textMsg != null){
//增加你所需要的处理逻辑,这里只是简单重复消息
replyMsg.append("您给我的消息是:");
replyMsg.append(textMsg.getContent());
}
else{
replyMsg.append(":)不是文本的消息,我暂时看不懂");
}
String returnXml = getReplyTextMessage(replyMsg.toString(), textMsg.getFromUserName());
pw.println(returnXml);
}
private WeChatTextMessage getWeChatTextMessage(String xml){
XStream xstream = new XStream(new DomDriver());
xstream.alias("xml", WeChatTextMessage.class);
xstream.aliasField("ToUserName", WeChatTextMessage.class, "toUserName");
xstream.aliasField("FromUserName", WeChatTextMessage.class, "fromUserName");
xstream.aliasField("CreateTime", WeChatTextMessage.class, "createTime");
xstream.aliasField("MsgType", WeChatTextMessage.class, "messageType");
xstream.aliasField("Content", WeChatTextMessage.class, "content");
xstream.aliasField("MsgId", WeChatTextMessage.class, "msgId");
WeChatTextMessage wechatTextMessage = (WeChatTextMessage)xstream.fromXML(xml);
return wechatTextMessage;
}
private String getReplyTextMessage(String content, String weChatUser){
WeChatReplyTextMessage we = new WeChatReplyTextMessage();
we.setMessageType("text");
we.setFuncFlag("0");
we.setCreateTime(new Long(new Date().getTime()).toString());
we.setContent(content);
we.setToUserName(weChatUser);
we.setFromUserName("shanghaiweather");//TODO 你的公众帐号微信号
XStream xstream = new XStream(new DomDriver());
xstream.alias("xml", WeChatReplyTextMessage.class);
xstream.aliasField("ToUserName", WeChatReplyTextMessage.class, "toUserName");
xstream.aliasField("FromUserName", WeChatReplyTextMessage.class, "fromUserName");
xstream.aliasField("CreateTime", WeChatReplyTextMessage.class, "createTime");
xstream.aliasField("MsgType", WeChatReplyTextMessage.class, "messageType");
xstream.aliasField("Content", WeChatReplyTextMessage.class, "content");
xstream.aliasField("FuncFlag", WeChatReplyTextMessage.class, "funcFlag");
String xml =xstream.toXML(we);
return xml;
}
}
原创文章,转载请注明: 转载自http://www.qiyadeng.com/
本文链接地址: 微信公众平台开发(二)–简单的聊天机器人
开始微信公众平台的开发,我们首先要了解微信平台可以帮助我们做哪些事情?
使用您的公众账号登陆http://mp.weixin.qq.com/,选择菜单--高级功能-开发模式--查看文档,即能看到微信公众平台目前所能开发的功能。
一、通讯机制
公众平台的主要内容是
- 接受用户发送给您公众账号的消息
- 给您的用户回复消息
需要特别说明的是,发送消息和回复消失是一个连贯的过程,只能在一个对话中完成。也就是说您的用户不找您说话,您是不能主动发送消息给你的客户(群发是另外一种情况,有次数限制。你也可以申请付费使用微信CRM平台)。所有的发送消息和接受消息,都需要微信平台进行中转。
二、消息类型 下面介绍用户能给您发送的消息类型,也就是目前接受到的消息类型。1.接受消息类型
1.1文本消息:
这也是我们平时碰到最多的,可以根据文本中提到的一些关键字,进行判断,判断用户的含义,并进行回复。
1.2图片消息:
目前通过图片理解用户想表达的意思,还是有较大难度,因此多数的公众账号,会选择忽略图片信息或选择由人工来处理。只能说一句:图片很美,但是我看不懂。
1.3地理位置消息:
用户把他的位置发给您,这对大多数公众账号来说,是一个重要的信息。可以提供一些基于位置信息的服务,比如酒店预订公众账号,可以给你推荐你周边的酒店。 另外一个补充是,可以在文本消息中分析出位置信息,并加以利用。比如用户输入“南京路步行街”,可以提供用户南京路步行街的相关商户。
1.4链接消息:
目前还没有看到开发模式中特别有效的使用方法。使用比较多的可能会是购物时或是咨询时,对所谈论的对象进行明确。
1.5事件推送消息:
当用户进入到和你对话的过程中,可以先和用户打招呼等。这个消息目前只支持4.5版本,且暂时还没有开发。后续可想想的空间很大,比如用户进入到会话之后,摇一摇会发生什么呢?
2.回复消息类型 2.1文本消息这是我们平时发送最多的一类消息,当只需要简单的文字即可回答用户的消息时,可用文本消息。文本消息中可以带有链接地址。
2.2图文消息
图文消息,这是我们在推送消息中经常看到的消息格式。每项内容可以点击查看更详细信息(当然你也可以把链接设置为空,使其不能跳转)
2.3音乐消息
在你的答复中给用户一个语音消息或是音乐,可以获得不少用户的亲睐。
了解了公众平台的通讯机制和消息类型,接下来,我们开始准备开发环境了。
原创文章,转载请注明: 转载自http://www.qiyadeng.com/
本文链接地址: 微信公众平台开发(一)