Skip to content

Commit 22e440a

Browse files
committed
Improved ShardedKey
We now use direct allocated ByteBuffers under the hood which allows us to more strict control over the memory and its lifetime
1 parent 36ab51c commit 22e440a

1 file changed

Lines changed: 49 additions & 24 deletions

File tree

  • app/src/main/kotlin/com/darkrockstudios/app/securecamera/security
Lines changed: 49 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,37 @@
11
package com.darkrockstudios.app.securecamera.security
22

33
import dev.whyoleg.cryptography.random.CryptographyRandom
4+
import java.nio.ByteBuffer
45
import kotlin.random.Random
56

67
class ShardedKey(
78
key: ByteArray
89
) {
9-
private val part1: ByteArray
10-
private val part2: ByteArray
10+
private val part1: ByteBuffer
11+
private val part2: ByteBuffer
1112
private val keySize: Int = key.size
1213

1314
init {
14-
// Randomly size the storage array so it is not the exact length of an AES key
15-
part1 = ByteArray(keySize + CryptographyRandom.nextInt(3, 155))
16-
CryptographyRandom.nextBytes(part1)
15+
// Randomly size the storage buffer so it is not the exact length of an AES key
16+
val part1Size = keySize + CryptographyRandom.nextInt(3, 155)
17+
part1 = ByteBuffer.allocateDirect(part1Size)
18+
val part1Array = ByteArray(part1Size)
19+
CryptographyRandom.nextBytes(part1Array)
20+
part1.put(part1Array)
21+
part1.flip()
1722

1823
// Best effort to ensure the two key parts don't live next to each other in memory.
19-
// Because this is the JVM, Buckets and Garbage collection and what not will move
20-
// things around, so they may end up next to each other now or later, but at least
21-
// we tried. This will be released once the constructor returns (or when ever the GC
22-
// runs next)
23-
val spacer = ByteArray(Random.nextInt(978, 2893))
24+
// This will be released once the constructor returns (or when ever the GC runs next)
25+
val spacerSize = Random.nextInt(978, 2893)
26+
val spacer = ByteBuffer.allocateDirect(spacerSize)
27+
val spacerArray = ByteArray(spacerSize)
28+
CryptographyRandom.nextBytes(spacerArray)
29+
spacer.put(spacerArray)
30+
spacer.flip()
31+
2432
// Use it so it doesn't get optimized out
2533
(0..Random.nextInt(13)).forEachIndexed { i, _ ->
26-
val x = spacer[0] + spacer[i]
34+
val x = spacer.get(0) + spacer.get(i)
2735
// Almost never true, but compiler doesn't know that
2836
if (System.nanoTime() % 10000 == 0L) {
2937
// Side effect that's rarely executed
@@ -32,13 +40,18 @@ class ShardedKey(
3240
}
3341
}
3442

35-
// Randomly size the storage array so it is not the exact length of an AES key
36-
part2 = ByteArray(keySize + CryptographyRandom.nextInt(5, 111))
37-
// Fill it with random data first
38-
CryptographyRandom.nextBytes(part2)
43+
// Randomly size the storage buffer so it is not the exact length of an AES key
44+
val part2Size = keySize + CryptographyRandom.nextInt(5, 111)
45+
part2 = ByteBuffer.allocateDirect(part2Size)
46+
val part2Array = ByteArray(part2Size)
47+
CryptographyRandom.nextBytes(part2Array)
48+
part2.put(part2Array)
49+
part2.flip()
50+
3951
// Now fill the data part with our XOR'd key
4052
for (i in key.indices) {
41-
part2[i] = (key[i].toInt() xor part1[i].toInt()).toByte()
53+
val xorByte = (key[i].toInt() xor part1.get(i).toInt()).toByte()
54+
part2.put(i, xorByte)
4255
}
4356
}
4457

@@ -50,19 +63,31 @@ class ShardedKey(
5063
fun reconstructKey(): ByteArray {
5164
val originalKey = ByteArray(keySize)
5265
for (i in originalKey.indices) {
53-
originalKey[i] = (part1[i].toInt() xor part2[i].toInt()).toByte()
66+
originalKey[i] = (part1.get(i).toInt() xor part2.get(i).toInt()).toByte()
5467
}
5568

5669
return originalKey
5770
}
5871

5972
fun evict() {
60-
// Modern Android already zero's memory on allocation, but... who knows.
61-
part1.let { k ->
62-
k.forEachIndexed { index, _ -> k[index] = 0x00 }
63-
}
64-
part2.let { k ->
65-
k.forEachIndexed { index, _ -> k[index] = 0x00 }
73+
part1.zeroOut()
74+
part2.zeroOut()
75+
}
76+
}
77+
78+
private fun ByteBuffer.zeroOut() {
79+
if (hasArray()) {
80+
val a = array()
81+
val start = arrayOffset() + position()
82+
val end = arrayOffset() + limit()
83+
java.util.Arrays.fill(a, start, end, 0.toByte())
84+
} else {
85+
val pos = position()
86+
val lim = limit()
87+
for (i in 0 until capacity()) {
88+
put(i, 0)
6689
}
90+
position(pos)
91+
limit(lim)
6792
}
68-
}
93+
}

0 commit comments

Comments
 (0)