Skip to content
This repository was archived by the owner on Sep 13, 2024. It is now read-only.

Commit b5cbf27

Browse files
Merge pull request #22 from coderbunker/issue#5-hotp
Finished HOTP implementation
2 parents 24e79b8 + 09a0992 commit b5cbf27

File tree

7 files changed

+249
-25
lines changed

7 files changed

+249
-25
lines changed

.idea/caches/build_file_checksums.ser

0 Bytes
Binary file not shown.

app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
android:screenOrientation="landscape">
2121

2222
</activity>
23-
<activity android:name=".KioskActivity">
23+
<activity
24+
android:name=".KioskActivity"
25+
android:screenOrientation="landscape">
2426
<intent-filter>
2527
<action android:name="android.intent.action.MAIN" />
2628

app/src/main/java/com/coderbunker/kioskapp/KioskActivity.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@
1515
import android.view.Window;
1616
import android.view.WindowManager;
1717
import android.webkit.SslErrorHandler;
18-
import android.webkit.WebChromeClient;
1918
import android.webkit.WebView;
2019
import android.webkit.WebViewClient;
2120
import android.widget.Button;
2221
import android.widget.Toast;
2322

23+
import com.coderbunker.kioskapp.lib.HOTP;
2424
import com.coderbunker.kioskapp.lib.TOTP;
2525

