/* * * Mobile One Time Passwords (Mobile-OTP) for Java 2 Micro Edition, J2ME * written by Matthias Straub, Heilbronn, Germany, 2003 * (c) 2003, 2009 by Matthias Straub * * Version 1.061 * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * This program uses the MD5 library for java by Santeri Paavolainen * that was also released under the terms of the GPL * * CHANGELOG * Version 1.04: * - first public release * New in version 1.05: * - added the ability to set an offset from UTC for devices that are unaware of timezones * - is compatible with otpverify.sh v.1.04 * New in version 1.06: * - display optimization for newer phones with bigger displays * New in version 1.061: * - only numeric keys as well as * and # can be used. This adds compatibility to smartphones like the Nokia N97 * * usage: * - "#**#" to initialize token * - any four digit PIN to generate a one-time-password * */ package MobileOTP; import javax.microedition.midlet.*; import javax.microedition.lcdui.*; import java.util.Date; import javax.microedition.rms.*; import MobileOTP.MD5; import java.util.Calendar; public class MobileOTP extends MIDlet implements CommandListener { private Command exitCommand = new Command("Exit", Command.SCREEN, 1); private Command aboutCommand = new Command("About", Command.SCREEN, 2); private Command infoCommand = new Command("Info", Command.SCREEN, 2); private Command timezoneCommand = new Command ("Time Zone", Command.SCREEN, 2); private Command backCommand = new Command("Back", Command.BACK, 1); private Command cancelCommand = new Command("Cancel", Command.BACK, 1); private Command tzsaveCommand = new Command("Save", Command.BACK, 2); private static String PIN=""; private static String pindis=""; private static String secret=""; private static String initdate=""; private static int timez=12; private static int count=0; private static int initcount; private static RecordStore rs; Date now=new Date(); Calendar cnow; String epoch; public MobileOTP() { } public void startApp() { PIN=""; count=0; initcount=20; pindis=""; try { rs = RecordStore.openRecordStore( "motp", true); if ( rs.getNumRecords() !=3 ) { rs.closeRecordStore(); RecordStore.deleteRecordStore( "motp" ); rs = RecordStore.openRecordStore ("motp",true); rs.addRecord("0000000000000000".getBytes(),0,8); rs.addRecord("Not initialized!".getBytes(),0,16); rs.addRecord("12".getBytes(),0,2); //System.out.println ("New RS!"); } } catch(RecordStoreException rse) {System.out.println("RS Error! Open");} try { secret=new String(rs.getRecord(1)); initdate=new String(rs.getRecord(2)); timez=Integer.parseInt(new String(rs.getRecord(3))); } catch(RecordStoreException rse) {System.out.println("RS Error! Read");} Canvas lowinput = new Canvas () { String otp=""; String seed=""; MD5 hash; public void paint(Graphics g) { g.setColor(255,255,255); int w=getWidth(); int h=getHeight(); g.fillRect(0,0,w,h); g.setColor(0,0,0); g.drawString("Mobile-OTP",1,1,0); String printpin="PIN: "+pindis; g.drawString(printpin,w/7,h/3-2,0); if (PIN.equals("#**#")) { if (initcount != 0) { g.setColor(255,255,255); g.fillRect(0,0,w,h); g.setColor(0,0,0); g.drawString("Random Keys:",w/9,h/3-2,0); g.drawString(""+initcount,w/2-8,h/2,0); } else { secret=new MD5(seed).asHex().substring(0,16); cnow=Calendar.getInstance(); String day=""+cnow.get(Calendar.DAY_OF_MONTH); String month=""+(cnow.get(Calendar.MONTH)+1); String hour=""+cnow.get(Calendar.HOUR); String minute=""+cnow.get(Calendar.MINUTE); String year=""+(cnow.get(Calendar.YEAR)-2000); if (day.length()<2) day="0"+day; if (month.length()<2) month="0"+month; if (hour.length()<2) hour="0"+hour; if (minute.length()<2) minute="0"+minute; if (year.length()<2) year="0"+year; initdate=day+"/"+month+"/"+year+","+hour+":"+minute; try { rs.setRecord(1,secret.getBytes(),0,secret.length()); rs.setRecord(2,initdate.getBytes(),0,initdate.length()); } catch (RecordStoreException rse) {System.out.println("RS Error! Write");} g.setColor(255,255,255); g.fillRect(0,0,w,h); g.setColor(0,0,0); g.drawString("Init-Secret=",w/7,h/3-2,0); g.drawString(secret.substring(0,8),w/6,h/2,0); g.drawString(secret.substring(8,16),w/6,h/2+h/5,0); } } else if (count==4) { now=new Date(); epoch=""+(now.getTime()+((timez-12)*3600000)); epoch=epoch.substring(0,epoch.length()-4); otp=epoch+secret+PIN; hash=new MD5(otp); otp=hash.asHex().substring(0,6); g.drawString("Pass: " + otp,w/7,h/2,0); } } protected void keyPressed (int keyCode) { if (PIN.equals("#**#") & initcount != 0 ) { seed=seed + ((char)keyCode); initcount--; repaint(); } else { if (keyCode == KEY_NUM1 | keyCode == KEY_NUM2 | keyCode == KEY_NUM3 | keyCode == KEY_NUM4 | keyCode == KEY_NUM5 | keyCode == KEY_NUM6 | keyCode == KEY_NUM7 | keyCode == KEY_NUM8 | keyCode == KEY_NUM9 | keyCode == KEY_NUM0 | keyCode == KEY_STAR | keyCode == KEY_POUND) { seed=""; count++; initcount=20; if (count==5) { count=1; PIN=""; pindis=""; } PIN=PIN + ((char)keyCode); pindis=pindis + "*"; repaint(); } } } }; lowinput.addCommand(exitCommand); lowinput.addCommand(aboutCommand); lowinput.addCommand(infoCommand); lowinput.addCommand(timezoneCommand); lowinput.setCommandListener(this); Display.getDisplay(this).setCurrent(lowinput); } public void pauseApp() { } public void destroyApp(boolean unconditional) { } public void commandAction(Command c, Displayable s) { if ( c == exitCommand ) { try { rs.closeRecordStore(); } catch(RecordStoreException rse) {} destroyApp(false); notifyDestroyed(); } if ( c == aboutCommand ) { Canvas about = new Canvas() { public void paint(Graphics g) { int w=getWidth(); int h=getHeight(); g.setColor(255,255,255); g.fillRect(0,0,w,h); g.setColor(0,0,0); g.drawString("Mobile-OTP",3,3,0); g.drawString("v. 1.061",3,3+h/5,0); g.drawString("(c) 2003, 2009",3,3+2*h/5,0); g.drawString("M. Straub",3,3+3*h/5,0); } }; about.addCommand(backCommand); about.setCommandListener(this); Display.getDisplay(this).setCurrent(about); } if ( c == infoCommand ) { now=new Date(); epoch=""+(now.getTime()+((timez-12)*3600000)); epoch=epoch.substring(0,epoch.length()-4); Canvas infoc = new Canvas() { public void paint(Graphics g) { int w=getWidth(); int h=getHeight(); g.setColor(255,255,255); g.fillRect(0,0,w,h); g.setColor(0,0,0); g.drawString("Epoch-Time:",1,1,0); g.drawString(epoch,1,1+h/5,0); g.drawString("Initialization:",1,1+2*h/5,0); g.drawString(initdate,1,1+3*h/5,0); } }; infoc.addCommand(backCommand); infoc.setCommandListener(this); Display.getDisplay(this).setCurrent(infoc); } if ( c == backCommand | c == cancelCommand ) { startApp (); } if ( c == tzsaveCommand ) { try { rs.setRecord(3,(""+timez).getBytes(),0,(""+timez).length()); } catch (RecordStoreException rse) {System.out.println("RS Error! Write");} startApp (); } if ( c == timezoneCommand ) { Canvas timezc = new Canvas() { public void paint(Graphics g) { now=new Date(); epoch=""+(now.getTime()+((timez-12)*3600000)); epoch=epoch.substring(0,epoch.length()-4); int w=getWidth(); int h=getHeight(); g.setColor(255,255,255); g.fillRect(0,0,w,h); g.setColor(0,0,0); g.drawString("Set Time Zone=",1,1,0); if (timez > 12) g.drawString("UTC"+(12-timez),1,1+h/5,0); else g.drawString("UTC+"+(12-timez),1,1+h/5,0); g.drawString("Epoch-Time:",1,1+2*h/5,0); g.drawString(epoch,1,1+3*h/5,0); } protected void keyPressed (int keyCode) { if (keyCode == KEY_NUM1 | keyCode == KEY_NUM4 | keyCode == KEY_NUM7 | keyCode == KEY_STAR | keyCode == LEFT) { timez++; } if (keyCode == KEY_NUM3 | keyCode == KEY_NUM6 | keyCode == KEY_NUM9 | keyCode == KEY_POUND | keyCode == RIGHT) { timez--; } if (timez < 0 ) timez=0; if (timez > 24 ) timez=24; repaint(); } }; timezc.addCommand(cancelCommand); timezc.addCommand(tzsaveCommand); timezc.setCommandListener(this); Display.getDisplay(this).setCurrent(timezc); } } }