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:
parent
793663acb5
commit
75cf014e5d
2 changed files with 90 additions and 0 deletions
|
@ -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,
|
||||||
|
|
|
@ -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))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue