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 package com.stevesoltys.seedvault.crypto
import com.google.crypto.tink.subtle.AesGcmHkdfStreaming import com.google.crypto.tink.subtle.AesGcmHkdfStreaming
import com.stevesoltys.seedvault.encodeBase64
import com.stevesoltys.seedvault.header.HeaderReader import com.stevesoltys.seedvault.header.HeaderReader
import com.stevesoltys.seedvault.header.MAX_SEGMENT_LENGTH import com.stevesoltys.seedvault.header.MAX_SEGMENT_LENGTH
import com.stevesoltys.seedvault.header.MAX_VERSION_HEADER_SIZE import com.stevesoltys.seedvault.header.MAX_VERSION_HEADER_SIZE
@ -13,6 +14,8 @@ import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import java.security.GeneralSecurityException import java.security.GeneralSecurityException
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.security.SecureRandom import java.security.SecureRandom
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
@ -39,6 +42,10 @@ internal interface Crypto {
*/ */
fun getRandomBytes(size: Int): ByteArray 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 * Returns a [AesGcmHkdfStreaming] encrypting stream
* that gets encrypted and authenticated the given associated data. * that gets encrypted and authenticated the given associated data.
@ -119,6 +126,24 @@ internal class CryptoImpl(
secureRandom.nextBytes(this) 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) @Throws(IOException::class, GeneralSecurityException::class)
override fun newEncryptingStream( override fun newEncryptingStream(
outputStream: OutputStream, outputStream: OutputStream,

View file

@ -1,7 +1,12 @@
package com.stevesoltys.seedvault.crypto 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.header.HeaderReaderImpl
import com.stevesoltys.seedvault.metadata.METADATA_SALT_SIZE
import io.mockk.mockk 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.Assertions.assertThrows
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance 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))
)
}
} }