Java tutorial
/* * Copyright (C) 2010 Stanford University * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package edu.stanford.junction.extra; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeMap; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.jivesoftware.smack.util.Base64; import org.json.JSONArray; import org.json.JSONObject; import edu.stanford.junction.JunctionMaker; import edu.stanford.junction.SwitchboardConfig; import edu.stanford.junction.api.activity.ActivityScript; import edu.stanford.junction.api.activity.JunctionActor; import edu.stanford.junction.api.activity.JunctionExtra; import edu.stanford.junction.api.messaging.MessageHeader; import edu.stanford.junction.provider.xmpp.XMPPSwitchboardConfig; public class Encryption extends JunctionExtra { /** * If an invitation is accepted, auto-detect whether * to use encryption via a parameter "aes=[key]" in the invitation. */ public final static String FIELD_ENC = "e"; public final static String FIELD_IV = "iv"; public final static String URL_KEY_PARAM = "skey"; private Cipher mCipher = null; private SecretKeySpec mKeySpec = null; protected byte[] mKey = null; public Encryption() { } private Encryption(byte[] key) { mKey = key; init(); } private void init() { try { mCipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); mKeySpec = new SecretKeySpec(mKey, "AES"); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { try { SwitchboardConfig config = new XMPPSwitchboardConfig("prpl.stanford.edu"); JunctionMaker jm = JunctionMaker.getInstance(config); JunctionActor rec = new JunctionActor("Recevier") { @Override public void onMessageReceived(MessageHeader header, JSONObject message) { System.out.println("rec got " + message.toString()); } @Override public List<JunctionExtra> getInitialExtras() { List<JunctionExtra> e = super.getInitialExtras(); e.add(new Encryption()); return e; } @Override public void onActivityCreate() { System.out.println("Receiver created"); } }; JunctionActor send = new JunctionActor("Sender") { @Override public void onMessageReceived(MessageHeader header, JSONObject message) { System.out.println("send got " + message.toString()); } @Override public void onActivityJoin() { try { JSONObject message = new JSONObject("{\"msg\":\"hello!! encrypted!\"}"); sendMessageToSession(message); message = new JSONObject("{\"msg\":\"hello!! encrypted!\"}"); sendMessageToSession(message); message = new JSONObject("{\"msg\":\"hello!! encrypted!\"}"); sendMessageToSession(message); message = new JSONObject("{\"msg\":\"Keep the cryptotimes rollin!\",\"more\":\"mannnn\"}"); sendMessageToSession(message); message = new JSONObject("{\"msg\":\"Keep the cryptotimes rollin!\",\"more\":\"mannnn\"}"); sendMessageToSession(message); } catch (Exception e) { } } @Override public List<JunctionExtra> getInitialExtras() { List<JunctionExtra> e = super.getInitialExtras(); e.add(new Encryption()); return e; } @Override public void onActivityCreate() { System.out.println("Sender created"); } }; ActivityScript myScript = new ActivityScript(); myScript.setActivityID("edu.stanford.junction.cryptdemo"); myScript.setFriendlyName("CryptDemo"); myScript.setSessionID("cryptosess"); URI mySession = new URI("junction://prpl.stanford.edu/cryptosess?skey=XPVisDpGE82GYc8nCcgj%2FQ%3D%3D"); jm.newJunction(mySession, rec); //jm.newJunction(myScript, rec); URI invite = rec.getJunction().getInvitationURI(); System.out.println("created invitation " + invite); jm.newJunction(invite, send); synchronized (send) { send.wait(); } } catch (Exception e) { e.printStackTrace(); } } @Override public boolean beforeActivityJoin() { // TODO: probably better to have mCreated or something. if (mKey != null) return true; try { URI invite = getActor().getJunction().getAcceptedInvitation(); System.out.println("JOINING " + invite); if (invite != null) { String params = invite.getQuery(); QueryString qs = new QueryString(params); String b64key = qs.getParameter(URL_KEY_PARAM); if (b64key == null) return true; mKey = Base64Coder.decode(b64key); init(); } } catch (Exception e) { e.printStackTrace(); } return true; } @Override public boolean beforeActivityCreate() { try { KeyGenerator kgen = KeyGenerator.getInstance("AES"); kgen.init(128); SecretKey skey = kgen.generateKey(); mKey = skey.getEncoded(); init(); } catch (Exception e) { e.printStackTrace(); } return true; } /** * Encrypts a message before sending it over the wire. */ @Override public synchronized boolean beforeSendMessage(JSONObject msg) { if (mKeySpec == null) return true; try { String msgStr = msg.toString(); mCipher.init(Cipher.ENCRYPT_MODE, mKeySpec); byte[] enc = null; enc = mCipher.doFinal(msgStr.getBytes()); String encStr = new String(Base64Coder.encode(enc)); String ivStr = new String(Base64Coder.encode(mCipher.getIV())); // clear object JSONArray keys = msg.names(); for (int i = 0; i < keys.length(); i++) { msg.remove(keys.getString(i)); } msg.put(FIELD_ENC, encStr); msg.put(FIELD_IV, ivStr); } catch (Exception e) { e.printStackTrace(); } return true; } /** * Decrypts an inbound message before handing it to the activity developer. */ @Override public synchronized boolean beforeOnMessageReceived(MessageHeader h, JSONObject msg) { if (mKeySpec == null) return true; try { if (!msg.has(FIELD_ENC)) { return true; } String b64 = msg.getString(FIELD_ENC); byte[] dec = Base64Coder.decode(b64); if (msg.has(FIELD_IV)) { byte[] iv = Base64.decode(msg.getString(FIELD_IV)); mCipher.init(Cipher.DECRYPT_MODE, mKeySpec, new IvParameterSpec(iv)); msg.remove(FIELD_IV); } else { mCipher.init(Cipher.DECRYPT_MODE, mKeySpec); } byte[] res = mCipher.doFinal(dec); JSONObject obj = new JSONObject(new String(res)); msg.remove("e"); Iterator<String> keys = obj.keys(); while (keys.hasNext()) { String key = keys.next(); msg.put(key, obj.get(key)); } } catch (Exception e) { e.printStackTrace(); } return true; } /** * Low priority so we don't interfere with other extras */ @Override public Integer getPriority() { return 3; } @Override public void updateInvitationParameters(Map<String, String> params) { if (mKey != null) { String b64 = new String(Base64Coder.encode(mKey)); params.put(URL_KEY_PARAM, b64); } } } class QueryString { private Map<String, List<String>> parameters; public QueryString(String qs) { parameters = new TreeMap<String, List<String>>(); // Parse query string String pairs[] = qs.split("&"); for (String pair : pairs) { String name; String value; int pos = pair.indexOf('='); // for "n=", the value is "", for "n", the value is null if (pos == -1) { name = pair; value = null; } else { try { name = URLDecoder.decode(pair.substring(0, pos), "UTF-8"); value = URLDecoder.decode(pair.substring(pos + 1, pair.length()), "UTF-8"); } catch (UnsupportedEncodingException e) { // Not really possible, throw unchecked throw new IllegalStateException("No UTF-8"); } } List<String> list = parameters.get(name); if (list == null) { list = new ArrayList<String>(); parameters.put(name, list); } list.add(value); } } public String getParameter(String name) { List<String> values = parameters.get(name); if (values == null) return null; if (values.size() == 0) return ""; return values.get(0); } public String[] getParameterValues(String name) { List<String> values = parameters.get(name); if (values == null) return null; return (String[]) values.toArray(new String[values.size()]); } public Enumeration<String> getParameterNames() { return Collections.enumeration(parameters.keySet()); } public Map<String, String[]> getParameterMap() { Map<String, String[]> map = new TreeMap<String, String[]>(); for (Map.Entry<String, List<String>> entry : parameters.entrySet()) { List<String> list = entry.getValue(); String[] values; if (list == null) values = null; else values = (String[]) list.toArray(new String[list.size()]); map.put(entry.getKey(), values); } return map; } } //Copyright 2003-2009 Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland //www.source-code.biz, www.inventec.ch/chdh // //This module is multi-licensed and may be used under the terms //of any of the following licenses: // //EPL, Eclipse Public License, http://www.eclipse.org/legal //LGPL, GNU Lesser General Public License, http://www.gnu.org/licenses/lgpl.html //AL, Apache License, http://www.apache.org/licenses //BSD, BSD License, http://www.opensource.org/licenses/bsd-license.php // //Please contact the author if you need another license. //This module is provided "as is", without warranties of any kind. /** * A Base64 Encoder/Decoder. * * <p> * This class is used to encode and decode data in Base64 format as described in RFC 1521. * * <p> * Home page: <a href="http://www.source-code.biz">www.source-code.biz</a><br> * Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland<br> * Multi-licensed: EPL/LGPL/AL/BSD. * * <p> * Version history:<br> * 2003-07-22 Christian d'Heureuse (chdh): Module created.<br> * 2005-08-11 chdh: Lincense changed from GPL to LGPL.<br> * 2006-11-21 chdh:<br> * Method encode(String) renamed to encodeString(String).<br> * Method decode(String) renamed to decodeString(String).<br> * New method encode(byte[],int) added.<br> * New method decode(String) added.<br> * 2009-07-16: Additional licenses (EPL/AL) added.<br> * 2009-09-16: Additional license (BSD) added.<br> */ class Base64Coder { //Mapping table from 6-bit nibbles to Base64 characters. private static char[] map1 = new char[64]; static { int i = 0; for (char c = 'A'; c <= 'Z'; c++) map1[i++] = c; for (char c = 'a'; c <= 'z'; c++) map1[i++] = c; for (char c = '0'; c <= '9'; c++) map1[i++] = c; map1[i++] = '+'; map1[i++] = '/'; } //Mapping table from Base64 characters to 6-bit nibbles. private static byte[] map2 = new byte[128]; static { for (int i = 0; i < map2.length; i++) map2[i] = -1; for (int i = 0; i < 64; i++) map2[map1[i]] = (byte) i; } /** * Encodes a string into Base64 format. * No blanks or line breaks are inserted. * @param s a String to be encoded. * @return A String with the Base64 encoded data. */ public static String encodeString(String s) { return new String(encode(s.getBytes())); } /** * Encodes a byte array into Base64 format. * No blanks or line breaks are inserted. * @param in an array containing the data bytes to be encoded. * @return A character array with the Base64 encoded data. */ public static char[] encode(byte[] in) { return encode(in, in.length); } /** * Encodes a byte array into Base64 format. * No blanks or line breaks are inserted. * @param in an array containing the data bytes to be encoded. * @param iLen number of bytes to process in <code>in</code>. * @return A character array with the Base64 encoded data. */ public static char[] encode(byte[] in, int iLen) { int oDataLen = (iLen * 4 + 2) / 3; // output length without padding int oLen = ((iLen + 2) / 3) * 4; // output length including padding char[] out = new char[oLen]; int ip = 0; int op = 0; while (ip < iLen) { int i0 = in[ip++] & 0xff; int i1 = ip < iLen ? in[ip++] & 0xff : 0; int i2 = ip < iLen ? in[ip++] & 0xff : 0; int o0 = i0 >>> 2; int o1 = ((i0 & 3) << 4) | (i1 >>> 4); int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6); int o3 = i2 & 0x3F; out[op++] = map1[o0]; out[op++] = map1[o1]; out[op] = op < oDataLen ? map1[o2] : '='; op++; out[op] = op < oDataLen ? map1[o3] : '='; op++; } return out; } /** * Decodes a string from Base64 format. * @param s a Base64 String to be decoded. * @return A String containing the decoded data. * @throws IllegalArgumentException if the input is not valid Base64 encoded data. */ public static String decodeString(String s) { return new String(decode(s)); } /** * Decodes a byte array from Base64 format. * @param s a Base64 String to be decoded. * @return An array containing the decoded data bytes. * @throws IllegalArgumentException if the input is not valid Base64 encoded data. */ public static byte[] decode(String s) { return decode(s.toCharArray()); } /** * Decodes a byte array from Base64 format. * No blanks or line breaks are allowed within the Base64 encoded data. * @param in a character array containing the Base64 encoded data. * @return An array containing the decoded data bytes. * @throws IllegalArgumentException if the input is not valid Base64 encoded data. */ public static byte[] decode(char[] in) { int iLen = in.length; if (iLen % 4 != 0) throw new IllegalArgumentException("Length of Base64 encoded input string is not a multiple of 4."); while (iLen > 0 && in[iLen - 1] == '=') iLen--; int oLen = (iLen * 3) / 4; byte[] out = new byte[oLen]; int ip = 0; int op = 0; while (ip < iLen) { int i0 = in[ip++]; int i1 = in[ip++]; int i2 = ip < iLen ? in[ip++] : 'A'; int i3 = ip < iLen ? in[ip++] : 'A'; if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127) throw new IllegalArgumentException("Illegal character in Base64 encoded data."); int b0 = map2[i0]; int b1 = map2[i1]; int b2 = map2[i2]; int b3 = map2[i3]; if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0) throw new IllegalArgumentException("Illegal character in Base64 encoded data."); int o0 = (b0 << 2) | (b1 >>> 4); int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2); int o2 = ((b2 & 3) << 6) | b3; out[op++] = (byte) o0; if (op < oLen) out[op++] = (byte) o1; if (op < oLen) out[op++] = (byte) o2; } return out; } //Dummy constructor. private Base64Coder() { } } // end class Base64Coder