Add crypto method to get salted names for package data and APKs

This will only hide installed apps from naive attackers as the APKs are still not encrypted and even then other attacks would be possible.

However, it allows us to simplify our storage plugin API.
This commit is contained in:
Torsten Grote 2021-09-16 18:11:37 +02:00 committed by Chirayu Desai
parent 793663acb5
commit 75cf014e5d
2 changed files with 90 additions and 0 deletions

View file

@ -1,6 +1,7 @@
package com.stevesoltys.seedvault.crypto
import com.google.crypto.tink.subtle.AesGcmHkdfStreaming
import com.stevesoltys.seedvault.encodeBase64
import com.stevesoltys.seedvault.header.HeaderReader
import com.stevesoltys.seedvault.header.MAX_SEGMENT_LENGTH
import com.stevesoltys.seedvault.header.MAX_VERSION_HEADER_SIZE
@ -13,6 +14,8 @@ import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.security.GeneralSecurityException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.security.SecureRandom
import javax.crypto.spec.SecretKeySpec
@ -39,6 +42,10 @@ internal interface Crypto {
*/
fun getRandomBytes(size: Int): ByteArray
fun getNameForPackage(salt: String, packageName: String): String
fun getNameForApk(salt: String, packageName: String, suffix: String = ""): String
/**
* Returns a [AesGcmHkdfStreaming] encrypting stream
* that gets encrypted and authenticated the given associated data.
@ -119,6 +126,24 @@ internal class CryptoImpl(
secureRandom.nextBytes(this)
}
override fun getNameForPackage(salt: String, packageName: String): String {
return sha256("$salt$packageName".toByteArray()).encodeBase64()
}
override fun getNameForApk(salt: String, packageName: String, suffix: String): String {
return sha256("${salt}APK$packageName$suffix".toByteArray()).encodeBase64()
}
private fun sha256(bytes: ByteArray): ByteArray {
val messageDigest: MessageDigest = try {
MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) {
throw AssertionError(e)
}
messageDigest.update(bytes)
return messageDigest.digest()
}
@Throws(IOException::class, GeneralSecurityException::class)
override fun newEncryptingStream(
outputStream: OutputStream,

View file

@ -1,7 +1,12 @@
package com.stevesoltys.seedvault.crypto
import com.stevesoltys.seedvault.getRandomBase64
import com.stevesoltys.seedvault.getRandomString
import com.stevesoltys.seedvault.header.HeaderReaderImpl
import com.stevesoltys.seedvault.metadata.METADATA_SALT_SIZE
import io.mockk.mockk
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotEquals
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
@ -26,4 +31,64 @@ class CryptoImplTest {
}
}
@Test
fun `getNameForPackage() and getNameForApk() return deterministic results`() {
val salt = getRandomBase64(METADATA_SALT_SIZE)
val packageName = getRandomString(32)
assertEquals(
crypto.getNameForPackage(salt, packageName),
crypto.getNameForPackage(salt, packageName)
)
assertEquals(
crypto.getNameForApk(salt, packageName),
crypto.getNameForApk(salt, packageName)
)
}
@Test
fun `getNameForPackage() and getNameForApk() return different results`() {
val salt = getRandomBase64(METADATA_SALT_SIZE)
val packageName = getRandomString(32)
assertNotEquals(
crypto.getNameForPackage(salt, packageName),
crypto.getNameForApk(salt, packageName)
)
}
@Test
fun `getNameForPackage() and getNameForApk() return different results for different salts`() {
val packageName = getRandomString(32)
assertNotEquals(
crypto.getNameForPackage(getRandomBase64(METADATA_SALT_SIZE), packageName),
crypto.getNameForPackage(getRandomBase64(METADATA_SALT_SIZE), packageName)
)
assertNotEquals(
crypto.getNameForApk(getRandomBase64(METADATA_SALT_SIZE), packageName),
crypto.getNameForApk(getRandomBase64(METADATA_SALT_SIZE), packageName)
)
}
@Test
fun `getNameForPackage() and getNameForApk() return different for different package names`() {
val salt = getRandomBase64(METADATA_SALT_SIZE)
assertNotEquals(
crypto.getNameForPackage(salt, getRandomString(32)),
crypto.getNameForPackage(salt, getRandomString(32))
)
assertNotEquals(
crypto.getNameForApk(salt, getRandomString(32)),
crypto.getNameForApk(salt, getRandomString(32))
)
}
@Test
fun `getNameForApk() return different results for different suffixes`() {
val salt = getRandomBase64(METADATA_SALT_SIZE)
val packageName = getRandomString(32)
assertNotEquals(
crypto.getNameForApk(salt, packageName, getRandomString(4)),
crypto.getNameForApk(salt, packageName, getRandomString(5))
)
}
}