# OWASP Top 10 For Flutter – M10: Insufficient Cryptography in Flutter & Dart

Welcome to the final article in our deep dive into the OWASP Mobile Top 10 for Flutter developers.

In earlier parts, we tackled:

* [M1: Mastering Credential Security in Flutter](https://docs.talsec.app/appsec-articles/articles/owasp-top-10-for-flutter-m1-mastering-credential-security-in-flutter)
* [M2: Inadequate Supply Chain Security in Flutter](https://docs.talsec.app/appsec-articles/articles/owasp-top-10-for-flutter-m2-inadequate-supply-chain-security-in-flutter)
* [M3: Insecure Authentication and Authorization in Flutter](https://docs.talsec.app/appsec-articles/articles/owasp-top-10-for-flutter-m3-insecure-authentication-and-authorization-in-flutter)
* [M4: Insufficient Input/Output Validation in Flutter](https://docs.talsec.app/appsec-articles/articles/owasp-top-10-for-flutter-m4-insufficient-input-output-validation-in-flutter)
* [M5: Insecure Communication for Flutter and Dart](https://docs.talsec.app/appsec-articles/articles/owasp-top-10-for-flutter-m5-insecure-communication-for-flutter-and-dart)
* [M6: Inadequate Privacy Controls in Flutter & Dart](https://docs.talsec.app/appsec-articles/articles/owasp-top-10-for-flutter-m6-inadequate-privacy-controls-in-flutter-and-dart)
* [M7: Insufficient Binary Protection in Flutter & Dart](https://docs.talsec.app/appsec-articles/articles/owasp-top-10-for-flutter-m7-insufficient-binary-protection-in-flutter-and-dart)
* [M8: Security Misconfiguration in Flutter & Dart](https://docs.talsec.app/appsec-articles/articles/owasp-top-10-for-flutter-m8-security-misconfiguration-in-flutter-and-dart)
* [M9: Insecure Data Storage in Flutter & Dart](https://docs.talsec.app/appsec-articles/articles/owasp-top-10-for-flutter-m9-insecure-data-storage-in-flutter-and-dart)

Each is a critical piece of the mobile security puzzle.

In this tenth and final article, we focus on [**M10: Insufficient Cryptography**](https://owasp.org/www-project-mobile-top-10/2023-risks/m10-insufficient-cryptography.html).

<figure><img src="https://1548930415-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNjTFXsqCLQ3RU2oA2uHC%2Fuploads%2FivF8V9F8tKDhCCOiNOzv%2FOWASP_M10.png?alt=media&#x26;token=ef61b0ac-525c-4b31-97c3-208b7988325e" alt=""><figcaption></figcaption></figure>

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><p><strong>Majid Hajian</strong><br>Azure &#x26; AI advocate <a href="https://x.com/Microsoft">@Microsoft</a><br>Dart &#x26; Flutter community leader<br>Organizer <a href="https://x.com/FlutterVikings">@FlutterVikings</a><br>Author of <a href="http://flutterengineering.io/">http://flutterengineering.io</a></p><p><a href="https://x.com/mhadaily">https://x.com/mhadaily</a></p></td><td><a href="https://1548930415-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FNjTFXsqCLQ3RU2oA2uHC%2Fuploads%2FmRKoYoDQD83DI0FHR0DS%2FAyTDH3m6_400x400.jpg?alt=media&#x26;token=02b44bd2-4d28-42d7-8952-6f0633f7ec10">AyTDH3m6_400x400.jpg</a></td></tr></tbody></table>

Let me tell you about the scariest code review I’ve ever done.

A few years ago, I was asked to review a financial app. It handled millions of dollars in transactions. The team used encryption everywhere. They had secure storage. They’d thought about authentication.

But their crypto implementation used DES. The key was hardcoded. It was literally the word `password` padded to 8 bytes.

DES was considered insecure by the late 90s. [NIST officially withdrew it in 2005](https://csrc.nist.gov/news/2005/withdrawal-of-fips-46-3-fips-74-and-fips-81). This was in 2021.

The developers weren’t incompetent. They were talented engineers. They simply hadn’t kept up with cryptographic best practices. They copied code from a Stack Overflow answer from 2008. They assumed it was fine.

That’s why this final article exists.

[**OWASP M10: Insufficient Cryptography**](https://owasp.org/www-project-mobile-top-10/2023-risks/m10-insufficient-cryptography.html) covers:

* weak algorithms
* poor key management
* improper implementations
* insecure random number generation

Let’s get into it.

{% hint style="success" icon="book-blank" %}
**Source code:** All code examples from this article are available as a runnable Flutter project on GitHub:

[flutterengineering\_examples — M10 Insufficient Cryptography](https://github.com/mhadaily/flutterengineering_examples/tree/main/OWASP_Top_10_Flutter/m10_insufficient_cryptography)
{% endhint %}

### Understanding the Threat Landscape

Before we get into code, it helps to know who breaks cryptographic systems. It also helps to know how.

![](https://t9012551331.p.clickup-attachments.com/t9012551331/5d26f7ed-5ee8-4b67-bcac-ba2adc3ff46e/cryptographic-security-pillars-flowchart-dark.webp)

#### Who’s Interested in Breaking Your Crypto?

Crypto issues attract motivated attackers. Breaking encryption exposes everything it was meant to protect.

| Who                      | Motivation                   | Attack methods                                     |
| ------------------------ | ---------------------------- | -------------------------------------------------- |
| **Nation-state groups**  | Espionage, mass surveillance | Advanced cryptanalysis, quantum computing research |
| **Cybercriminals**       | Financial theft, ransomware  | Brute force, known-plaintext attacks               |
| **Malicious insiders**   | Data exfiltration            | Key theft, algorithm manipulation                  |
| **Competitors**          | Industrial espionage         | Decryption of trade secrets                        |
| **Security researchers** | Finding vulnerabilities      | Protocol analysis, side-channel attacks            |

#### How Cryptographic Systems Get Broken

OWASP rates exploitability as **AVERAGE**. It’s harder than SQL injection. But it’s devastating when it works.

![](https://t9012551331.p.clickup-attachments.com/t9012551331/bb8e84c7-6cd0-47ff-94ca-4b19f30a56b8/cryptographic-attack-vectors-flowchart-dark.webp)

Attackers rarely break modern algorithms directly. They exploit implementation mistakes.

Common examples:

* predictable random numbers
* reused nonces
* hardcoded keys
* deprecated algorithms

#### Common Weaknesses in Flutter Apps

Here are the cryptographic weaknesses I see most often in Flutter apps:

1. **Weak or deprecated algorithms**: MD5, SHA-1, DES, 3DES, RC4
2. **Insufficient key lengths**: AES-128 when AES-256 is required, RSA < 2048 bits
3. **Hardcoded keys**: keys embedded in source
4. **Predictable IVs**: reused IVs or sequential IVs
5. **Insecure random generation**: using `Random()` instead of `Random.secure()`
6. **Missing authenticated encryption**: AES-CBC without HMAC
7. **Poor password hashing**: SHA-256 instead of bcrypt / Argon2id
8. **Improper key storage**: SharedPreferences or plain files

Each one is an opportunity for attackers. Let’s avoid them.

### Algorithm Selection Guide

Cryptographic standards exist for a reason. Don’t reinvent this per project.

#### Symmetric Encryption

Use this for most “encrypt data” cases.

| Algorithm                | Key size          | Use case                           | Status        |
| ------------------------ | ----------------- | ---------------------------------- | ------------- |
| **AES-256-GCM**          | 256-bit           | General purpose, authenticated     | ✅ Recommended |
| **ChaCha20-Poly1305**    | 256-bit           | Mobile/embedded, software-friendly | ✅ Recommended |
| **AES-256-CBC + HMAC**   | 256-bit + MAC key | Legacy compatibility               | ⚠️ Acceptable |
| AES-128-GCM              | 128-bit           | Performance critical               | ⚠️ Acceptable |
| AES-ECB                  | Any               | **Never use**                      | ❌ Insecure    |
| DES, 3DES, RC4, Blowfish | Various           | **Never use**                      | ❌ Deprecated  |

My default is **AES-256-GCM**. It’s fast on modern devices. It’s widely supported. It gives integrity and confidentiality.

**ChaCha20-Poly1305** is an excellent alternative. It can outperform AES on devices without AES hardware.

#### Asymmetric Encryption

Use this for key exchange and digital signatures.

| Algorithm                | Key size    | Use case                    | Status         |
| ------------------------ | ----------- | --------------------------- | -------------- |
| **RSA-OAEP**             | ≥ 2048-bit  | Key wrapping, encryption    | ✅ Recommended  |
| **ECDH (P-256 / P-384)** | 256/384-bit | Key agreement               | ✅ Recommended  |
| **X25519**               | 256-bit     | Modern key agreement        | ✅ Recommended  |
| **Ed25519**              | 256-bit     | Modern signatures           | ✅ Recommended  |
| **ECDSA (P-256)**        | 256-bit     | Signatures                  | ✅ Recommended  |
| RSA-PKCS1v1.5            | Any         | Avoid (padding oracle risk) | ⚠️ Legacy only |
| RSA < 2048-bit           | < 2048-bit  | Never use                   | ❌ Insecure     |

For new projects, I lean toward X25519 and Ed25519. RSA-OAEP with 2048+ bits is still fine.

#### Hashing and Key Derivation

Password hashing is not general-purpose hashing.

| Algorithm             | Use case                          | Status        |
| --------------------- | --------------------------------- | ------------- |
| **Argon2id**          | Password hashing                  | ✅ Best        |
| **bcrypt**            | Password hashing                  | ✅ Recommended |
| **scrypt**            | Password hashing                  | ✅ Recommended |
| **PBKDF2-SHA256**     | Key derivation (310k+ iterations) | ⚠️ Acceptable |
| **HKDF-SHA256**       | Key expansion from strong key     | ✅ Recommended |
| **SHA-256 / SHA-512** | Integrity checks, HMAC            | ✅ Recommended |
| SHA-1                 | Avoid for security                | ❌ Deprecated  |
| MD5                   | Never use                         | ❌ Broken      |

Argon2id, bcrypt, and scrypt are deliberately slow. That’s a feature.

### Secure Implementation in Flutter

#### Setting Up Cryptography Packages

Add these to `pubspec.yaml`:

```yaml
dependencies:
  cryptography: ^2.9.0
  cryptography_flutter: ^2.3.4
  encrypt: ^5.0.3
  flutter_secure_storage: ^10.0.0
```

The `cryptography` package is my default. It’s well maintained. It supports modern algorithms.

#### Symmetric Encryption with AES-GCM

This is a complete example using AES-256-GCM and secure key storage.

```dart
// ✅ SECURE: AES-256-GCM encryption with proper key management
import 'dart:convert';
import 'dart:typed_data';
import 'package:cryptography/cryptography.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class SecureEncryptionService {
  final _secureStorage = const FlutterSecureStorage();
  static const _keyAlias = 'encryption_master_key';

  final _algorithm = AesGcm.with256bits();

  Future<void> generateAndStoreKey() async {
    final secretKey = await _algorithm.newSecretKey();
    final keyBytes = await secretKey.extractBytes();

    await _secureStorage.write(
      key: _keyAlias,
      value: base64Encode(keyBytes),
    );
  }

  Future<SecretKey?> _getSecretKey() async {
    final keyBase64 = await _secureStorage.read(key: _keyAlias);
    if (keyBase64 == null) return null;

    final keyBytes = base64Decode(keyBase64);
    return SecretKey(keyBytes);
  }

  Future<String> encrypt(String plaintext) async {
    final secretKey = await _getSecretKey();
    if (secretKey == null) {
      throw StateError('Encryption key not found. Call generateAndStoreKey() first.');
    }

    final plaintextBytes = utf8.encode(plaintext);

    final secretBox = await _algorithm.encrypt(
      plaintextBytes,
      secretKey: secretKey,
    );

    return base64Encode(secretBox.concatenation());
  }

  Future<String> decrypt(String ciphertext) async {
    final secretKey = await _getSecretKey();
    if (secretKey == null) {
      throw StateError('Encryption key not found.');
    }

    try {
      final combined = base64Decode(ciphertext);

      final secretBox = SecretBox.fromConcatenation(
        combined,
        nonceLength: 12,
        macLength: 16,
      );

      final plaintextBytes = await _algorithm.decrypt(
        secretBox,
        secretKey: secretKey,
      );

      return utf8.decode(plaintextBytes);
    } on SecretBoxAuthenticationError {
      throw SecurityException('Data integrity check failed - possible tampering');
    } catch (e) {
      throw SecurityException('Decryption failed: $e');
    }
  }

  Future<void> deleteKey() async {
    await _secureStorage.delete(key: _keyAlias);
  }
}

class SecurityException implements Exception {
  final String message;
  SecurityException(this.message);

  @override
  String toString() => 'SecurityException: $message';
}
```

#### Using ChaCha20-Poly1305

```dart
// ✅ SECURE: ChaCha20-Poly1305 authenticated encryption
import 'package:cryptography/cryptography.dart';

class ChaChaEncryptionService {
  final _algorithm = Chacha20.poly1305Aead();

  Future<SecretBox> encrypt(
    List<int> plaintext,
    SecretKey secretKey, {
    List<int>? aad,
  }) async {
    return _algorithm.encrypt(
      plaintext,
      secretKey: secretKey,
      aad: aad ?? [],
    );
  }

  Future<List<int>> decrypt(
    SecretBox secretBox,
    SecretKey secretKey, {
    List<int>? aad,
  }) async {
    return _algorithm.decrypt(
      secretBox,
      secretKey: secretKey,
      aad: aad ?? [],
    );
  }

  Future<SecretKey> generateKey() async {
    return _algorithm.newSecretKey();
  }
}
```

#### Common Mistakes to Avoid

```dart
// ❌ INSECURE: Common cryptographic mistakes
import 'dart:convert';
import 'dart:math';
import 'package:crypto/crypto.dart';
import 'package:encrypt/encrypt.dart' as encrypt;

class InsecureCrypto {
  static const _hardcodedKey = 'MySecretKey12345MySecretKey12345';

  String generateWeakKey() {
    final random = Random();
    return List.generate(32, (_) => random.nextInt(256))
        .map((b) => b.toRadixString(16).padLeft(2, '0'))
        .join();
  }

  String encryptECB(String plaintext) {
    final key = encrypt.Key.fromUtf8(_hardcodedKey);
    final encrypter = encrypt.Encrypter(
      encrypt.AES(key, mode: encrypt.AESMode.ecb),
    );
    return encrypter.encrypt(plaintext).base64;
  }

  static final _staticIV = encrypt.IV.fromUtf8('1234567890123456');

  String encryptWithStaticIV(String plaintext) {
    final key = encrypt.Key.fromUtf8(_hardcodedKey);
    final encrypter = encrypt.Encrypter(encrypt.AES(key));
    return encrypter.encrypt(plaintext, iv: _staticIV).base64;
  }

  String encryptCBCNoAuth(String plaintext) {
    final key = encrypt.Key.fromUtf8(_hardcodedKey);
    final iv = encrypt.IV.fromSecureRandom(16);
    final encrypter = encrypt.Encrypter(
      encrypt.AES(key, mode: encrypt.AESMode.cbc),
    );
    return encrypter.encrypt(plaintext, iv: iv).base64;
  }

  String hashPasswordWeak(String password) {
    final bytes = utf8.encode(password);
    final hash = sha256.convert(bytes);
    return hash.toString();
  }
}
```

### Password Hashing Best Practices

Rules:

1. Never store passwords in plaintext.
2. Never use fast hashes for passwords (MD5/SHA-1/SHA-256).
3. Always use a unique salt per password.
4. Use Argon2id, bcrypt, or scrypt.

#### Argon2id (Recommended)

```dart
// ✅ SECURE: Argon2id password hashing
import 'dart:convert';
import 'dart:typed_data';
import 'package:cryptography/cryptography.dart';

class SecurePasswordHasher {
  final _argon2id = Argon2id(
    parallelism: 4,
    memory: 65536,
    iterations: 3,
    hashLength: 32,
  );

  Future<String> hashPassword(String password) async {
    final salt = SecretKeyData.random(length: 16);
    final saltBytes = await salt.extractBytes();

    final secretKey = await _argon2id.deriveKeyFromPassword(
      password: password,
      nonce: saltBytes,
    );
    final hashBytes = await secretKey.extractBytes();

    return jsonEncode({
      'algorithm': 'argon2id',
      'salt': base64Encode(saltBytes),
      'hash': base64Encode(hashBytes),
      'params': {
        'parallelism': _argon2id.parallelism,
        'memory': _argon2id.memory,
        'iterations': _argon2id.iterations,
      },
    });
  }

  Future<bool> verifyPassword(String password, String storedHash) async {
    try {
      final data = jsonDecode(storedHash) as Map<String, dynamic>;

      if (data['algorithm'] != 'argon2id') {
        throw StateError('Unknown hash algorithm');
      }

      final salt = base64Decode(data['salt'] as String);
      final expectedHash = base64Decode(data['hash'] as String);
      final params = data['params'] as Map<String, dynamic>;

      final argon2id = Argon2id(
        parallelism: params['parallelism'] as int,
        memory: params['memory'] as int,
        iterations: params['iterations'] as int,
        hashLength: expectedHash.length,
      );

      final secretKey = await argon2id.deriveKeyFromPassword(
        password: password,
        nonce: salt,
      );
      final computedHash = await secretKey.extractBytes();

      return _constantTimeEquals(
        Uint8List.fromList(computedHash),
        Uint8List.fromList(expectedHash),
      );
    } catch (_) {
      return false;
    }
  }

  bool needsRehash(String storedHash) {
    try {
      final data = jsonDecode(storedHash) as Map<String, dynamic>;
      final params = data['params'] as Map<String, dynamic>;

      return (params['memory'] as int) < _argon2id.memory ||
          (params['iterations'] as int) < _argon2id.iterations ||
          (params['parallelism'] as int) < _argon2id.parallelism;
    } catch (_) {
      return true;
    }
  }

  bool _constantTimeEquals(Uint8List a, Uint8List b) {
    if (a.length != b.length) return false;

    var result = 0;
    for (var i = 0; i < a.length; i++) {
      result |= a[i] ^ b[i];
    }
    return result == 0;
  }
}
```

#### PBKDF2 (When Argon2 Isn’t Available)

OWASP guidance (as of 2023) recommends **310,000+** iterations for PBKDF2-SHA256.

```dart
// ✅ SECURE: PBKDF2 password hashing (when Argon2id not available)
import 'dart:convert';
import 'dart:typed_data';
import 'package:cryptography/cryptography.dart';

class Pbkdf2PasswordHasher {
  final _pbkdf2 = Pbkdf2(
    macAlgorithm: Hmac.sha256(),
    iterations: 310000,
    bits: 256,
  );

  Future<String> hashPassword(String password) async {
    final salt = SecretKeyData.random(length: 16);
    final saltBytes = await salt.extractBytes();

    final secretKey = await _pbkdf2.deriveKeyFromPassword(
      password: password,
      nonce: saltBytes,
    );
    final hashBytes = await secretKey.extractBytes();

    return jsonEncode({
      'algorithm': 'pbkdf2-sha256',
      'iterations': _pbkdf2.iterations,
      'salt': base64Encode(saltBytes),
      'hash': base64Encode(hashBytes),
    });
  }

  Future<bool> verifyPassword(String password, String storedHash) async {
    try {
      final data = jsonDecode(storedHash) as Map<String, dynamic>;
      final salt = base64Decode(data['salt'] as String);
      final expectedHash = base64Decode(data['hash'] as String);
      final iterations = data['iterations'] as int;

      final pbkdf2 = Pbkdf2(
        macAlgorithm: Hmac.sha256(),
        iterations: iterations,
        bits: expectedHash.length * 8,
      );

      final secretKey = await pbkdf2.deriveKeyFromPassword(
        password: password,
        nonce: salt,
      );
      final computedHash = await secretKey.extractBytes();

      return _constantTimeEquals(
        Uint8List.fromList(computedHash),
        Uint8List.fromList(expectedHash),
      );
    } catch (_) {
      return false;
    }
  }

  bool _constantTimeEquals(Uint8List a, Uint8List b) {
    if (a.length != b.length) return false;
    var result = 0;
    for (var i = 0; i < a.length; i++) {
      result |= a[i] ^ b[i];
    }
    return result == 0;
  }
}
```

### Key Management Best Practices

Crypto is only as strong as your key management.

#### Key Generation

Never use `Random()` for security. Use `Random.secure()` or let the crypto library generate keys.

```dart
// ✅ SECURE: Proper key generation
import 'dart:convert';
import 'dart:math';
import 'package:cryptography/cryptography.dart';

class SecureKeyGenerator {
  Future<SecretKey> generateAesKey() async {
    final algorithm = AesGcm.with256bits();
    return algorithm.newSecretKey();
  }

  List<int> generateSecureRandomBytes(int length) {
    final random = Random.secure();
    return List.generate(length, (_) => random.nextInt(256));
  }

  Future<SimpleKeyPair> generateKeyPair() async {
    final algorithm = X25519();
    return algorithm.newKeyPair();
  }

  Future<SimpleKeyPair> generateSigningKeyPair() async {
    final algorithm = Ed25519();
    return algorithm.newKeyPair();
  }

  Future<SecretKey> deriveKeyFromPassword(
    String password,
    List<int> salt,
  ) async {
    final argon2id = Argon2id(
      parallelism: 4,
      memory: 65536,
      iterations: 3,
      hashLength: 32,
    );

    return argon2id.deriveKeyFromPassword(
      password: password,
      nonce: salt,
    );
  }

  Future<List<SecretKey>> deriveSubKeys(
    SecretKey masterKey,
    int numberOfKeys,
  ) async {
    final hkdf = Hkdf(
      hmac: Hmac.sha256(),
      outputLength: 32,
    );

    final keys = <SecretKey>[];
    for (var i = 0; i < numberOfKeys; i++) {
      final derivedKey = await hkdf.deriveKey(
        secretKey: masterKey,
        info: utf8.encode('subkey-$i'),
        nonce: [],
      );
      keys.add(derivedKey);
    }

    return keys;
  }
}
```

#### Key Storage Architecture

Don’t store keys in plaintext preferences or files.

* **Android:** Keystore-backed secure storage
* **iOS:** Keychain-backed secure storage

![](https://t9012551331.p.clickup-attachments.com/t9012551331/1fad245a-d5cb-4e3b-8b8f-095f62f2cb1c/key-management-architecture-flowchart-dark.webp)

### Digital Signatures

Use signatures to verify authenticity and detect tampering.

```dart
// ✅ SECURE: Digital signature implementation
import 'dart:convert';
import 'package:cryptography/cryptography.dart';

class SignatureService {
  final _algorithm = Ed25519();

  Future<SimpleKeyPair> generateKeyPair() async {
    return _algorithm.newKeyPair();
  }

  Future<Signature> sign(
    List<int> data,
    SimpleKeyPair keyPair,
  ) async {
    return _algorithm.sign(data, keyPair: keyPair);
  }

  Future<bool> verify(
    List<int> data,
    Signature signature,
  ) async {
    return _algorithm.verify(data, signature: signature);
  }

  Future<String> signMessage(
    String message,
    SimpleKeyPair keyPair,
  ) async {
    final messageBytes = utf8.encode(message);
    final signature = await sign(messageBytes, keyPair);
    return base64Encode(signature.bytes);
  }

  Future<bool> verifyMessage(
    String message,
    String signatureBase64,
    SimplePublicKey publicKey,
  ) async {
    try {
      final messageBytes = utf8.encode(message);
      final signatureBytes = base64Decode(signatureBase64);

      final signature = Signature(
        signatureBytes,
        publicKey: publicKey,
      );

      return verify(messageBytes, signature);
    } catch (_) {
      return false;
    }
  }
}
```

### Secure Random Number Generation

`Random()` is for UI and games. It’s not a CSPRNG.

Use `Random.secure()` for security.

```dart
// ✅ SECURE: Proper random number generation
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';

class SecureRandomGenerator {
  static Uint8List generateBytes(int length) {
    final random = Random.secure();
    return Uint8List.fromList(
      List.generate(length, (_) => random.nextInt(256)),
    );
  }

  static String generateString(int byteLength) {
    final bytes = generateBytes(byteLength);
    return base64UrlEncode(bytes).replaceAll('=', '');
  }

  static int generateInt(int max) {
    final random = Random.secure();
    return random.nextInt(max);
  }

  static Uint8List generateAesGcmNonce() {
    return generateBytes(12);
  }

  static Uint8List generateSalt({int length = 16}) {
    return generateBytes(length);
  }
}
```

### Security Checklist

Use this to audit your crypto.

#### Cryptographic Implementation Audit

* Algorithm selection
  * ☐ Use AES-256-GCM or ChaCha20-Poly1305.
  * ☐ Avoid MD5 / SHA-1 / DES / 3DES.
  * ☐ Use AEAD where possible.
* Key management
  * ☐ Generate keys with a CSPRNG.
  * ☐ Store keys in Keychain / Keystore.
  * ☐ No hardcoded keys.
  * ☐ Rotation plan exists.
* IV / nonce handling
  * ☐ Unique nonce per encryption.
  * ☐ Correct nonce length (12 bytes for GCM).
* Password hashing
  * ☐ Argon2id, bcrypt, or scrypt.
  * ☐ Unique salt per password.
  * ☐ Work factor is current.
* Implementation security
  * ☐ Vetted crypto libraries.
  * ☐ Constant-time comparisons for secrets.
  * ☐ Error handling avoids oracles.

#### Quick Reference: Minimum Parameters

* **AES-GCM:** 256-bit key, 96-bit nonce, 128-bit tag
* **ChaCha20-Poly1305:** 256-bit key, 96-bit nonce
* **RSA:** 2048-bit minimum (3072-bit recommended)
* **Argon2id:** 64MB memory, 3 iterations, 4 parallelism
* **PBKDF2-SHA256:** 310,000+ iterations
* **Salt:** 16+ bytes

### Conclusion

Cryptography is powerful. It’s also easy to get wrong.

The difference between secure and insecure crypto is usually details:

* `Random.secure()` vs `Random()`
* AES-GCM vs AES-ECB
* Argon2id vs SHA-256 for passwords

If you follow the patterns here and stick to well-tested libraries, you’ll avoid most real-world crypto failures.

### Resources

* [OWASP Mobile Top 10 — M10: Insufficient Cryptography](https://owasp.org/www-project-mobile-top-10/2023-risks/m10-insufficient-cryptography.html)
* [OWASP MASTG — Testing Cryptography](https://mas.owasp.org/MASTG/General/0x04g-Testing-Cryptography/)
* [OWASP Cryptographic Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html)
* [OWASP Password Storage Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html)
* [cryptography (Dart/Flutter)](https://pub.dev/packages/cryptography)
* [cryptography\_flutter](https://pub.dev/packages/cryptography_flutter)
* [NIST Cryptographic Standards](https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines)
* [Argon2id (RFC 9106)](https://datatracker.ietf.org/doc/html/rfc9106)
* [Dart `Random.secure()` API](https://api.dart.dev/stable/dart-math/Random/Random.secure.html)
* [flutter\_secure\_storage](https://pub.dev/packages/flutter_secure_storage)
* [Libsodium — Good cryptography practices](https://doc.libsodium.org/)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.talsec.app/appsec-articles/articles/owasp-top-10-for-flutter-m10-insufficient-cryptography-in-flutter-and-dart.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
