Commits

chunlinyao  committed 7964396

add hotp support

  • Participants
  • Parent commits 7479219

Comments (0)

Files changed (12)

 dist
 deploy
 build
+\.orig\..*$
+\.orig$
+\.chg\..*$
+\.rej$
+\.conflict\~$
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- You may freely edit this file. See commented blocks below for -->
-<!-- some examples of how to customize the build. -->
-<!-- (If you delete it and reopen the project it will be recreated.) -->
-<project name="Google_Auth_Token_Generator" default="jar" basedir=".">
-    <description>Builds, tests, and runs the project .</description>
-    <import file="nbproject/build-impl.xml"/>
-    <!--
-
-            There exist several targets which are by default empty and which can be
-            used for execution of your tasks. These targets are usually executed
-            before and after some main targets. They are:
-
-            pre-init:                 called before initialization of project properties
-            post-init:                called after initialization of project properties
-            pre-preprocess:           called before text preprocessing of sources
-            post-preprocess:          called after text preprocessing of sources
-            pre-compile:              called before source compilation
-            post-compile:             called after source compilation
-            pre-obfuscate:            called before obfuscation 
-            post-obfuscate:           called after obfuscation
-            pre-preverify:            called before preverification
-            post-preverify:           called after preverification
-            pre-jar:                  called before jar building
-            post-jar:                 called after jar building
-            pre-build:                called before final distribution building
-            post-build:               called after final distribution building
-            pre-clean:                called before cleaning build products
-            post-clean:               called after cleaning build products
-
-            Example of pluging a my-special-task after the compilation could look like
-
-            <target name="post-compile">
-            <my-special-task>
-            <fileset dir="${build.classes.dir}"/>
-            </my-special-task>
-            </target>
-
-            For list of available properties check the imported
-            nbproject/build-impl.xml file.
-
-            Other way how to customize the build is by overriding existing main targets.
-            The target of interest are:
-
-            preprocess:               preprocessing
-            extract-libs:             extraction of libraries and resources
-            compile:                  compilation
-            create-jad:               construction of jad and jar manifest source
-            obfuscate:                obfuscation
-            preverify:                preverification
-            jar:                      jar archive building
-            run:                      execution
-            debug:                    execution in debug mode
-            build:                    building of the final distribution
-            javadoc:                  javadoc generation
-
-            Example of overriding the target for project execution could look like
-
-            <target name="run" depends="init,jar">
-            <my-special-exec jadfile="${dist.dir}/${dist.jad}"/>
-            </target>
-
-            Be careful about correct dependencies when overriding original target. 
-            Again, for list of available properties which you can use check the target 
-            you are overriding in nbproject/build-impl.xml file.
-
-            A special target for-all-configs can be used to run some specific targets for
-            all project configurations in a sequence. File nbproject/build-impl.xml 
-            already contains some "for-all" targets:
-    
-            jar-all
-            javadoc-all
-            clean-all
-      
-            Example of definition of target iterating over all project configurations:
-    
-            <target name="jar-all">
-            <property name="target.to.call" value="jar"/>
-            <antcall target="for-all-configs"/>
-            </target>
-
-            -->
-</project>
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- You may freely edit this file. See commented blocks below for -->
+<!-- some examples of how to customize the build. -->
+<!-- (If you delete it and reopen the project it will be recreated.) -->
+<project name="Mobile_OTP_Generator" default="jar" basedir=".">
+    <description>Builds, tests, and runs the project .</description>
+    <import file="nbproject/build-impl.xml"/>
+    <!--
+
+            There exist several targets which are by default empty and which can be
+            used for execution of your tasks. These targets are usually executed
+            before and after some main targets. They are:
+
+            pre-init:                 called before initialization of project properties
+            post-init:                called after initialization of project properties
+            pre-preprocess:           called before text preprocessing of sources
+            post-preprocess:          called after text preprocessing of sources
+            pre-compile:              called before source compilation
+            post-compile:             called after source compilation
+            pre-obfuscate:            called before obfuscation 
+            post-obfuscate:           called after obfuscation
+            pre-preverify:            called before preverification
+            post-preverify:           called after preverification
+            pre-jar:                  called before jar building
+            post-jar:                 called after jar building
+            pre-build:                called before final distribution building
+            post-build:               called after final distribution building
+            pre-clean:                called before cleaning build products
+            post-clean:               called after cleaning build products
+
+            Example of pluging a my-special-task after the compilation could look like
+
+            <target name="post-compile">
+            <my-special-task>
+            <fileset dir="${build.classes.dir}"/>
+            </my-special-task>
+            </target>
+
+            For list of available properties check the imported
+            nbproject/build-impl.xml file.
+
+            Other way how to customize the build is by overriding existing main targets.
+            The target of interest are:
+
+            preprocess:               preprocessing
+            extract-libs:             extraction of libraries and resources
+            compile:                  compilation
+            create-jad:               construction of jad and jar manifest source
+            obfuscate:                obfuscation
+            preverify:                preverification
+            jar:                      jar archive building
+            run:                      execution
+            debug:                    execution in debug mode
+            build:                    building of the final distribution
+            javadoc:                  javadoc generation
+
+            Example of overriding the target for project execution could look like
+
+            <target name="run" depends="init,jar">
+            <my-special-exec jadfile="${dist.dir}/${dist.jad}"/>
+            </target>
+
+            Be careful about correct dependencies when overriding original target. 
+            Again, for list of available properties which you can use check the target 
+            you are overriding in nbproject/build-impl.xml file.
+
+            A special target for-all-configs can be used to run some specific targets for
+            all project configurations in a sequence. File nbproject/build-impl.xml 
+            already contains some "for-all" targets:
+    
+            jar-all
+            javadoc-all
+            clean-all
+      
+            Example of definition of target iterating over all project configurations:
+    
+            <target name="jar-all">
+            <property name="target.to.call" value="jar"/>
+            <antcall target="for-all-configs"/>
+            </target>
+
+            -->
+</project>

File nbproject/build-impl.xml

 <?xml version="1.0" encoding="UTF-8"?>
 <!-- *** GENERATED FROM project.xml - DO NOT EDIT *** -->
-<project name="Google_Auth_Token_Generator-impl" default="jar" basedir="..">
+<project name="Mobile_OTP_Generator-impl" default="jar" basedir="..">
     <!--load-properties-->
     <target name="pre-load-properties">
         <property file="nbproject/private/private.properties"/>

File nbproject/genfiles.properties

 # This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
 # Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
-build.xml.data.CRC32=31cbc7cd
-build.xml.script.CRC32=b1669726
+build.xml.data.CRC32=2de3b35c
+build.xml.script.CRC32=4f3d9949
 build.xml.stylesheet.CRC32=9c6a911d
-nbproject/build-impl.xml.data.CRC32=31cbc7cd
-nbproject/build-impl.xml.script.CRC32=4ae22da1
+nbproject/build-impl.xml.data.CRC32=2de3b35c
+nbproject/build-impl.xml.script.CRC32=dcb5f389
 nbproject/build-impl.xml.stylesheet.CRC32=7a0aeb65

File nbproject/private/private.properties

-#Fri, 13 May 2011 16:50:51 +0800
+#Sat, 14 May 2011 21:41:34 +0800
 app-version.autoincrement=true
-config.active=
-deployment.counter=65
-deployment.number=0.0.64
+config.active=release
+deployment.counter=72
+deployment.number=0.0.71
+file.reference.cldc_classes.zip=/home/yaochunlin/NetBeansProjects/mobile-otp/lib/cldc_classes.zip
+file.reference.cldc_crypto.zip=/home/yaochunlin/NetBeansProjects/mobile-otp/lib/cldc_crypto.zip
 javadoc.preview=true
 netbeans.user=/home/yaochunlin/.netbeans/7.0

File nbproject/private/private.xml

 <?xml version="1.0" encoding="UTF-8"?>
 <project-private xmlns="http://www.netbeans.org/ns/project-private/1">
     <editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/1"/>
-    <open-files xmlns="http://www.netbeans.org/ns/projectui-open-files/1">
-        <file>file:/home/yaochunlin/NetBeansProjects/trunk/src/TokenGenerator/TokenGen.java</file>
-        <file>file:/home/yaochunlin/NetBeansProjects/trunk/src/TokenGenerator/StockDB.java</file>
-        <file>file:/home/yaochunlin/NetBeansProjects/trunk/src/TokenGenerator/Main.java</file>
-    </open-files>
 </project-private>

