====== Seguridad y Privacidad en PAE ======
===== Principios de seguridad =====
- **Privacy-first**: datos sensibles nunca salen del dispositivo
- **Encryption**: datos en reposo encriptados
- **Offline-first**: no depender de conectividad para funciones críticas
- **Minimal permissions**: solo solicitar permisos necesarios
- **Defense in depth**: múltiples capas de protección
===== Gestión de permisos =====
==== Android Manifest ====
==== Solicitud de permisos en runtime ====
// Machine/src/main/java/co/ada/paemachine/permissions/PermissionManager.kt
class PermissionManager(private val activity: Activity) {
fun requestCameraPermission() {
val permission = Manifest.permission.CAMERA
if (ContextCompat.checkSelfPermission(activity, permission)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
activity,
arrayOf(permission),
CAMERA_PERMISSION_CODE
)
}
}
fun requestLocationPermission() {
val permission = Manifest.permission.ACCESS_FINE_LOCATION
if (ContextCompat.checkSelfPermission(activity, permission)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
activity,
arrayOf(permission),
LOCATION_PERMISSION_CODE
)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array,
grantResults: IntArray
) {
when (requestCode) {
CAMERA_PERMISSION_CODE -> {
if ((grantResults.isNotEmpty() &&
grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
onCameraPermissionGranted()
} else {
onCameraPermissionDenied()
}
}
}
}
}
===== Encriptación de datos =====
==== Encriptación en reposo (EncryptedSharedPreferences) ====
// Credenciales sensibles
val encryptedSharedPrefs = EncryptedSharedPreferences.create(
context,
"secret_shared_prefs",
MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build(),
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
// Guardar
encryptedSharedPrefs.edit().apply {
putString("api_token", "secreto")
putString("user_id", "12345")
apply()
}
// Leer
val token = encryptedSharedPrefs.getString("api_token", null)
val userId = encryptedSharedPrefs.getString("user_id", null)
==== Encriptación en base de datos (EncryptedFile) ====
// Fotos de entregas y beneficiarios
val file = File(context.filesDir, "delivery_photo.jpg")
val encryptedFile = EncryptedFile.Builder(
context,
file,
MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build(),
EncryptedFile.FileEncryptionScheme.AES256_GCM_HKDF_4KB
).build()
// Escribir
encryptedFile.openFileOutput().use { outputStream ->
val photoBytes = bitmap.toByteArray()
outputStream.write(photoBytes)
}
// Leer
val decryptedBytes = encryptedFile.openFileInput().use { inputStream ->
inputStream.readBytes()
}
val bitmap = BitmapFactory.decodeByteArray(decryptedBytes, 0, decryptedBytes.size)
==== SQLite con encriptación (SQLCipher) ====
// Database encriptada con contraseña
val dbPassword = "secret_database_passphrase"
val database = Room.databaseBuilder(
context,
DeliveryDatabase::class.java,
"delivery_db.db"
)
.openHelperFactory(
SQLCipherOpenHelperFactory { SQLiteDatabase.loadLibs(context); dbPassword }
)
.build()
===== Almacenamiento seguro de credenciales =====
==== Keystore de Android ====
// Generar y guardar clave privada
val keyGenParameterSpec = KeyGenParameterSpec.Builder(
"delivery_key",
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
).apply {
setBlockModes(KeyProperties.BLOCK_MODE_GCM)
setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
setKeySize(256)
setUserAuthenticationRequired(true)
setUserAuthenticationValidityDurationSeconds(30)
}.build()
val keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore"
)
keyGenerator.init(keyGenParameterSpec)
val secretKey = keyGenerator.generateKey()
// Recuperar clave
val keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
val key = keyStore.getKey("delivery_key", null) as SecretKey
===== Comunicación segura (HTTPS + Certificate Pinning) =====
==== Configuración de HTTP client ====
// MachineEndpoints.kt
val httpClient = HttpClientConfig()
.socketTimeout(20000)
.connectTimeout(20000)
.setHOST("backend.api.com")
.setProtocol("https")
.build()
val httpClientFactory = FuelManager()
httpClientFactory.apply {
basePath = "https://backend.api.com"
client.setDefault(
FuelManager.getInstance().client.setConfigBlock()
)
}
==== Certificate Pinning ====
val certificatePinner = CertificatePinner.Builder()
.add(
"backend.api.com",
"sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
)
.add(
"backend.api.com",
"sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="
)
.build()
val okHttpClient = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
===== Sanitización de datos =====
==== Prevención de SQL Injection ====
// MALO ❌
val query = "SELECT * FROM Beneficiary WHERE name = '$inputName'"
// BUENO ✅
val beneficiaries = repository.beneficiaries
.where { name = inputName }
.toList()
==== Prevención de XSS (si hay web component) ====
// MALO ❌
webView.loadData("", "text/html", "utf-8")
// BUENO ✅
val escapedHtml = Html.escapeHtml(userInput)
webView.loadData(escapedHtml, "text/html; charset=utf-8", null)
===== Logging seguro =====
==== NO registrar datos sensibles ====
// MALO ❌
Log.d("API", "User token: $token")
Log.d("DB", "Password: $password")
Log.d("Sync", "Delivery data: $delivery")
// BUENO ✅
Log.d("API", "User authenticated successfully")
Log.d("DB", "Beneficiary record accessed")
Log.d("Sync", "Delivery synced - ID: ${delivery.id}")
==== Logger personalizado que oculta datos sensibles ====
object SecureLogger {
fun d(tag: String, message: String) {
if (BuildConfig.DEBUG) {
Log.d(tag, message)
}
}
fun logDelivery(delivery: Delivery) {
d("SYNC", "Delivery ID: ${delivery.id}, Weight: ${delivery.weight}")
// nunca loguear servicioDeliveryId o fotos
}
fun logToken(token: String) {
d("AUTH", "Token length: ${token.length}, starts with: ${token.take(5)}")
}
}
===== Protección contra tampering =====
==== Code Obfuscation (ProGuard/R8) ====
# proguard-rules.pro
-keep class co.ada.contract.models.** {
public (...);
}
-keepclassmembers class co.ada.domain.state.** {
public ;
}
-repackageclasses
-dontoptimize
==== Detección de root/jailbreak ====
// Machine/src/main/java/co/ada/paemachine/security/
object SecurityChecker {
fun isDeviceSecure(context: Context): Boolean {
return !isRooted() && !isDebuggerConnected()
}
private fun isRooted(): Boolean {
val paths = arrayOf(
"/system/app/Superuser.apk",
"/sbin/su",
"/system/bin/su",
"/system/xbin/su",
"/data/local/xbin/su"
)
return paths.any { File(it).exists() }
}
private fun isDebuggerConnected(): Boolean {
return Debug.isDebuggerConnected()
}
}
===== Data Privacy =====
==== Cumplimiento GDPR ====
=== 1. Consentimiento ===
// Solicitar consentimiento inicial
if (!hasUserConsent()) {
showPrivacyDialog {
onAccepted = {
savePrivacyConsent(true)
}
onDeclined = {
exitApplication()
}
}
}
=== 2. Transparencia ===
PRIVACY_POLICY.MD
- Qué datos se recopilan
- Cómo se almacenan
- Cuánto tiempo se retienen
- Derechos del usuario
=== 3. Derecho al olvido ===
object DataDeletion {
fun deleteUserData(userId: Long): Boolean {
// Borrar de base de datos
repository.deleteUserDeliveries(userId)
repository.deleteUserBeneficiaries(userId)
repository.deleteUserShifts(userId)
// Borrar fotos
deleteUserPhotoFiles(userId)
// Borrar caché
deleteUserCache(userId)
return true
}
}
===== Network Security =====
==== Network Security Configuration ====
backend.api.com
.
==== AndroidManifest.xml ====
===== Verificación de integridad =====
==== Google Play Integrity API ====
// Machine/src/main/java/co/ada/paemachine/security/IntegrityChecker.kt
class IntegrityChecker(private val context: Context) {
private val integrityClient = IntegrityClientFactory.create(context)
suspend fun verifyAppIntegrity(): Boolean = withContext(Dispatchers.IO) {
return@withContext try {
val response = integrityClient.requestIntegrityToken(
IntegrityTokenRequest.Builder()
.setCloudProjectNumber(PROJECT_NUMBER)
.build()
).await()
val tokenResponse = response.token()
// Enviar tokenResponse a backend para verificar
true
} catch (e: Exception) {
false
}
}
}
===== Checklist de seguridad ante deployment =====
* [ ] No hay secrets en código
* [ ] Permisos mínimos solicitados
* [ ] Encriptación habilitada para datos sensibles
* [ ] ProGuard/R8 habilitado en release
* [ ] HTTPS en todas las comunicaciones
* [ ] Certificate pinning configurado
* [ ] Logs no contienen datos sensibles
* [ ] Verifica OWASP Top 10 Mobile
* [ ] Pruebas de penetración completadas
* [ ] Privacy policy actualizado
* [ ] Code review completado
* [ ] Security patches aplicados
===== Referencias =====
* [[https://android-developers.googleblog.com/|Android Security & Privacy Year in Review]]
* [[https://owasp.org/www-project-mobile-top-10/|OWASP Mobile Top 10]]
* [[https://developer.android.com/docs/quality-guidelines/build-for-safety|Google Play Security Best Practices]]
* [[https://developer.android.com/training/articles/security-overview|Android Developers: Security]]