Browse Source

HOTP implementation

master
Chris Smith 11 months ago
parent
commit
7f694ff3cc

+ 1
- 0
.gitignore View File

@@ -4,6 +4,7 @@
4 4
 /.idea/caches
5 5
 /.idea/libraries
6 6
 /.idea/modules.xml
7
+/.idea/uiDesigner.xml
7 8
 /.idea/workspace.xml
8 9
 .DS_Store
9 10
 /build

+ 6
- 0
.idea/vcs.xml View File

@@ -0,0 +1,6 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project version="4">
3
+  <component name="VcsDirectoryMappings">
4
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
+  </component>
6
+</project>

+ 7
- 6
app/build.gradle View File

@@ -1,15 +1,13 @@
1 1
 apply plugin: 'com.android.application'
2
-
3 2
 apply plugin: 'kotlin-android'
4
-
5 3
 apply plugin: 'kotlin-android-extensions'
6 4
 
7 5
 android {
8
-    compileSdkVersion 26
6
+    compileSdkVersion 28
9 7
     defaultConfig {
10 8
         applicationId "com.chameth.yaotp"
11 9
         minSdkVersion 26
12
-        targetSdkVersion 26
10
+        targetSdkVersion 28
13 11
         versionCode 1
14 12
         versionName "1.0"
15 13
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
@@ -24,10 +22,13 @@ android {
24 22
 
25 23
 dependencies {
26 24
     implementation fileTree(dir: 'libs', include: ['*.jar'])
27
-    implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
28
-    implementation 'com.android.support:appcompat-v7:26.1.0'
25
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
26
+    implementation 'com.android.support:appcompat-v7:28.0.0-rc01'
29 27
     implementation 'com.android.support.constraint:constraint-layout:1.1.2'
28
+
30 29
     testImplementation 'junit:junit:4.12'
30
+    testImplementation 'com.natpryce:hamkrest:1.5.0.0'
31
+
31 32
     androidTestImplementation 'com.android.support.test:runner:1.0.2'
32 33
     androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
33 34
 }

+ 15
- 0
app/src/main/java/com/chameth/yaotp/algos/Hotp.kt View File

@@ -0,0 +1,15 @@
1
+package com.chameth.yaotp.algos
2
+
3
+import java.nio.ByteBuffer
4
+import kotlin.experimental.and
5
+import kotlin.math.pow
6
+
7
+fun hotp(key: ByteArray, counter: ByteArray, length: Int = 6) = hmacToHotp(hmacSha1(key, counter), length)
8
+
9
+internal fun offsetValue(bytes: ByteArray) = (bytes[19] and 0x0F).toInt()
10
+
11
+internal fun offset(bytes: ByteArray) = offsetValue(bytes).let { bytes.sliceArray(IntRange(it, it + 3)) }
12
+
13
+internal fun mask(bytes: ByteArray) = ByteArray(bytes.size) { pos -> if (pos == 0) bytes[pos] and 0x7f else bytes[pos] }
14
+
15
+internal fun hmacToHotp(bytes: ByteArray, length: Int = 6) = (ByteBuffer.wrap(mask(offset(bytes))).int % 10.0.pow(length)).toInt()

+ 15
- 0
app/src/main/java/com/chameth/yaotp/algos/Utils.kt View File

@@ -0,0 +1,15 @@
1
+package com.chameth.yaotp.algos
2
+
3
+import javax.crypto.Mac
4
+import javax.crypto.spec.SecretKeySpec
5
+
6
+private const val HMAC_SHA1_ALGORITHM = "HmacSHA1"
7
+
8
+fun hmacSha1(keyMaterial: ByteArray, input: ByteArray): ByteArray {
9
+    return with(Mac.getInstance(HMAC_SHA1_ALGORITHM)) {
10
+        init(getKey(keyMaterial))
11
+        doFinal(input)
12
+    }
13
+}
14
+
15
+private fun getKey(material: ByteArray) = SecretKeySpec(material, HMAC_SHA1_ALGORITHM)

+ 0
- 17
app/src/test/java/com/chameth/yaotp/ExampleUnitTest.kt View File

@@ -1,17 +0,0 @@
1
-package com.chameth.yaotp
2
-
3
-import org.junit.Test
4
-
5
-import org.junit.Assert.*
6
-
7
-/**
8
- * Example local unit test, which will execute on the development machine (host).
9
- *
10
- * See [testing documentation](http://d.android.com/tools/testing).
11
- */
12
-class ExampleUnitTest {
13
-    @Test
14
-    fun addition_isCorrect() {
15
-        assertEquals(4, 2 + 2)
16
-    }
17
-}

+ 45
- 0
app/src/test/java/com/chameth/yaotp/algos/HotpTest.kt View File

@@ -0,0 +1,45 @@
1
+package com.chameth.yaotp.algos
2
+
3
+import com.natpryce.hamkrest.assertion.assert
4
+import com.natpryce.hamkrest.equalTo
5
+import org.junit.Test
6
+
7
+class HotpTest {
8
+
9
+    private val rfcByteArray = byteArrayOfInts(0x1f, 0x86, 0x98, 0x69, 0x0e, 0x02, 0xca, 0x16, 0x61, 0x85, 0x50, 0xef, 0x7f, 0x19, 0xda, 0x8e, 0x94, 0x5b, 0x55, 0x5a)
10
+
11
+    @Test
12
+    fun testOffsetValue_returnsLastFourBitsAsInt() {
13
+        assert.that(offsetValue(rfcByteArray), equalTo(10))
14
+    }
15
+
16
+    @Test
17
+    fun testOffset_returnsFourBytesAtOffsetValue() {
18
+        assert.that(offset(rfcByteArray).toHexString(), equalTo("50ef7f19"))
19
+    }
20
+
21
+    @Test
22
+    fun testMask_returnsValueUnchanged_ifMsbIsZero() {
23
+        assert.that(mask(byteArrayOfInts(0x50, 0xef, 0x7f, 0x19)).toHexString(), equalTo("50ef7f19"))
24
+    }
25
+
26
+    @Test
27
+    fun testMask_returnsMaskedValue_ifMsbIsNotZero() {
28
+        assert.that(mask(byteArrayOfInts(0xA0, 0xef, 0x7f, 0x19)).toHexString(), equalTo("20ef7f19"))
29
+    }
30
+
31
+    @Test
32
+    fun testHmacToHotp_withDefaultLength() {
33
+        assert.that(hmacToHotp(rfcByteArray), equalTo(872921))
34
+    }
35
+
36
+    @Test
37
+    fun testHotp_withRfcTestData() {
38
+        val key = "12345678901234567890".toByteArray()
39
+        val expected = listOf(755224, 287082, 359152, 969429, 338314, 254676, 287922, 162583, 399871, 520489)
40
+
41
+        expected.forEachIndexed { index, i -> assert.that(hotp(key, byteArrayOfInts(0, 0, 0, 0, 0, 0, 0, index)), equalTo(i)) }
42
+    }
43
+
44
+    private fun byteArrayOfInts(vararg ints: Int) = ByteArray(ints.size) { pos -> ints[pos].toByte() }
45
+}

+ 3
- 0
app/src/test/java/com/chameth/yaotp/algos/TestUtils.kt View File

@@ -0,0 +1,3 @@
1
+package com.chameth.yaotp.algos
2
+
3
+fun ByteArray.toHexString() = map { String.format("%02x", it) }.reduce(String::plus)

+ 18
- 0
app/src/test/java/com/chameth/yaotp/algos/UtilsTest.kt View File

@@ -0,0 +1,18 @@
1
+package com.chameth.yaotp.algos
2
+
3
+import com.natpryce.hamkrest.assertion.assert
4
+import com.natpryce.hamkrest.equalTo
5
+import org.junit.Test
6
+
7
+class UtilsTest {
8
+
9
+    @Test
10
+    fun testHmacSha1_withKnownValues() {
11
+        val key = "key".toByteArray()
12
+        val input = "The quick brown fox jumps over the lazy dog".toByteArray()
13
+        val expected = "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9"
14
+
15
+        assert.that(hmacSha1(key, input).toHexString(), equalTo(expected))
16
+    }
17
+
18
+}

+ 1
- 3
build.gradle View File

@@ -2,6 +2,7 @@
2 2
 
3 3
 buildscript {
4 4
     ext.kotlin_version = '1.2.51'
5
+
5 6
     repositories {
6 7
         google()
7 8
         jcenter()
@@ -9,9 +10,6 @@ buildscript {
9 10
     dependencies {
10 11
         classpath 'com.android.tools.build:gradle:3.1.4'
11 12
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12
-
13
-        // NOTE: Do not place your application dependencies here; they belong
14
-        // in the individual module build.gradle files
15 13
     }
16 14
 }
17 15
 

Loading…
Cancel
Save