26+
import java.security.InvalidKeyException;
27+
import java.security.NoSuchAlgorithmException;
2628
import java.util.ArrayList;
2729
import java.util.Arrays;
2830
import java.util.List;
@@ -85,11 +87,16 @@ protected void onCreate(Bundle savedInstanceState) {
8587
webView = findViewById(R.id.webview);
8688
webView.setWebViewClient(new WebViewClient() {
8789
@Override
88-
public void onPageFinished(WebView view, String url) {
90+
public void onPageFinished(final WebView view, String url) {
8991
super.onPageFinished(view, url);
9092
TimerTask lock = new TimerTask() {
9193
@Override
9294
public void run() {
95+
runOnUiThread(new Runnable() {
96+
public void run() {
97+
Toast.makeText(context, "Kioskmode locked", Toast.LENGTH_SHORT).show();
98+
}
99+
});
93100
locked = true;
94101
}
95102
};
@@ -134,7 +141,6 @@ public boolean onTouch(View view, MotionEvent motionEvent) {
134141
});
135142

136143
numbers = new ArrayList<Button>();
137-
138144
}
139145

140146

@@ -224,6 +230,7 @@ private void launchHome() {
224230

225231
private void checkPwd() {
226232
String otp = prefs.getString("otp", null);
233+
int hotp_counter = prefs.getInt("hotp_counter", 0);
227234
if (otp == null) {
228235
Toast.makeText(context, "Please go to the settings and create a password", Toast.LENGTH_SHORT).show();
229236
launchHome();
@@ -232,8 +239,22 @@ private void checkPwd() {
232239
String generated_number = TOTP.generateCurrentNumber(otp, System.currentTimeMillis());
233240
String previous_generated_number = TOTP.generateCurrentNumber(otp, System.currentTimeMillis() - 30000);
234241

242+
243+
//HOTP
244+
for (int i = 0; i < 10; i++) {
245+
if (pwd.equals(HOTP.generateHOTP(hotp_counter - 5 + i, otp))) {
246+
Toast.makeText(context, "HOTP PIN correct", Toast.LENGTH_SHORT).show();
247+
248+
hotp_counter++;
249+
prefs.edit().putInt("hotp_counter", hotp_counter).apply();
250+
251+
launchHome();
252+
}
253+
}
254+
255+
//TOTP
235256
if (pwd.equals(generated_number) || pwd.equals(previous_generated_number)) {
236-
Toast.makeText(context, "PIN correct", Toast.LENGTH_SHORT).show();
257+
Toast.makeText(context, "TOTP PIN correct", Toast.LENGTH_SHORT).show();
237258
launchHome();
238259
} else {
239260
dialog.dismiss();

app/src/main/java/com/coderbunker/kioskapp/SettingsActivity.java

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22

33
import android.app.Activity;
44
import android.content.Context;
5+
import android.content.Intent;
56
import android.content.SharedPreferences;
67
import android.graphics.Bitmap;
78
import android.graphics.Color;
89
import android.support.v7.app.AppCompatActivity;
910
import android.os.Bundle;
1011
import android.view.KeyEvent;
12+
import android.view.MenuItem;
1113
import android.view.View;
1214
import android.webkit.URLUtil;
1315
import android.widget.Button;
@@ -28,25 +30,29 @@ public class SettingsActivity extends Activity {
2830

2931
private Context context = this;
3032
private EditText editURL;
31-
private ImageView imgQRCode;
33+
private ImageView imgQRCode, imgQRCodeHOTP;
34+
private TextView lblCurrentHOTPCycle;
3235
private Button btnSave;
3336

3437
private SharedPreferences prefs;
3538

36-
private String otp_uri;
39+
private String otp_uri, hotp_uri;
3740

3841
@Override
3942
protected void onCreate(Bundle savedInstanceState) {
4043
super.onCreate(savedInstanceState);
4144
setContentView(R.layout.activity_settings);
4245

46+
getActionBar().setDisplayHomeAsUpEnabled(true);
47+
4348
prefs = this.getSharedPreferences(
4449
"com.coderbunker.kioskapp", Context.MODE_PRIVATE);
4550

4651
imgQRCode = findViewById(R.id.imgQRCode);
52+
imgQRCodeHOTP = findViewById(R.id.imgQRCodeHOTP);
4753
editURL = findViewById(R.id.editText_URL);
4854
btnSave = findViewById(R.id.btnSave);
49-
55+
lblCurrentHOTPCycle = findViewById(R.id.current_hotp_cycle);
5056

5157
btnSave.setOnClickListener(new View.OnClickListener() {
5258
@Override
@@ -66,9 +72,12 @@ public void onClick(View v) {
6672

6773
String otp = prefs.getString("otp", null);
6874
String url = prefs.getString("url", "https://naibaben.github.io/");
75+
int hotp_counter = prefs.getInt("hotp_counter", 0);
6976

7077
editURL.setText(url);
7178

79+
lblCurrentHOTPCycle.setText("Current counter cycle: " + hotp_counter);
80+
7281
if (otp == null) {
7382

7483
byte key_1 = (byte) Math.floor(Math.random() * 10);
@@ -88,12 +97,14 @@ public void onClick(View v) {
8897
editor.apply();
8998
}
9099

91-
otp_uri = "otpauth://totp/Admin?secret=" + otp + "&issuer=Coderbunker";
100+
otp_uri = "otpauth://totp/Time%20based?secret=" + otp + "&issuer=Kiosk%20Coderbunker";
101+
hotp_uri = "otpauth://hotp/Hash%20based?secret=" + otp + "&issuer=Kiosk%20Coderbunker&counter=" + (hotp_counter - 1) + "&algorithm=SHA1";
92102

93-
generateQRCode(otp_uri);
103+
generateQRCodeTOTP(otp_uri);
104+
generateQRCodeHOTP(hotp_uri);
94105
}
95106

96-
private void generateQRCode(String uri) {
107+
private void generateQRCodeTOTP(String uri) {
97108
QRCodeWriter writer = new QRCodeWriter();
98109
try {
99110
BitMatrix bitMatrix = writer.encode(uri, BarcodeFormat.QR_CODE, 512, 512);
@@ -111,4 +122,36 @@ private void generateQRCode(String uri) {
111122
e.printStackTrace();
112123
}
113124
}
125+
126+
private void generateQRCodeHOTP(String uri) {
127+
QRCodeWriter writer = new QRCodeWriter();
128+
try {
129+
BitMatrix bitMatrix = writer.encode(uri, BarcodeFormat.QR_CODE, 512, 512);
130+
int width = bitMatrix.getWidth();
131+
int height = bitMatrix.getHeight();
132+
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
133+
for (int x = 0; x < width; x++) {
134+
for (int y = 0; y < height; y++) {
135+
bmp.setPixel(x, y, bitMatrix.get(x, y) ? Color.BLACK : Color.WHITE);
136+
}
137+
}
138+
imgQRCodeHOTP.setImageBitmap(bmp);
139+
140+
} catch (WriterException e) {
141+
e.printStackTrace();
142+
}
143+
}
144+
145+
@Override
146+
public boolean onOptionsItemSelected(MenuItem item) {
147+
switch (item.getItemId()) {
148+
case android.R.id.home:
149+
Intent intent = new Intent(SettingsActivity.this, MainActivity.class);
150+
startActivity(intent);
151+
finish();
152+
return true;
153+
default:
154+
return super.onOptionsItemSelected(item);
155+
}
156+
}
114157
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* HOTP.java
3+
* OATH Initiative,
4+
* HOTP one-time password algorithm
5+
*
6+
*/
7+
8+
/* Copyright (C) 2004, OATH. All rights reserved.
9+
*
10+
* License to copy and use this software is granted provided that it
11+
* is identified as the "OATH HOTP Algorithm" in all material
12+
* mentioning or referencing this software or this function.
13+
*
14+
* License is also granted to make and use derivative works provided
15+
* that such works are identified as
16+
* "derived from OATH HOTP algorithm"
17+
* in all material mentioning or referencing the derived work.
18+
*
19+
* OATH (Open AuTHentication) and its members make no
20+
* representations concerning either the merchantability of this
21+
* software or the suitability of this software for any particular
22+
* purpose.
23+
*
24+
* It is provided "as is" without express or implied warranty
25+
* of any kind and OATH AND ITS MEMBERS EXPRESSaLY DISCLAIMS
26+
* ANY WARRANTY OR LIABILITY OF ANY KIND relating to this software.
27+
*
28+
* These notices must be retained in any copies of any part of this
29+
* documentation and/or software.
30+
*/
31+
32+
package com.coderbunker.kioskapp.lib;
33+
34+
import java.lang.reflect.UndeclaredThrowableException;
35+
import java.security.GeneralSecurityException;
36+
37+
import javax.crypto.Mac;
38+
import javax.crypto.spec.SecretKeySpec;
39+
40+
/**
41+
* This class contains static methods that are used to calculate the
42+
* One-Time Password (OTP) using
43+
* JCE to provide the HMAC-SHA-1.
44+
*
45+
* @author Loren Hart
46+
* @version 1.0
47+
*/
48+
public class HOTP {
49+
public static final int[] DIGITS_POWER
50+
// 0 1 2 3 4 5 6 7 8
51+
= {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
52+
53+
public static String generateHOTP(long count, String seedString) {
54+
55+
byte[] counter = new byte[8];
56+
long movingFactor = count;
57+
58+
for (int i = counter.length - 1; i >= 0; i--) {
59+
counter[i] = (byte) (movingFactor & 0xff);
60+
movingFactor >>= 8;
61+
}
62+
byte[] seed = new byte[0];
63+
try {
64+
seed = Base32.decode(seedString);
65+
} catch (Base32.DecodingException e) {
66+
e.printStackTrace();
67+
}
68+
69+
70+
byte[] hash = HMAC(seed, counter);
71+
int offset = hash[hash.length - 1] & 0xf;
72+
73+
int otpBinary = ((hash[offset] & 0x7f) << 24)
74+
| ((hash[offset + 1] & 0xff) << 16)
75+
| ((hash[offset + 2] & 0xff) << 8)
76+
| (hash[offset + 3] & 0xff);
77+
78+
int otp = otpBinary % DIGITS_POWER[6];
79+
String result = Integer.toString(otp);
80+
81+
while (result.length() < 6) {
82+
result = "0" + result;
83+
}
84+
return result;
85+
86+
}
87+
88+
private static byte[] HMAC(byte[] seed, byte[] counter) {
89+
90+
try {
91+
Mac hmac = Mac.getInstance("HmacSHA1");
92+
SecretKeySpec macKey = new SecretKeySpec(seed, "RAW");
93+
hmac.init(macKey);
94+
return hmac.doFinal(counter);
95+
96+
} catch (GeneralSecurityException ex) {
97+
throw new UndeclaredThrowableException(ex);
98+
}
99+
}
100+
}

app/src/main/res/layout/activity_settings.xml

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,79 @@
1919
android:orientation="vertical">
2020

2121

22-
<TextView
23-
android:id="@+id/textView_otp"
22+
<LinearLayout
2423
android:layout_width="match_parent"
25-
android:layout_height="wrap_content"
26-
android:gravity="center_horizontal"
27-
android:paddingVertical="20dp"
28-
android:text="TOTP"
29-
android:textColor="@android:color/black"
30-
android:textSize="22dp" />
24+
android:layout_height="match_parent"
25+
android:orientation="horizontal">
3126

32-
<ImageView
33-
android:id="@+id/imgQRCode"
34-
android:layout_width="match_parent"
35-
android:layout_height="wrap_content"
36-
android:src="@drawable/ic_launcher_background" />
27+
<LinearLayout
28+
android:layout_width="match_parent"
29+
android:layout_height="match_parent"
30+
android:layout_weight="1"
31+
android:orientation="vertical">
32+
33+
<TextView
34+
android:id="@+id/textView_otp"
35+
android:layout_width="match_parent"
36+
android:layout_height="wrap_content"
37+
android:layout_weight="1"
38+
android:gravity="center_horizontal"
39+
android:paddingVertical="20dp"
40+
android:text="Time based"
41+
android:textColor="@android:color/black"
42+
android:textSize="22dp" />
43+
44+
<ImageView
45+
android:id="@+id/imgQRCode"
46+
android:layout_width="match_parent"
47+
android:layout_height="wrap_content"
48+
android:layout_weight="1"
49+
android:src="@drawable/ic_launcher_background" />
50+
51+
<TextView
52+
android:id="@+id/placeholder"
53+
android:layout_width="match_parent"
54+
android:layout_height="wrap_content"
55+
android:layout_weight="1"
56+
android:textAlignment="center" />
57+
58+
</LinearLayout>
59+
60+
<LinearLayout
61+
android:layout_width="match_parent"
62+
android:layout_height="match_parent"
63+
android:layout_weight="1"
64+
android:orientation="vertical">
65+
66+
<TextView
67+
android:id="@+id/textView_hotp"
68+
android:layout_width="match_parent"
69+
android:layout_height="wrap_content"
70+
android:layout_weight="1"
71+
android:gravity="center_horizontal"
72+
android:paddingVertical="20dp"
73+
android:text="Counter based"
74+
android:textColor="@android:color/black"
75+
android:textSize="22dp" />
76+
77+
<ImageView
78+
android:id="@+id/imgQRCodeHOTP"
79+
android:layout_width="match_parent"
80+
android:layout_height="wrap_content"
81+
android:layout_weight="1"
82+
android:src="@drawable/ic_launcher_background" />
83+
84+
<TextView
85+
android:id="@+id/current_hotp_cycle"
86+
android:layout_width="match_parent"
87+
android:layout_height="wrap_content"
88+
android:layout_weight="1"
89+
android:text="Current cycle: ---"
90+
android:textAlignment="center" />
91+
92+
</LinearLayout>
93+
94+
</LinearLayout>
3795

3896
<TextView
3997
android:id="@+id/textView_URL"

0 commit comments

Comments
 (0)