File nbproject/project.properties

 abilities=MMAPI=1.1,JSR82=1.1,JSR280=1.0,JSR226=1.0,MIDP=2.1,JSR229=1.1,SATSA=1.0,CLDC=1.1,JSR177=1.0,JSR179=1.0,J2MEWS=1.0,WMA=2.0,JSR172=1.0,JSR256=1.0,OBEX=1.0,ColorScreen=,JSR238=1.0,JSR239=1.0,JSR211=1.0,JSR234=1.0,ScreenWidth=240,JSR75=1.0,JSR184=1.1,ScreenHeight=320,ScreenColorDepth=16,JSR180=1.1.0
-all.configurations=\ 
+all.configurations=\ ,release
 application.args=
 application.description=
 application.description.detail=
 build.classes.excludes=**/*.java,**/*.form,**/*.class,**/.nbintdb,**/*.mvd,**/*.wsclient,**/*.vmd
 build.dir=build/${config.active}
 build.root.dir=build
+configs.release.debug.level=debug
+configs.release.javac.debug=false
+configs.release.javac.deprecation=false
+configs.release.javac.encoding=UTF-8
+configs.release.javac.optimize=true
 debug.level=debug
 debugger.timeout=
 deployment.copy.target=deploy
 deployment.method=Copy
 deployment.override.jarurl=false
 dist.dir=dist/${config.active}
-dist.jad=gauth.jad
-dist.jar=gauth.jar
+dist.jad=mobile-otp.jad
+dist.jar=mobile-otp.jar
 dist.javadoc.dir=${dist.dir}/doc
 dist.root.dir=dist
 extra.classpath=
 manifest.is.liblet=false
 manifest.jad=
 manifest.manifest=
-manifest.midlets=MIDlet-1: Sware Token Generator,/vass.png,TokenGenerator.Main\n
-manifest.others=MIDlet-Vendor: Vendor\nMIDlet-Name: Sware Token Generator\nMIDlet-Version: 1.0\n
+manifest.midlets=MIDlet-1: Mobile-OTP,/vass.png,TokenGenerator.Main\n
+manifest.others=MIDlet-Vendor: chunlinyao\nMIDlet-Name: Mobile-OTP\nMIDlet-Version: 1.0\n
 manifest.pushregistry=
 name=Google Auth Mobile Token Generator
 no.dependencies=false

File nbproject/project.xml

-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://www.netbeans.org/ns/project/1">
-    <type>org.netbeans.modules.kjava.j2meproject</type>
-    <configuration>
-        <data xmlns="http://www.netbeans.org/ns/j2me-project">
-            <name>Google Auth Token Generator</name>
-            <minimum-ant-version>1.6</minimum-ant-version>
-        </data>
-        <references xmlns="http://www.netbeans.org/ns/ant-project-references/2"/>
-    </configuration>
-</project>
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://www.netbeans.org/ns/project/1">
+    <type>org.netbeans.modules.kjava.j2meproject</type>
+    <configuration>
+        <data xmlns="http://www.netbeans.org/ns/j2me-project">
+            <name>Mobile OTP Generator</name>
+            <minimum-ant-version>1.6</minimum-ant-version>
+        </data>
+        <references xmlns="http://www.netbeans.org/ns/ant-project-references/2"/>
+    </configuration>
+</project>

File src/TokenGenerator/AccountDB.java

         return getAccount(accountName) != null;
     }
 
+    static void incCounter(String name) {
+        Account acc = getAccount(name);
+        if (acc != null) {
+            acc.setCounter(acc.getCounter() + 1L);
+            update(acc.getAccountName(), acc.getAccountName(), acc.getSecret(), acc.getOtpType(), acc.getInterval(), acc.getCounter());
+        }
+    }
+
     private AccountDB() {
     }
 

File src/TokenGenerator/Main.java

 package TokenGenerator;
 
 import TokenGenerator.Account.OtpType;
+import java.util.Date;
 import javax.microedition.midlet.*;
 import javax.microedition.lcdui.*;
 import javax.microedition.rms.RecordStoreException;
     private Command deleteCommand;
     private Command cancelCommand1;
     private Command editCommand;
+    private Command reCalcTokenCommand;
     private Command okCommand2;
-    private Command reCalcTokenCommand;
     private Form passForm;
     private StringItem tokenStringItem;
+    private StringItem extraInfo;
     private Form addForm;
     private TextField secretTextfield;
     private TextField accountNameTextField;
     private StringItem deleteAccountName;
     private StringItem stringItem;
     //</editor-fold>//GEN-END:|fields|0|
-    private String oldName;
+    private String selectedAccount;
 
     /**
      * The HelloMIDlet constructor.
     public Main() {
     }
 
+    private void caculateToken() {
+        // write pre-action user code here
+        OtpType type = AccountDB.getOtpType(selectedAccount);
+        if (type == OtpType.HOTP) {
+            String secret = AccountDB.getSecret(selectedAccount);
+            long counter = AccountDB.getCounter(selectedAccount) + 1L;
+            getTokenStringItem().setText(TokenGen.computePin(secret, new Long(counter), null));
+            getExtraInfo().setText(String.valueOf(counter));
+            AccountDB.incCounter(selectedAccount);
+        } else if (type == OtpType.TOTP) {
+            String secret = AccountDB.getSecret(selectedAccount);
+            Integer interval = new Integer(AccountDB.getInterval(selectedAccount));
+            getTokenStringItem().setText(TokenGen.computePin(secret, null, interval));
+            long ts = (new Date()).getTime() / 1000L;
+            getExtraInfo().setText(String.valueOf(interval.intValue() - (ts % interval.intValue())));
+        }
+
+        // write post-action user code here
+    }
+
     private void updateAccount() {
         // write pre-action user code here
         try {
             if (accountName.length() == 0) {
                 throw new RuntimeException("帐号名不能为空");
             }
-            if (oldName == null || oldName.equals(accountName) == false) {
+            if (selectedAccount == null || selectedAccount.equals(accountName) == false) {
                 if (AccountDB.nameExists(accountName)) {
                     throw new RuntimeException("帐号名不能重复");
                 }
             int interval = Integer.valueOf(getIntervalField().getString()).intValue();
             long counter = Long.parseLong(getCounterField().getString());
 
-            AccountDB.update(oldName, accountName, tokenKey, otpType, interval, counter);
+            AccountDB.update(selectedAccount, accountName, tokenKey, otpType, interval, counter);
             refreshAccountList();
             switchDisplayable(null, getAccountList());
         } catch (Exception e) {
     }
 
     private void editAccount() {
-        oldName = getAccountList().getString(getAccountList().getSelectedIndex());
-        getAccountNameTextField().setString(oldName);
-        getSecretTextfield().setString(AccountDB.getSecret(oldName));
-        getOtyType().setSelectedIndex(AccountDB.getOtpType(oldName).value.intValue(), true);
-        getIntervalField().setString(String.valueOf(AccountDB.getInterval(oldName)));
-        getCounterField().setString(String.valueOf(AccountDB.getCounter(oldName)));
+        selectedAccount = getAccountList().getString(getAccountList().getSelectedIndex());
+        getAccountNameTextField().setString(selectedAccount);
+        getSecretTextfield().setString(AccountDB.getSecret(selectedAccount));
+        getOtyType().setSelectedIndex(AccountDB.getOtpType(selectedAccount).value.intValue(), true);
+        getIntervalField().setString(String.valueOf(AccountDB.getInterval(selectedAccount)));
+        getCounterField().setString(String.valueOf(AccountDB.getCounter(selectedAccount)));
         switchDisplayable(null, getAddForm());
     }
 
     private void deleteAccount() {
-        oldName = getAccountList().getString(getAccountList().getSelectedIndex());
-        getDeleteAccountName().setText(oldName);
+        selectedAccount = getAccountList().getString(getAccountList().getSelectedIndex());
+        getDeleteAccountName().setText(selectedAccount);
         switchDisplayable(null, getDeleteForm());
     }
     //<editor-fold defaultstate="collapsed" desc=" Generated Methods ">//GEN-BEGIN:|methods|0|
                 // write post-action user code here
             } else if (command == addCommand) {//GEN-LINE:|7-commandAction|3|86-preAction
                 // write pre-action user code here
-                oldName = null;
-                getAccountNameTextField().setString(oldName);
-                getSecretTextfield().setString(oldName);
+                selectedAccount = null;
+                getAccountNameTextField().setString(selectedAccount);
+                getSecretTextfield().setString(selectedAccount);
                 getOtyType().setSelectedIndex(0, true);
                 getIntervalField().setString("30");
                 getCounterField().setString("0");
                 // write post-action user code here
             } else if (command == okCommand) {//GEN-LINE:|7-commandAction|11|83-preAction
                 // write pre-action user code here
-                oldName = getAccountList().getString(getAccountList().getSelectedIndex());
-                getTokenStringItem().setText(TokenGen.computePin(AccountDB.getSecret(oldName), null));
+                selectedAccount = getAccountList().getString(getAccountList().getSelectedIndex());
+                caculateToken();
                 switchDisplayable(null, getPassForm());//GEN-LINE:|7-commandAction|12|83-postAction
                 // write post-action user code here
             }//GEN-BEGIN:|7-commandAction|13|69-preAction
                 // write post-action user code here
             } else if (command == okCommand2) {//GEN-LINE:|7-commandAction|21|113-preAction
                 // write pre-action user code here
-                AccountDB.delete(oldName);
+                AccountDB.delete(selectedAccount);
                 refreshAccountList();
                 switchDisplayable(null, getAccountList());//GEN-LINE:|7-commandAction|22|113-postAction
                 // write post-action user code here
                 switchDisplayable(null, getAccountList());//GEN-LINE:|7-commandAction|24|19-postAction
                 // write post-action user code here
             } else if (command == reCalcTokenCommand) {//GEN-LINE:|7-commandAction|25|118-preAction
-                // write pre-action user code here
-                getTokenStringItem().setText(TokenGen.computePin(AccountDB.getSecret(oldName), null));
+                caculateToken();
 //GEN-LINE:|7-commandAction|26|118-postAction
                 // write post-action user code here
             }//GEN-BEGIN:|7-commandAction|27|7-postCommandAction
         if (passForm == null) {//GEN-END:|14-getter|0|14-preInit
             // write pre-init user code here
 
-            passForm = new Form("\u5BC6\u7801\u751F\u6210\u5668", new Item[] { getTokenStringItem() });//GEN-BEGIN:|14-getter|1|14-postInit
+            passForm = new Form("\u5BC6\u7801\u751F\u6210\u5668", new Item[] { getTokenStringItem(), getExtraInfo() });//GEN-BEGIN:|14-getter|1|14-postInit
             passForm.addCommand(getExitCommand());
             passForm.addCommand(getReCalcTokenCommand());
             passForm.setCommandListener(this);//GEN-END:|14-getter|1|14-postInit
     }
     //</editor-fold>//GEN-END:|117-getter|2|
 
+    //<editor-fold defaultstate="collapsed" desc=" Generated Getter: extraInfo ">//GEN-BEGIN:|120-getter|0|120-preInit
+    /**
+     * Returns an initiliazed instance of extraInfo component.
+     * @return the initialized component instance
+     */
+    public StringItem getExtraInfo() {
+        if (extraInfo == null) {//GEN-END:|120-getter|0|120-preInit
+            // write pre-init user code here
+            extraInfo = new StringItem("\u5269\u4F59\u65F6\u95F4/\u672C\u6B21\u5E8F\u53F7:", null, Item.PLAIN);//GEN-LINE:|120-getter|1|120-postInit
+            // write post-init user code here
+        }//GEN-BEGIN:|120-getter|2|
+        return extraInfo;
+    }
+    //</editor-fold>//GEN-END:|120-getter|2|
+
     /**
      * Returns a display instance.
      * @return the display instance.

File src/TokenGenerator/Main.vmd

                     <Property name="label" typeID="Pjava.lang.String" value="V编辑"/>
                     <Property name="type" typeID="Pint" value="V8"/>
                 </Component>
-                <Component componentID="112" typeID="Cjavax.microedition.lcdui.Command">
-                    <Property name="instanceName" typeID="Pjava.lang.String" value="VokCommand2"/>
+                <Component componentID="117" typeID="Cjavax.microedition.lcdui.Command">
+                    <Property name="instanceName" typeID="Pjava.lang.String" value="VreCalcTokenCommand"/>
                     <Property name="codeGenerated" typeID="Pboolean" value="Vtrue"/>
                     <Property name="label" typeID="Pjava.lang.String" value="V确定"/>
                     <Property name="type" typeID="Pint" value="V4"/>
                 </Component>
-                <Component componentID="117" typeID="Cjavax.microedition.lcdui.Command">
-                    <Property name="instanceName" typeID="Pjava.lang.String" value="VreCalcTokenCommand"/>
+                <Component componentID="112" typeID="Cjavax.microedition.lcdui.Command">
+                    <Property name="instanceName" typeID="Pjava.lang.String" value="VokCommand2"/>
                     <Property name="codeGenerated" typeID="Pboolean" value="Vtrue"/>
                     <Property name="label" typeID="Pjava.lang.String" value="V确定"/>
                     <Property name="type" typeID="Pint" value="V4"/>
                     <Property name="commands" typeID="1C#CommandEventSource" value="A2:3_R194_R118"/>
                     <Property name="title" typeID="Pjava.lang.String" value="V密码生成器"/>
                     <Property name="commandListener" typeID="C#CommandListener" value="R7"/>
-                    <Property name="items" typeID="1Cjavax.microedition.lcdui.Item" value="A1:3_R89"/>
+                    <Property name="items" typeID="1Cjavax.microedition.lcdui.Item" value="A2:3_R894_R120"/>
                     <Component componentID="19" typeID="C#CommandEventSource">
                         <Property name="eventHandler" typeID="C#EventHandler" value="R78"/>
                         <Property name="displayable" typeID="Cjavax.microedition.lcdui.Displayable" value="R14"/>
                         <Property name="displayable" typeID="Cjavax.microedition.lcdui.Displayable" value="R14"/>
                         <Property name="command" typeID="Cjavax.microedition.lcdui.Command" value="R117"/>
                     </Component>
+                    <Component componentID="120" typeID="Cjavax.microedition.lcdui.StringItem">
+                        <Property name="instanceName" typeID="Pjava.lang.String" value="VextraInfo"/>
+                        <Property name="codeGenerated" typeID="Pboolean" value="Vtrue"/>
+                        <Property name="label" typeID="Pjava.lang.String" value="V剩余时间/本次序号:"/>
+                        <Property name="itemCommandListener" typeID="C#ItemCommandListener" value="R17"/>
+                        <Property name="appearanceMode" typeID="Pint" value="V0"/>
+                    </Component>
                 </Component>
                 <Component componentID="61" typeID="Cjavax.microedition.lcdui.Form">
                     <Property name="instanceName" typeID="Pjava.lang.String" value="VaddForm"/>
         </Component>
     </Document>
     <FlowScene version="1">
+        <Node componentID="14" descriptorID="componentNode14" x="268" y="31"/>
         <Node componentID="2" descriptorID="componentNode2" x="34" y="181"/>
-        <Node componentID="14" descriptorID="componentNode14" x="268" y="31"/>
         <Node componentID="74" descriptorID="componentNode74" x="259" y="411"/>
         <Node componentID="61" descriptorID="componentNode61" x="594" y="251"/>
         <Node componentID="90" descriptorID="componentNode90" x="664" y="117"/>

File src/TokenGenerator/TokenGen.java

         this(mac, PASS_CODE_LENGTH, INTERVAL);
     }
 
+    public TokenGen(Mac mac, int interval) {
+        this(mac, PASS_CODE_LENGTH, interval);
+    }
+
     public TokenGen(Mac mac, int codeLength, int interval) {
         this.hmac = mac;
         this.codeLength = codeLength;
         }
         return result;
     }
-    
-     /**
-   * Computes the one-time PIN given the secret key.
-   * 
-   * @param secret
-   *          the secret key
-   * @return the PIN
-   * @throws GeneralSecurityException
-   * @throws DecodingException
-   *           If the key string is improperly encoded.
-   */
-  public static String computePin(String secret, Long counter) {
-    try {
-      final byte[] keyBytes = Base32.decode(secret);
-      Mac mac = new HMac(new SHA1Digest());
-      mac.init(new KeyParameter(keyBytes));
-      TokenGen pcg = new TokenGen(mac);
-      if (counter == null) { // time-based totp
-        return pcg.genTimeoutToken();
-      } else { // counter-based hotp
-        return pcg.genToken(counter.longValue());
-      }
-    } catch (RuntimeException e) {
-      return "General security exception";
-    } 
-  }
+
+    /**
+     * Computes the one-time PIN given the secret key.
+     * 
+     * @param secret
+     *          the secret key
+     * @return the PIN
+     * @throws GeneralSecurityException
+     * @throws DecodingException
+     *           If the key string is improperly encoded.
+     */
+    public static String computePin(String secret, Long counter, Integer interval) {
+        try {
+            final byte[] keyBytes = Base32.decode(secret);
+            Mac mac = new HMac(new SHA1Digest());
+            mac.init(new KeyParameter(keyBytes));
+            TokenGen pcg;
+            if (interval == null) {
+                pcg = new TokenGen(mac);
+            } else {
+                pcg = new TokenGen(mac, interval.intValue());
+            }
+            if (counter == null) { // time-based totp
+                return pcg.genTimeoutToken();
+            } else { // counter-based hotp
+                return pcg.genToken(counter.longValue());
+            }
+        } catch (RuntimeException e) {
+            return "General security exception";
+        }
+    }
 }