Safer password handling (#1146)
* no longer convert passwords to string * also clear backing array of outputStream * use fill and small refactor
This commit is contained in:
parent
206d824ed2
commit
596a8d002f
@ -16,6 +16,8 @@ import uy.kohesive.injekt.injectLazy
|
|||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.CharBuffer
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
@ -33,28 +35,28 @@ object CbzCrypto {
|
|||||||
const val DATABASE_NAME = "tachiyomiEncrypted.db"
|
const val DATABASE_NAME = "tachiyomiEncrypted.db"
|
||||||
const val DEFAULT_COVER_NAME = "cover.jpg"
|
const val DEFAULT_COVER_NAME = "cover.jpg"
|
||||||
private val securityPreferences: SecurityPreferences by injectLazy()
|
private val securityPreferences: SecurityPreferences by injectLazy()
|
||||||
private val keyStore = KeyStore.getInstance(KEYSTORE).apply {
|
private val keyStore = KeyStore.getInstance(Keystore).apply {
|
||||||
load(null)
|
load(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val encryptionCipherCbz
|
private val encryptionCipherCbz
|
||||||
get() = Cipher.getInstance(CRYPTO_SETTINGS).apply {
|
get() = Cipher.getInstance(CryptoSettings).apply {
|
||||||
init(
|
init(
|
||||||
Cipher.ENCRYPT_MODE,
|
Cipher.ENCRYPT_MODE,
|
||||||
getKey(ALIAS_CBZ),
|
getKey(AliasCbz),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val encryptionCipherSql
|
private val encryptionCipherSql
|
||||||
get() = Cipher.getInstance(CRYPTO_SETTINGS).apply {
|
get() = Cipher.getInstance(CryptoSettings).apply {
|
||||||
init(
|
init(
|
||||||
Cipher.ENCRYPT_MODE,
|
Cipher.ENCRYPT_MODE,
|
||||||
getKey(ALIAS_SQL),
|
getKey(AliasSql),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDecryptCipher(iv: ByteArray, alias: String): Cipher {
|
private fun getDecryptCipher(iv: ByteArray, alias: String): Cipher {
|
||||||
return Cipher.getInstance(CRYPTO_SETTINGS).apply {
|
return Cipher.getInstance(CryptoSettings).apply {
|
||||||
init(
|
init(
|
||||||
Cipher.DECRYPT_MODE,
|
Cipher.DECRYPT_MODE,
|
||||||
getKey(alias),
|
getKey(alias),
|
||||||
@ -69,12 +71,12 @@ object CbzCrypto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun generateKey(alias: String): SecretKey {
|
private fun generateKey(alias: String): SecretKey {
|
||||||
return KeyGenerator.getInstance(ALGORITHM).apply {
|
return KeyGenerator.getInstance(Algorithm).apply {
|
||||||
init(
|
init(
|
||||||
KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
|
KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
|
||||||
.setKeySize(KEY_SIZE)
|
.setKeySize(KeySize)
|
||||||
.setBlockModes(BLOCK_MODE)
|
.setBlockModes(BlockMode)
|
||||||
.setEncryptionPaddings(PADDING)
|
.setEncryptionPaddings(Padding)
|
||||||
.setRandomizedEncryptionRequired(true)
|
.setRandomizedEncryptionRequired(true)
|
||||||
.setUserAuthenticationRequired(false)
|
.setUserAuthenticationRequired(false)
|
||||||
.build(),
|
.build(),
|
||||||
@ -82,13 +84,13 @@ object CbzCrypto {
|
|||||||
}.generateKey()
|
}.generateKey()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun encrypt(password: String, cipher: Cipher): String {
|
private fun encrypt(password: ByteArray, cipher: Cipher): String {
|
||||||
val outputStream = ByteArrayOutputStream()
|
val outputStream = ByteArrayOutputStream()
|
||||||
outputStream.use { output ->
|
outputStream.use { output ->
|
||||||
output.write(cipher.iv)
|
output.write(cipher.iv)
|
||||||
ByteArrayInputStream(password.toByteArray()).use { input ->
|
ByteArrayInputStream(password).use { input ->
|
||||||
val buffer = ByteArray(BUFFER_SIZE)
|
val buffer = ByteArray(BufferSize)
|
||||||
while (input.available() > BUFFER_SIZE) {
|
while (input.available() > BufferSize) {
|
||||||
input.read(buffer)
|
input.read(buffer)
|
||||||
output.write(cipher.update(buffer))
|
output.write(cipher.update(buffer))
|
||||||
}
|
}
|
||||||
@ -98,51 +100,66 @@ object CbzCrypto {
|
|||||||
return Base64.encodeToString(outputStream.toByteArray(), Base64.DEFAULT)
|
return Base64.encodeToString(outputStream.toByteArray(), Base64.DEFAULT)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun decrypt(encryptedPassword: String, alias: String): String {
|
private fun decrypt(encryptedPassword: String, alias: String): ByteArray {
|
||||||
val inputStream = Base64.decode(encryptedPassword, Base64.DEFAULT).inputStream()
|
val inputStream = Base64.decode(encryptedPassword, Base64.DEFAULT).inputStream()
|
||||||
return inputStream.use { input ->
|
return inputStream.use { input ->
|
||||||
val iv = ByteArray(IV_SIZE)
|
val iv = ByteArray(IvSize)
|
||||||
input.read(iv)
|
input.read(iv)
|
||||||
val cipher = getDecryptCipher(iv, alias)
|
val cipher = getDecryptCipher(iv, alias)
|
||||||
ByteArrayOutputStream().use { output ->
|
ByteArrayOutputStreamPassword().use { output ->
|
||||||
val buffer = ByteArray(BUFFER_SIZE)
|
val buffer = ByteArray(BufferSize)
|
||||||
while (inputStream.available() > BUFFER_SIZE) {
|
while (inputStream.available() > BufferSize) {
|
||||||
inputStream.read(buffer)
|
inputStream.read(buffer)
|
||||||
output.write(cipher.update(buffer))
|
output.write(cipher.update(buffer))
|
||||||
}
|
}
|
||||||
output.write(cipher.doFinal(inputStream.readBytes()))
|
output.write(cipher.doFinal(inputStream.readBytes()))
|
||||||
output.toString()
|
output.toByteArray().also {
|
||||||
|
output.clear()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteKeyCbz() {
|
fun deleteKeyCbz() {
|
||||||
keyStore.deleteEntry(ALIAS_CBZ)
|
keyStore.deleteEntry(AliasCbz)
|
||||||
generateKey(ALIAS_CBZ)
|
generateKey(AliasCbz)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun encryptCbz(password: String): String {
|
fun encryptCbz(password: String): String {
|
||||||
return encrypt(password, encryptionCipherCbz)
|
return encrypt(password.toByteArray(), encryptionCipherCbz)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDecryptedPasswordCbz(): CharArray {
|
fun getDecryptedPasswordCbz(): CharArray {
|
||||||
val encryptedPassword = securityPreferences.cbzPassword().get()
|
val encryptedPassword = securityPreferences.cbzPassword().get()
|
||||||
if (encryptedPassword.isBlank()) error("This archive is encrypted please set a password")
|
if (encryptedPassword.isBlank()) error("This archive is encrypted please set a password")
|
||||||
|
|
||||||
return decrypt(encryptedPassword, ALIAS_CBZ).toCharArray()
|
val cbzBytes = decrypt(encryptedPassword, AliasCbz)
|
||||||
|
return Charsets.UTF_8.decode(ByteBuffer.wrap(cbzBytes)).array()
|
||||||
|
.also {
|
||||||
|
cbzBytes.fill('#'.code.toByte())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateAndEncryptSqlPw() {
|
private fun generateAndEncryptSqlPw() {
|
||||||
val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
|
val charPool: List<Char> = ('a'..'z') + ('A'..'Z') + ('0'..'9')
|
||||||
val password = (1..SQL_PASSWORD_LENGTH).map {
|
val passwordArray = CharArray(SqlPasswordLength)
|
||||||
charPool[SecureRandom().nextInt(charPool.size)]
|
for (i in 0..<SqlPasswordLength) {
|
||||||
}.joinToString("", transform = { it.toString() })
|
passwordArray[i] = charPool[SecureRandom().nextInt(charPool.size)]
|
||||||
securityPreferences.sqlPassword().set(encrypt(password, encryptionCipherSql))
|
}
|
||||||
|
val passwordBuffer = Charsets.UTF_8.encode(CharBuffer.wrap(passwordArray))
|
||||||
|
val passwordBytes = ByteArray(passwordBuffer.limit())
|
||||||
|
passwordBuffer.get(passwordBytes)
|
||||||
|
securityPreferences.sqlPassword().set(encrypt(passwordBytes, encryptionCipherSql))
|
||||||
|
.also {
|
||||||
|
passwordArray.fill('#')
|
||||||
|
passwordBuffer.array().fill('#'.code.toByte())
|
||||||
|
passwordBytes.fill('#'.code.toByte())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDecryptedPasswordSql(): ByteArray {
|
fun getDecryptedPasswordSql(): ByteArray {
|
||||||
if (securityPreferences.sqlPassword().get().isBlank()) generateAndEncryptSqlPw()
|
if (securityPreferences.sqlPassword().get().isBlank()) generateAndEncryptSqlPw()
|
||||||
return decrypt(securityPreferences.sqlPassword().get(), ALIAS_SQL).toByteArray()
|
return decrypt(securityPreferences.sqlPassword().get(), AliasSql)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isPasswordSet(): Boolean {
|
fun isPasswordSet(): Boolean {
|
||||||
@ -202,18 +219,24 @@ object CbzCrypto {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val BUFFER_SIZE = 2048
|
private const val BufferSize = 2048
|
||||||
private const val KEY_SIZE = 256
|
private const val KeySize = 256
|
||||||
private const val IV_SIZE = 16
|
private const val IvSize = 16
|
||||||
|
|
||||||
private const val ALGORITHM = KeyProperties.KEY_ALGORITHM_AES
|
private const val Algorithm = KeyProperties.KEY_ALGORITHM_AES
|
||||||
private const val BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC
|
private const val BlockMode = KeyProperties.BLOCK_MODE_CBC
|
||||||
private const val PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7
|
private const val Padding = KeyProperties.ENCRYPTION_PADDING_PKCS7
|
||||||
private const val CRYPTO_SETTINGS = "$ALGORITHM/$BLOCK_MODE/$PADDING"
|
private const val CryptoSettings = "$Algorithm/$BlockMode/$Padding"
|
||||||
|
|
||||||
private const val KEYSTORE = "AndroidKeyStore"
|
private const val Keystore = "AndroidKeyStore"
|
||||||
private const val ALIAS_CBZ = "cbzPw"
|
private const val AliasCbz = "cbzPw"
|
||||||
private const val ALIAS_SQL = "sqlPw"
|
private const val AliasSql = "sqlPw"
|
||||||
|
|
||||||
private const val SQL_PASSWORD_LENGTH = 32
|
private const val SqlPasswordLength = 32
|
||||||
|
|
||||||
|
private class ByteArrayOutputStreamPassword : ByteArrayOutputStream() {
|
||||||
|
fun clear() {
|
||||||
|
this.buf.fill('#'.code.toByte())
|
||||||
|
}
|
||||||
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
|
Loading…
x
Reference in New Issue
Block a user