From c17bc0dbfb7205e690283612c67943592f34191c Mon Sep 17 00:00:00 2001 From: SuperVirus Date: Fri, 18 Aug 2017 11:39:36 +0200 Subject: [PATCH] Variable digits lenths (#30) Adds support for variable digit lengths --- .../andotp/Activities/MainActivity.java | 5 ++- .../flocke/andotp/Database/Entry.java | 32 +++++++++++++++++-- .../andotp/Utilities/TokenCalculator.java | 5 +-- .../main/res/layout/dialog_manual_entry.xml | 21 ++++++++++++ app/src/main/res/values/strings_main.xml | 1 + 5 files changed, 58 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Activities/MainActivity.java b/app/src/main/java/org/shadowice/flocke/andotp/Activities/MainActivity.java index 8a52a01e..6d3be591 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Activities/MainActivity.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Activities/MainActivity.java @@ -93,11 +93,13 @@ public class MainActivity extends BaseActivity final EditText labelInput = (EditText) inputView.findViewById(R.id.manual_label); final EditText secretInput = (EditText) inputView.findViewById(R.id.manual_secret); final EditText periodInput = (EditText) inputView.findViewById(R.id.manual_period); + final EditText digitsInput = (EditText) inputView.findViewById(R.id.manual_digits); final Spinner algorithmInput = (Spinner) inputView.findViewById(R.id.manual_algorithm); typeInput.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_expandable_list_item_1, Entry.OTPType.values())); algorithmInput.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_expandable_list_item_1, TokenCalculator.HashAlgorithm.values())); periodInput.setText(Integer.toString(TokenCalculator.TOTP_DEFAULT_PERIOD)); + digitsInput.setText(Integer.toString(TokenCalculator.TOTP_DEFAULT_DIGITS)); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.dialog_title_manual_entry) @@ -112,8 +114,9 @@ public class MainActivity extends BaseActivity String label = labelInput.getText().toString(); String secret = secretInput.getText().toString(); int period = Integer.parseInt(periodInput.getText().toString()); + int digits = Integer.parseInt(digitsInput.getText().toString()); - Entry e = new Entry(type, secret, period, label, algorithm); + Entry e = new Entry(type, secret, period, digits, label, algorithm); e.updateOTP(); adapter.addEntry(e); adapter.saveEntries(); diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Database/Entry.java b/app/src/main/java/org/shadowice/flocke/andotp/Database/Entry.java index 88ddcad0..44e68243 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Database/Entry.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Database/Entry.java @@ -42,11 +42,13 @@ public class Entry { private static final String JSON_SECRET = "secret"; private static final String JSON_LABEL = "label"; private static final String JSON_PERIOD = "period"; + private static final String JSON_DIGITS = "digits"; private static final String JSON_TYPE = "type"; private static final String JSON_ALGORITHM = "algorithm"; private OTPType type = OTPType.TOTP; private int period = TokenCalculator.TOTP_DEFAULT_PERIOD; + private int digits = TokenCalculator.TOTP_DEFAULT_DIGITS; private TokenCalculator.HashAlgorithm algorithm = TokenCalculator.DEFAULT_ALGORITHM; private byte[] secret; private String label; @@ -55,10 +57,11 @@ public class Entry { public Entry(){} - public Entry(OTPType type, String secret, int period, String label, TokenCalculator.HashAlgorithm algorithm) { + public Entry(OTPType type, String secret, int period, int digits, String label, TokenCalculator.HashAlgorithm algorithm) { this.type = type; this.secret = new Base32().decode(secret.toUpperCase()); this.period = period; + this.digits = digits; this.label = label; this.algorithm = algorithm; } @@ -83,6 +86,7 @@ public class Entry { String issuer = uri.getQueryParameter("issuer"); String period = uri.getQueryParameter("period"); + String digits = uri.getQueryParameter("digits"); String algorithm = uri.getQueryParameter("algorithm"); if(issuer != null){ @@ -98,6 +102,12 @@ public class Entry { this.period = TokenCalculator.TOTP_DEFAULT_PERIOD; } + if (digits != null) { + this.digits = Integer.parseInt(digits); + } else { + this.digits = TokenCalculator.TOTP_DEFAULT_DIGITS; + } + if (algorithm != null) { this.algorithm = TokenCalculator.HashAlgorithm.valueOf(algorithm.toUpperCase()); } else { @@ -110,6 +120,12 @@ public class Entry { this.label = jsonObj.getString(JSON_LABEL); this.period = jsonObj.getInt(JSON_PERIOD); + try { + this.digits = jsonObj.getInt(JSON_DIGITS); + } catch(JSONException e) { + this.digits = TokenCalculator.TOTP_DEFAULT_DIGITS; + } + try { this.type = OTPType.valueOf(jsonObj.getString(JSON_TYPE)); } catch(JSONException e) { @@ -128,6 +144,7 @@ public class Entry { jsonObj.put(JSON_SECRET, new String(new Base32().encode(getSecret()))); jsonObj.put(JSON_LABEL, getLabel()); jsonObj.put(JSON_PERIOD, getPeriod()); + jsonObj.put(JSON_DIGITS, getDigits()); jsonObj.put(JSON_TYPE, getType().toString()); jsonObj.put(JSON_ALGORITHM, algorithm.toString()); @@ -166,6 +183,14 @@ public class Entry { this.period = period; } + public int getDigits() { + return digits; + } + + public void setDigits(int digits) { + this.digits = digits; + } + public TokenCalculator.HashAlgorithm getAlgorithm() { return this.algorithm; } @@ -187,7 +212,7 @@ public class Entry { long counter = time / this.getPeriod(); if (counter > last_update) { - currentOTP = TokenCalculator.TOTP(secret, period, algorithm); + currentOTP = TokenCalculator.TOTP(secret, period, digits, algorithm); last_update = counter; return true; @@ -202,6 +227,7 @@ public class Entry { if (o == null || getClass() != o.getClass()) return false; Entry entry = (Entry) o; return period == entry.period && + digits == entry.digits && type == entry.type && algorithm == entry.algorithm && Arrays.equals(secret, entry.secret) && @@ -210,6 +236,6 @@ public class Entry { @Override public int hashCode() { - return Objects.hash(type, period, algorithm, secret, label); + return Objects.hash(type, period, digits, algorithm, secret, label); } } diff --git a/app/src/main/java/org/shadowice/flocke/andotp/Utilities/TokenCalculator.java b/app/src/main/java/org/shadowice/flocke/andotp/Utilities/TokenCalculator.java index 3dfeabdb..84e58a63 100644 --- a/app/src/main/java/org/shadowice/flocke/andotp/Utilities/TokenCalculator.java +++ b/app/src/main/java/org/shadowice/flocke/andotp/Utilities/TokenCalculator.java @@ -33,6 +33,7 @@ import javax.crypto.spec.SecretKeySpec; public class TokenCalculator { public static final int TOTP_DEFAULT_PERIOD = 30; + public static final int TOTP_DEFAULT_DIGITS = 6; public enum HashAlgorithm { SHA1, SHA256, SHA512 @@ -50,8 +51,8 @@ public class TokenCalculator { return mac.doFinal(data); } - public static String TOTP(byte[] secret, int period, HashAlgorithm algorithm) { - return String.format("%06d", TOTP(secret, period, System.currentTimeMillis() / 1000, 6, algorithm)); + public static String TOTP(byte[] secret, int period, int digits, HashAlgorithm algorithm) { + return String.format("%0" + digits + "d", TOTP(secret, period, System.currentTimeMillis() / 1000, digits, algorithm)); } public static int TOTP(byte[] key, int period, long time, int digits, HashAlgorithm algorithm) diff --git a/app/src/main/res/layout/dialog_manual_entry.xml b/app/src/main/res/layout/dialog_manual_entry.xml index 30559adc..2c97254e 100644 --- a/app/src/main/res/layout/dialog_manual_entry.xml +++ b/app/src/main/res/layout/dialog_manual_entry.xml @@ -86,6 +86,27 @@ + + + + + + + + Type Secret Period + Digits Label Algorithm