Skip to content

Secure IPC

TLS-Based Secure Communication

TLS (Transport Layer Security) is the standard for encrypting data in transit. It provides end-to-end encryption, integrity, and authentication, making it ideal for most use cases.

Implementation Steps

  1. Use HTTPS for Remote APIs:
    Flutter’s http package supports HTTPS out of the box. Always use HTTPS endpoints for remote communication, and enable certificate pinning to prevent MITM attacks.
import 'package:http/http.dart' as http;
import 'dart:convert';

Future<void> sendSecureRequest() async {
  final response = await http.post(
    Uri.parse('https://api.example.com/secure-endpoint'),
    headers: {'Content-Type': 'application/json'},
    body: jsonEncode({'data': 'sensitive'}),
  );
  print(response.body);
}
  1. TLS Server for Native Modules:
    For direct communication between Flutter and native code (e.g., via sockets), implement a TLS server in native code. For Android, use SSLServerSocketFactory from javax.net.ssl to create a secure server socket. For iOS, use URLSession with TLS configuration.

Android (Kotlin):

import javax.net.ssl.*
import java.security.KeyStore
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.net.ServerSocket

fun startTlsServer() {
  val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
  val certificateFactory = CertificateFactory.getInstance("X.509")
  val certificate = certificateFactory.generateCertificate(java.io.FileInputStream("server.crt"))
  keyStore.setCertificateEntry("server", certificate)

  val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultType())
  trustManagerFactory.init(keyStore)

  val sslContext = SSLContext.getInstance("TLS")
  sslContext.init(null, trustManagerFactory.trustManagers, null)

  val sslServerSocketFactory = sslContext.serverSocketFactory
  val serverSocket = ServerSocket(8080).apply { 
    sslServerSocketFactory.createServerSocket(this).use { socket ->
      // Handle client connections
    }
  }
}

iOS (Swift):

import Foundation
import Alamofire

let session = URLSession(configuration: .default, delegate: nil, delegateQueue: nil)
let request = URLRequest(url: URL(string: "https://api.example.com/secure-endpoint")!)
let task = session.dataTask(with: request) { data, response, error in
  // Handle response
}
task.resume()

  1. Certificate Pinning:
    Pin the server’s certificate to avoid trusting untrusted CAs. Use flutter_secure_storage for Flutter and native trust stores for Android/iOS.

Flutter (Dart):

import 'package:flutter_secure_storage/flutter_secure_storage.dart'

final storage = FlutterSecureStorage();
await storage.write(key: 'server_cert', value: base64Cert);

Android (Java):

TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setCertificateEntry("server", certificate);
tmf.init(ks);

iOS (Swift):

let trust = SecTrustCreateWithCertificates(certificateData as CFData, nil, nil)
var trustResult: SecTrustResultType = .invalid
SecTrustEvaluate(trust!, &trustResult)
if trustResult == .proceed {
  // Proceed with connection
}


Custom Protocol Implementation

Custom protocols are useful for low-level communication or when TLS is not feasible (e.g., peer-to-peer or legacy systems). They require careful design to ensure encryption, authentication, and integrity.

Key Components

  • Encryption: Use AES-256 in GCM mode for symmetric encryption. Flutter’s pointycastle library supports AES.
  • Authentication: Add HMAC signatures to verify data integrity.
  • Key Exchange: Use Diffie-Hellman (DH) or pre-shared keys (PSK) for secure key exchange.

Example: AES-256 with HMAC

Flutter (Dart):

import 'package:pointycastle/export.dart';
import 'package:crypto/crypto.dart';

String encryptData(String plainText, String key) {
  final keyBytes = key.codeUnits;
  final iv = IVParameter(IVParameter.generate(16));
  final cipher = AESBlockCipher(
    KeyParameter(keyBytes),
    iv,
  );
  final encrypted = List<int>.filled(16, 0);
  cipher.processBlock(plainText.codeUnits, 0, encrypted, 0);
  return base64Encode(encrypted);
}

Native (Android Kotlin):

val cipher = Cipher.getInstance("AES/GCM/NoPadding")
val keySpec = SecretKeySpec(key.toByteArray(), "AES")
cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv)
val encrypted = cipher.doFinal(data.toByteArray())


Best Practices for Secure IPC

  1. Prioritize TLS: Use TLS for most IPC scenarios due to its maturity and built-in protections against common attacks.
  2. Validate Inputs: Sanitize all data to prevent injection attacks, even when encrypted.
  3. Secure Key Management: Store cryptographic keys in secure storage (e.g., Android’s Keystore, iOS’s Keychain) and avoid hardcoding them.
  4. Use Forward Secrecy: For custom protocols, implement ephemeral key exchange (e.g., DH) to protect past communications.
  5. Monitor for Errors: Handle exceptions gracefully and avoid exposing sensitive information in logs or error messages.

Key takeaways

  • TLS is the default choice for secure IPC due to its robust encryption and authentication features.
  • Custom protocols require careful design, including encryption, HMAC, and secure key exchange.
  • Always validate inputs and use secure storage for cryptographic keys.
  • Certificate pinning is critical for TLS-based communication to prevent MITM attacks.
  • Avoid plaintext data in all IPC channels; encrypt payloads before transmission.