iOS Swift Android Kotlin Crittografia & Privacy ⏱️ 14 min lettura

Crittografia Locale SQLite con SQLCipher su iOS & Android

Come blindare i dati sensibili a riposo su smartphone: crittografia a livello di pagina AES-256 con SQLCipher, ancoraggio della chiave nel Secure Enclave / Android Keystore e prevenzione dell'estrazione forense.

🏥 Caso di Studio Reale: Questa architettura crittografica a zero-cloud è la stessa implementata nel nostro progetto open-source RosettaEdge per archiviare referti medici, valori ematici LOINC e cartelle cliniche senza dipendere da server esterni. La dimostrazione formale e i benchmark sono documentati nella monografia tecnica su Zenodo.

1. Il Falso Senso di Sicurezza di SQLite

Molte app archiviano dati biometrici o finanziari in un normale file database.sqlite confidando nella sandbox del sistema operativo (iOS Data Protection o Android app isolation).

Questo approccio fallisce contro:

  • Dispositivi con Jailbreak o permessi di Root.
  • Backup locali non cifrati (es. backup iTunes/Finder via USB o strumenti ADB).
  • Analisi forense su chip di memoria fisica (chip-off analysis).

SQLCipher risolve il problema alla radice: ogni singola pagina da 4096 byte del database viene cifrata con standard AES-256 (CBC o GCM) con HMAC per pagina per prevenire manomissioni.

2. Generazione e Custodia Hardware della Chiave

Non salvare mai la passphrase come stringa nel codice sorgente! Genera una chiave simmetrica casuale da 256 bit e salvala nel chip di sicurezza hardware del dispositivo.

Implementazione in Swift (iOS Keychain & Secure Enclave):

import Foundation
import Security

public final class KeychainKeyManager {
    private static let keyTag = "dev.alexmaina.rosettaedge.vaultkey"

    public static func getOrCreateMasterKey() throws -> Data {
        // 1. Tenta il recupero da Keychain
        let query: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: keyTag,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne
        ]
        var item: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &item)
        if status == errSecSuccess, let keyData = item as? Data {
            return keyData
        }

        // 2. Se non esiste, genera 32 byte crittograficamente sicuri (256 bit)
        var keyData = Data(count: 32)
        let result = keyData.withUnsafeMutableBytes {
            SecRandomCopyBytes(kSecRandomDefault, 32, $0.baseAddress!)
        }
        guard result == errSecSuccess else { throw KeyStoreError.generationFailed }

        // 3. Salva nel Keychain con protezione al primo sblocco del device
        let addQuery: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrAccount as String: keyTag,
            kSecValueData as String: keyData,
            kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
        ]
        SecItemAdd(addQuery as CFDictionary, nil)
        return keyData
    }
}

3. Apertura del Database Criptato con SQLCipher (Swift)

Subito dopo aver aperto la connessione SQLite, esegui il comando PRAGMA key prima di qualsiasi altra operazione:

import SQLCipher

func openEncryptedVault(path: String, keyData: Data) throws -> OpaquePointer? {
    var db: OpaquePointer?
    if sqlite3_open(path, &db) == SQLITE_OK {
        // Applica la chiave crittografica grezza (raw hex)
        let hexKey = keyData.map { String(format: "%02hhx", $0) }.joined()
        let pragmaQuery = "PRAGMA key = \"x'\(hexKey)'\";"
        
        if sqlite3_exec(db, pragmaQuery, nil, nil, nil) == SQLITE_OK {
            // Verifica che la chiave sia valida eseguendo una lettura test
            if sqlite3_exec(db, "SELECT count(*) FROM sqlite_master;", nil, nil, nil) == SQLITE_OK {
                print("Vault aperto e decifrato con successo!")
                return db
            }
        }
    }
    throw VaultError.invalidKeyOrCorrupted
}

4. Integrazione con Android Room (Kotlin)

Su Android, SQLCipher si integra nativamente con Room Database tramite SupportFactory:

import android.content.Context
import androidx.room.Room
import net.sqlcipher.database.SupportFactory
import net.sqlcipher.database.SQLiteDatabase

fun buildEncryptedRoomDatabase(context: Context, passphraseBytes: ByteArray): AppDatabase {
    // 1. Inizializza le librerie binarie C native di SQLCipher
    SQLiteDatabase.loadLibs(context)
    
    // 2. Crea la factory crittografica
    val factory = SupportFactory(passphraseBytes)

    // 3. Costruisci il database Room cifrato
    return Room.databaseBuilder(
        context.applicationContext,
        AppDatabase::class.java,
        "rosetta_vault.db"
    )
    .openHelperFactory(factory)
    .build()
}

❓ Domande Frequenti (FAQ Tecniche)

Qual è l'impatto di performance di SQLCipher rispetto a SQLite?

Sui moderni processori con accelerazione crittografica hardware integrata (AES-NI / NEON), l'overhead è compreso tra il 3% e l'8%. È impercettibile per l'utente anche con query complesse su decine di migliaia di record.

Cosa succede se l'utente perde la chiave di cifratura?

I dati rimangono matematicamente irrecuperabili. Per questo motivo la chiave deve essere gestita in modo sicuro tramite il Keychain di sistema protetto da biometria (Face ID / Impronta).

SQLCipher cifra anche gli indici e i file temporanei?

Sì, ogni pagina di dati, indice, albero B-Tree e file Write-Ahead Log (WAL) viene cifrata integralmente prima di toccare la memoria flash del telefono.