11package com.darkrockstudios.app.securecamera.security
22
33import dev.whyoleg.cryptography.random.CryptographyRandom
4+ import java.nio.ByteBuffer
45import kotlin.random.Random
56
67class 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