Главная
remove check_box_outline_blank close

Глава 4. Android-терминал: Интерфейс без памяти

2026-02-23

Аннотация В данной главе рассматривается техническая реализация Android-клиента для аппаратно-программного комплекса шифрования GuardianT. В отличие от классических мессенджеров, приложение функционирует в режиме «терминала», делегируя операции с приватными ключами внешнему аппаратному модулю (ESP8266/ESP32). Мы подробно разберем стек технологий, включая протоколы обнаружения устройств, встроенные HTTP-серверы и гибридную криптографию.

4.1. Введение: Смартфон как «Недоверенная среда»

Современные мобильные операционные системы (Android/iOS) обеспечивают удобство, но не приватность. Система имеет доступ к буферу обмена, клавиатурному вводу и содержимому экрана.

Проблема: Если ОС скомпрометирована, программное шифрование бесполезно — данные будут перехвачены до момента шифрования. Решение: Архитектура GuardianT выносит криптографическое ядро в физически изолированное устройство. Android-приложение становится лишь интерфейсом ввода-вывода (I/O), не хранящим секретов.

4.2. Архитектура приложения

Приложение написано на языке Kotlin с использованием паттерна MVVM (Model-View-ViewModel). Это позволяет отделить логику работы с данными от пользовательского интерфейса.

Стек технологий:

  1. Kotlin & Coroutines: Для асинхронной работы (сеть, I/O).
  2. ViewBinding: Для безопасного доступа к элементам интерфейса.
  3. NanoHTTPD: Встраиваемый легковесный веб-сервер.
  4. Java Cryptography Architecture (JCA): Для локального шифрования файлов.

4.3. Техническая реализация: Шаг за шагом

4.3.1. Поиск оборудования: Протокол UDP Discovery

Аппаратный ключ (ESP8266) при подключении к Wi-Fi получает динамический IP-адрес. Чтобы приложение могло найти ключ без ручного ввода IP, мы используем протокол UDP Broadcast.

Как это работает: Приложение «кричит» в сеть специальную фразу. Все устройства слышат её, но отвечает только то, которое знает пароль.

Реализация (Kotlin): Мы используем класс DatagramSocket. Важно выполнять это в отдельном потоке (Thread или Coroutine), так как Android запрещает сетевые операции в главном потоке.

/* Файл: EspDiscovery.kt */
fun discoverDevice(onFound: (String) -> Unit) {
    Thread {
        try {
            // 1. Создаем сокет для отправки
            val socket = DatagramSocket()
            socket.broadcast = true // Разрешаем широковещание

            val message = "DISCOVER_GARDIANT".toByteArray()
            
            // 2. Отправляем пакет на адрес 255.255.255.255
            // Это специальный адрес: "всем в этой сети"
            val packet = DatagramPacket(
                message, message.size,
                InetAddress.getByName("255.255.255.255"), 4211
            )
            socket.send(packet)

            // 3. Ждем ответ
            val buffer = ByteArray(1024)
            val responsePacket = DatagramPacket(buffer, buffer.size)
            socket.soTimeout = 2000 // Ждем не более 2 секунд
            
            socket.receive(responsePacket) // Блокирующее ожидание
            
            val responseData = String(responsePacket.data, 0, responsePacket.length)
            if (responseData.contains("GARDIANT_HERE")) {
                // Устройство найдено! Извлекаем его IP
                val deviceIp = responsePacket.address.hostAddress
                onFound(deviceIp)
            }
            socket.close()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }.start()
}

4.3.2. Обратная связь: Встроенный HTTP-сервер (NanoHTTPD)

Для первичной настройки (передачи пароля от Wi-Fi на устройство) мы меняем роли. Телефон становится сервером, а ESP8266 — клиентом. Для этого используется библиотека NanoHTTPD.

Зачем это нужно? Это позволяет передать сложные настройки (SSID, пароль, URL сервера) на устройство через простой HTTP GET запрос, который устройство делает к телефону.

Реализация:

/* Файл: ProvisioningServer.kt */
// Наследуемся от NanoHTTPD
class ProvisioningServer(port: Int) : NanoHTTPD(port) {

    override fun serve(session: IHTTPSession): Response {
        // Проверяем, какой URL запросило устройство
        if (session.uri == "/config") {
            // Формируем JSON с настройками
            val jsonConfig = """
                {
                  "wifi_ssid": "MyHomeNetwork",
                  "wifi_pass": "SecretPassword123",
                  "server_url": "https://chat.my-server.com"
                }
            """.trimIndent()
            
            return newFixedLengthResponse(
                Response.Status.OK, 
                "application/json", 
                jsonConfig
            )
        }
        return newFixedLengthResponse("Not Found")
    }
}

4.3.3. Гибридная криптография: AES-256

Аппаратный ключ (ESP8266) имеет мало памяти, поэтому он шифрует только текстовые сообщения. Большие файлы (фото, документы) шифруются на телефоне, но ключ шифрования генерируется случайно для каждого файла и никогда не сохраняется.

Мы используем алгоритм AES в режиме CBC (Cipher Block Chaining).

Важный нюанс: Для каждого файла обязательно генерируется уникальный вектор инициализации (IV). Если зашифровать один и тот же файл одним ключом, но разными IV — результат будет абсолютно разным. Это защищает от статистического анализа.

/* Файл: CryptoUtils.kt */
object CryptoUtils {
    fun encryptFile(data: ByteArray, secretKey: String): ByteArray {
        // 1. Подготовка ключа
        val keyBytes = secretKey.toByteArray().copyOf(32) // 256 бит
        val keySpec = SecretKeySpec(keyBytes, "AES")

        // 2. Генерация случайного IV (16 байт)
        val iv = ByteArray(16)
        SecureRandom().nextBytes(iv)
        val ivSpec = IvParameterSpec(iv)

        // 3. Настройка шифра
        val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
        cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec)

        // 4. Шифрование
        val encryptedData = cipher.doFinal(data)

        // 5. Упаковка: Сначала кладем IV, потом данные
        // Получатель считает первые 16 байт как IV
        return iv + encryptedData
    }
}

