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)) + ) + } + }