[{"data":1,"prerenderedAt":27},["ShallowReactive",2],{"articolo-crittografia-in-flutter-cifrare-i-dati-con-aes-gcm-hashing-e-derivazione-delle-chiavi":3,"comments-article-crittografia-in-flutter-cifrare-i-dati-con-aes-gcm-hashing-e-derivazione-delle-chiavi":26},{"id":4,"title":5,"slug":6,"excerpt":7,"body":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"status":16,"published_at":17,"meta_title":18,"meta_description":19,"category":20,"author":24},141,"Crittografia in Flutter: cifrare i dati con AES-GCM, hashing e derivazione delle chiavi","crittografia-in-flutter-cifrare-i-dati-con-aes-gcm-hashing-e-derivazione-delle-chiavi","Salvare un token in chiaro non basta più. Guida pratica alla crittografia lato client in Flutter con package:cryptography: AES-GCM, PBKDF2\u002FArgon2id, HMAC, scambio chiavi X25519 e accelerazione nativa, con gli errori da evitare.","## Perché parlare di crittografia in un'app Flutter\n\nMolte app mobili gestiscono dati che non dovrebbero mai finire in chiaro su disco: note private, documenti, chiavi API, cache di risposte contenenti dati personali, backup esportabili. Il `flutter_secure_storage` è perfetto per pochi byte (un refresh token, un PIN), perché si appoggia a Keychain e Keystore, ma non è pensato per cifrare un database da 40 MB o un file allegato.\n\nIn questi casi serve crittografia applicativa vera e propria: generare o derivare una chiave, cifrare i dati con un algoritmo autenticato e conservare la chiave nel posto giusto. In questa guida vediamo come farlo in Dart con `package:cryptography`, la libreria più completa e mantenuta dell'ecosistema, con accelerazione nativa opzionale.\n\n> Nota importante: la crittografia lato client **non sostituisce** HTTPS\u002FTLS. Protegge i dati *a riposo* sul dispositivo e, nel caso dell'end-to-end encryption, li protegge anche dal server. Il canale di rete va comunque protetto separatamente.\n\n## Setup\n\n```yaml\ndependencies:\n  cryptography: ^2.7.0\n  cryptography_flutter: ^2.3.2   # opzionale: implementazioni native\n  crypto: ^3.0.0                 # hash semplici (SHA-256, HMAC)\n  flutter_secure_storage: ^9.2.2\n```\n\nIl pacchetto `cryptography` è scritto in Dart puro e funziona ovunque (mobile, desktop, web). `cryptography_flutter` sostituisce alcune implementazioni con quelle di sistema (Android `javax.crypto`, iOS CryptoKit), con guadagni di performance notevoli su file grandi:\n\n```dart\nimport 'package:cryptography_flutter\u002Fcryptography_flutter.dart';\n\nvoid main() {\n  \u002F\u002F Registra le implementazioni native dove disponibili,\n  \u002F\u002F con fallback automatico su Dart puro.\n  FlutterCryptography.enable();\n  runApp(const MyApp());\n}\n```\n\n## Hash: quando servono (e quando no)\n\nUn hash non è crittografia: è una funzione a senso unico utile per verificare l'integrità di un file o costruire una cache key stabile.\n\n```dart\nimport 'dart:convert';\nimport 'package:crypto\u002Fcrypto.dart';\n\nString sha256Hex(String input) =>\n    sha256.convert(utf8.encode(input)).toString();\n\nString hmacSha256(String message, List\u003Cint> key) =>\n    Hmac(sha256, key).convert(utf8.encode(message)).toString();\n```\n\nRegole pratiche:\n\n- **MD5 e SHA-1**: solo per checksum non di sicurezza (es. nome file in cache). Mai per firme o password.\n- **SHA-256 + HMAC**: ottimo per firmare payload o verificare webhook.\n- **Password**: mai SHA-256 secco. Serve una KDF lenta (vedi sotto).\n- Per confrontare due hash segreti usate un confronto a tempo costante, non `==` su stringhe.\n\n## Cifratura simmetrica con AES-GCM\n\nAES-GCM è la scelta di default moderna: cifra **e** autentica, quindi un ciphertext manomesso fa fallire la decifratura invece di restituire spazzatura. Evitate AES-CBC senza MAC e soprattutto AES-ECB (che lascia visibili i pattern nei dati).\n\n```dart\nimport 'dart:convert';\nimport 'package:cryptography\u002Fcryptography.dart';\n\nclass CryptoService {\n  final AesGcm _algorithm = AesGcm.with256bits();\n\n  Future\u003CSecretKey> generateKey() => _algorithm.newSecretKey();\n\n  \u002F\u002F\u002F Restituisce nonce + ciphertext + MAC in un unico blob.\n  Future\u003CList\u003Cint>> encrypt(String plainText, SecretKey key) async {\n    final secretBox = await _algorithm.encrypt(\n      utf8.encode(plainText),\n      secretKey: key,\n    );\n    return secretBox.concatenation();\n  }\n\n  Future\u003CString> decrypt(List\u003Cint> blob, SecretKey key) async {\n    final secretBox = SecretBox.fromConcatenation(\n      blob,\n      nonceLength: _algorithm.nonceLength, \u002F\u002F 12 byte\n      macLength: 16,\n    );\n    final clear = await _algorithm.decrypt(secretBox, secretKey: key);\n    return utf8.decode(clear);\n  }\n}\n```\n\nPunti chiave:\n\n- Il **nonce** (IV) viene generato automaticamente a ogni chiamata ed è casuale. Non riutilizzatelo mai con la stessa chiave: con GCM il riuso del nonce compromette la sicurezza in modo catastrofico.\n- Il nonce **non è segreto**: va salvato insieme al ciphertext, ed è esattamente quello che fa `concatenation()`.\n- Se la decifratura fallisce viene lanciata una `SecretBoxAuthenticationError`: gestitela esplicitamente, è il segnale che i dati sono stati alterati o che la chiave è sbagliata.\n\n### Dati associati (AAD)\n\nGCM permette di autenticare metadati non cifrati, utile per legare un blob al suo contesto (per esempio l'id dell'utente o la versione dello schema):\n\n```dart\nfinal box = await _algorithm.encrypt(\n  bytes,\n  secretKey: key,\n  aad: utf8.encode('user:42|v:2'),\n);\n```\n\nIn decifratura dovete passare lo stesso `aad`, altrimenti la verifica fallisce. È un modo elegante per impedire che un record cifrato venga \"spostato\" su un altro utente.\n\n## Dove mettere la chiave\n\nGenerare una chiave è facile; conservarla è il punto difficile. Due strategie.\n\n### 1. Chiave casuale nel secure storage\n\nLa chiave AES viene generata al primo avvio e salvata nel Keychain\u002FKeystore. Il resto dei dati (file, database) è cifrato con quella chiave.\n\n```dart\nimport 'package:flutter_secure_storage\u002Fflutter_secure_storage.dart';\n\nclass KeyStore {\n  static const _storage = FlutterSecureStorage(\n    aOptions: AndroidOptions(encryptedSharedPreferences: true),\n    iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock),\n  );\n  static const _keyName = 'master_key_v1';\n\n  Future\u003CSecretKey> loadOrCreate() async {\n    final stored = await _storage.read(key: _keyName);\n    if (stored != null) {\n      return SecretKey(base64Decode(stored));\n    }\n    final key = await AesGcm.with256bits().newSecretKey();\n    final bytes = await key.extractBytes();\n    await _storage.write(key: _keyName, value: base64Encode(bytes));\n    return key;\n  }\n}\n```\n\nAttenzione all'opzione `accessibility`: con `first_unlock` la chiave è leggibile anche a schermo bloccato (serve se cifrate dati in background), mentre `first_unlock_this_device` evita che finisca nei backup iCloud. Scegliete in base al caso d'uso.\n\n### 2. Chiave derivata da una password\n\nSe volete che nemmeno chi ha accesso fisico al dispositivo possa leggere i dati senza la passphrase dell'utente, la chiave va **derivata** al momento dello sblocco e tenuta solo in memoria.\n\n```dart\nFuture\u003CSecretKey> deriveKey(String password, List\u003Cint> salt) async {\n  final pbkdf2 = Pbkdf2(\n    macAlgorithm: Hmac.sha256(),\n    iterations: 210000, \u002F\u002F allineato alle raccomandazioni OWASP\n    bits: 256,\n  );\n  return pbkdf2.deriveKeyFromPassword(password: password, nonce: salt);\n}\n```\n\nSe volete qualcosa di più resistente agli attacchi con GPU, `package:cryptography` offre anche Argon2id:\n\n```dart\nfinal argon2 = Argon2id(\n  memory: 19 * 1024, \u002F\u002F 19 MB\n  parallelism: 1,\n  iterations: 2,\n  hashLength: 32,\n);\nfinal key = await argon2.deriveKeyFromPassword(\n  password: password,\n  nonce: salt, \u002F\u002F 16 byte casuali, salvati in chiaro\n);\n```\n\nIl **salt** deve essere unico per utente, generato con `Random.secure()` e salvato in chiaro accanto ai dati:\n\n```dart\nimport 'dart:math';\n\nList\u003Cint> randomBytes(int length) {\n  final random = Random.secure();\n  return List\u003Cint>.generate(length, (_) => random.nextInt(256));\n}\n```\n\nNon usate mai `Random()` senza `secure` per materiale crittografico: è un generatore pseudo-casuale prevedibile.\n\n### Non bloccate la UI\n\nLe KDF sono volutamente lente (centinaia di millisecondi per design). Le implementazioni Dart pure sono CPU-bound e bloccano l'isolate principale anche se l'API è `async`. Spostate la derivazione in un isolate:\n\n```dart\nfinal keyBytes = await Isolate.run(() async {\n  final key = await deriveKey(password, salt);\n  return key.extractBytes();\n});\nfinal secretKey = SecretKey(keyBytes);\n```\n\n`Isolate.run` (Dart 2.19+) è la via più diretta; in alternativa `compute` da `package:flutter\u002Ffoundation.dart`.\n\n## Cifrare file di grandi dimensioni\n\nCaricare in memoria un video da 500 MB per cifrarlo è una ricetta per l'OOM. La strategia corretta è dividere il file in chunk (es. 1 MB) e cifrare ogni chunk come un `SecretBox` indipendente, includendo l'indice del chunk nell'AAD per impedire riordinamenti:\n\n```dart\nFuture\u003Cvoid> encryptFile(File source, File dest, SecretKey key) async {\n  final algorithm = AesGcm.with256bits();\n  final sink = dest.openWrite();\n  var index = 0;\n\n  await for (final chunk in source.openRead().transform(_chunker(1 \u003C\u003C 20))) {\n    final box = await algorithm.encrypt(\n      chunk,\n      secretKey: key,\n      aad: utf8.encode('chunk:$index'),\n    );\n    final payload = box.concatenation();\n    \u002F\u002F prefisso con la lunghezza per rileggere i blocchi\n    sink.add((ByteData(4)..setUint32(0, payload.length)).buffer.asUint8List());\n    sink.add(payload);\n    index++;\n  }\n  await sink.close();\n}\n```\n\nSu file grandi l'accelerazione nativa di `cryptography_flutter` fa la differenza: le implementazioni di sistema sono tipicamente di un ordine di grandezza più veloci di AES in Dart puro.\n\n## Crittografia asimmetrica: scambio chiavi e firme\n\nQuando due dispositivi devono condividere dati senza che il server possa leggerli, serve uno scambio di chiavi. X25519 (ECDH su Curve25519) è lo standard de facto:\n\n```dart\nfinal x25519 = X25519();\n\nfinal myKeyPair = await x25519.newKeyPair();\nfinal myPublicKey = await myKeyPair.extractPublicKey(); \u002F\u002F inviabile al server\n\n\u002F\u002F Ricevuta la chiave pubblica dell'altro peer:\nfinal sharedSecret = await x25519.sharedSecretKey(\n  keyPair: myKeyPair,\n  remotePublicKey: peerPublicKey,\n);\n\n\u002F\u002F Il segreto condiviso non va usato direttamente: passatelo per una KDF.\nfinal sessionKey = await Hkdf(hmac: Hmac.sha256(), outputLength: 32)\n    .deriveKey(secretKey: sharedSecret, info: utf8.encode('chat-session-v1'));\n```\n\nPer verificare l'autenticità di un messaggio (e non solo la sua integrità) si usa una firma Ed25519:\n\n```dart\nfinal ed25519 = Ed25519();\nfinal keyPair = await ed25519.newKeyPair();\n\nfinal signature = await ed25519.sign(message, keyPair: keyPair);\nfinal isValid = await ed25519.verify(message, signature: signature);\n```\n\n## Errori comuni da evitare\n\n- **Chiavi hardcoded nel codice**: una stringa in Dart finisce nel binario ed è estraibile in pochi minuti, anche con l'offuscamento. Se la chiave è nell'app, la sicurezza è solo apparente.\n- **AES-ECB o CBC senza autenticazione**: niente integrità, vulnerabile a padding oracle. Usate GCM o ChaCha20-Poly1305 (`Chacha20.poly1305Aead()`), ottimo fallback su dispositivi senza accelerazione AES hardware.\n- **Nonce fisso o incrementale condiviso tra dispositivi**: con GCM è l'errore più grave in assoluto.\n- **Nessuna versione nel formato**: prefissate sempre il blob con un byte di versione, così potrete migrare algoritmo o parametri KDF senza rompere i dati esistenti.\n- **Chiavi nei log o in Crashlytics**: scrivete un `toString()` custom sui modelli che le contengono, o evitate di inserirle in oggetti loggabili.\n- **Dati sensibili nei backup**: su Android valutate `android:allowBackup=\"false\"` o escludete i file cifrati dalle regole di backup.\n\nQuando avete finito di usare materiale segreto in memoria potete liberarlo esplicitamente:\n\n```dart\nfinal data = await secretKey.extract();\n\u002F\u002F ... uso ...\ndata.destroy(); \u002F\u002F azzera i byte, l'oggetto non è più utilizzabile\n```\n\n## Come scegliere\n\n| Esigenza | Soluzione |\n|---|---|\n| Token, PIN, piccoli segreti | `flutter_secure_storage` |\n| File o DB cifrati su disco | AES-GCM + chiave nel secure storage |\n| Dati protetti da passphrase utente | Argon2id\u002FPBKDF2 + AES-GCM, chiave solo in RAM |\n| Integrità di un payload | HMAC-SHA256 |\n| End-to-end tra dispositivi | X25519 + HKDF + AES-GCM, firme Ed25519 |\n\n## Conclusione\n\nIl 90% dei casi reali si risolve con tre mattoncini: una chiave a 256 bit generata o derivata correttamente, AES-GCM per cifrare, e un posto sicuro dove conservare la chiave. Il resto è disciplina: nonce sempre nuovi, formato versionato, nessun segreto nel codice sorgente e KDF fuori dal main isolate.\n\nLa regola d'oro resta quella di sempre: non scrivete primitive crittografiche da zero. Usate `package:cryptography`, abilitate `cryptography_flutter` per le performance e concentrate l'attenzione sul *key management*, che è dove si rompono quasi tutti i sistemi reali.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Farticles\u002F4a342429-38c9-49c3-b9f0-971348e20ecd.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1614064745729-79e39d1b39b2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3OTMxNjAxMTB8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"FlyD","https:\u002F\u002Funsplash.com\u002F@flyd2069","https:\u002F\u002Funsplash.com\u002Fphotos\u002Ftwo-pink-padlock-on-pink-surface-BCUX_cJaZSc",null,"published","2026-10-28T04:01:50+00:00","Crittografia in Flutter: AES-GCM, hash e chiavi sicure","Guida pratica alla crittografia in Flutter: AES-GCM con package:cryptography, PBKDF2 e Argon2id, HMAC, X25519 e gestione sicura delle chiavi.",{"id":21,"name":22,"slug":23},1,"Guide","guide",{"id":21,"name":25},"Flutter Bot",[],1793187034609]