4.3.4. Взаимодействие с API (HTTP Client)

Приложение общается с аппаратным ключом через REST API. Мы используем стандартный HttpURLConnection для обеспечения прозрачности и отсутствия лишних зависимостей.

/* Файл: HardwareClient.kt */
fun sendTextToHardware(ip: String, text: String) {
    Thread {
        val url = URL("http://$ip/encrypt?msg=$text")
        with(url.openConnection() as HttpURLConnection) {
            requestMethod = "GET"
            
            // Читаем ответ от устройства (это будет зашифрованный блоб)
            inputStream.bufferedReader().use {
                val encryptedBlob = it.readText()
                // Далее этот блоб отправляется на внешний сервер
                sendToServer(encryptedBlob)
            }
        }
    }.start()
}

4.4. Безопасность пользовательского интерфейса (UI Security)

Даже самая надежная криптография бессильна, если вредоносное приложение сделает скриншот вашего экрана. Android предоставляет механизм защиты FLAG_SECURE.

Реализация: В методе onCreate главной активности (MainActivity) мы устанавливаем флаг, который запрещает системе и другим приложениям видеть содержимое окна.

/* Файл: MainActivity.kt */
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // Блокировка скриншотов и предпросмотра в меню "Недавние"
    window.setFlags(
        WindowManager.LayoutParams.FLAG_SECURE,
        WindowManager.LayoutParams.FLAG_SECURE
    )

    // Инициализация UI через ViewBinding
    binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)
}

Очистка буфера обмена

Приложение автоматически очищает буфер обмена при сворачивании, чтобы скопированные сообщения не остались в памяти телефона.

4.4. Работа с сетью

Приложение общается с сервером через REST API.

interface GuardianApiService {
    // Отправка зашифрованного пакета
    @POST("/drop")
    suspend fun dropMessage(@Body packet: EncryptedPacket): Response<DropResult>

    // Получение сообщений
    @GET("/pickup")
    suspend fun pickupMessages(@Query("device_id") deviceId: String): Response<List<EncryptedPacket>>
}

Обратите внимание: в API нет методов "login" или "register" в привычном понимании. Аутентификация происходит на уровне подписи пакетов аппаратным ключом.

Итог

Android-приложение GuardianT — это лишь удобный пульт управления. Если вы потеряете телефон, вы не потеряете свою личность, так как ключи остались в аппаратном модуле.

arrow_back Назад к дневнику
home integration_instructions description security design_services person contacts
dark_mode