From 75cf014e5df30473917871758bbe9f2f7663e1e4 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Thu, 16 Sep 2021 18:11:37 +0200 Subject: [PATCH] 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. --- .../stevesoltys/seedvault/crypto/Crypto.kt | 25 +++++++ .../seedvault/crypto/CryptoImplTest.kt | 65 +++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt b/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt index ada5b759..fb0d509c 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/crypto/Crypto.kt @@ -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, diff --git a/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoImplTest.kt b/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoImplTest.kt index a0f0c9e7..879ee745 100644 --- a/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoImplTest.kt +++ b/app/src/test/java/com/stevesoltys/seedvault/crypto/CryptoImplTest.kt @@ -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)) + ) + } + }