Шифрование данных в мобильных приложениях

Джон Мачов

Перед началом работы

О данном руководстве

Забота о безопасности существует всегда, поэтому в программном обеспечении нужно учитывать необходимость защиты конфиденциальных данных. Мобильные приложения не являются неуязвимыми. Фактически, мобильные устройства и их программное обеспечение имеют особенность, заключающуюся в том, что большинство людей носит свои устройства с собой постоянно.

В данном руководстве представлен обзор шифрования данных в Java 2 Micro Edition J2ME-приложении (Java 2 Micro Edition) (MIDlet). Руководство начинается с краткого введения в шифрование данных. Шифрование - это тема не для слабых духом; данный раздел довольно краток и посвящен, в основном, API шифрования с открытым исходным кодом, написанным Legion of Bouncy Castle. Тема завершается разработкой мидлета (MIDlet), демонстрирующего шифрование и дешифрование текстовых строк.

Одной проблемой, присущей большинству мобильных устройств, является ограниченный объем памяти. Хотя Bouncy Castle и другие библиотеки шифрования предлагают богатую функциональность, существует также ее цена. Как и в большинстве используемых вами библиотеках, только небольшие фрагменты кода обычно нужны вашему приложению. Одним из традиционных способов удаления неиспользуемого кода, одновременно делая приложение более трудным для обратного анализа, является использование Java-обфускатора (obfuscator - компонент, выполняющий процедуру засекречивания class-файлов Java). Я познакомлю вас с обфускатором с открытым исходным кодом - ProGuard. Мы рассмотрим все, начиная с загрузки и установки и заканчивая конфигурированием J2ME Wireless Toolkit для использования ProGuard. В завершающем разделе данного руководства сравниваются размеры JAR-файла засекреченного (obfuscating) мидлета с обычным мидлетом.

Предварительные требования

Для запуска примеров этого руководства вам необходимо следующее программное обеспечение:

  • Java Development Kit (JDK)
  • Wireless Toolkit (WTK)
  • Bouncy Castle
  • ProGuard

Требуемое программное обеспечение

Установка программного обеспечения

Во время работы с данным руководством вы будете использовать четыре программных средства. Начнем с загрузки каждого из необходимых компонентов.

  • The Java Development Kit (JDK): JDK предоставляет компилятор исходных кодов, написанных на языке Java, и программу для создания JAR-файлов (Java Archive). При использовании Wireless Toolkit 2.0 (как в данном руководстве) вам нужно загрузить JDK версии 1.4 или выше. Загрузите JDK версии 1.4.2 (http://java.sun.com/j2se/1.4.2/download.html).
  • The Wireless Toolkit (WTK): Интегрированная среда разработки (IDE) Wireless Toolkit фирмы Sun Microsystems создает J2ME-мидлеты. Загружаемый файл WTK содержит IDE, а также библиотеки, необходимые для создания мидлетов. Загрузите J2ME Wireless Toolkit 2.1 (http://java.sun.com/products).
  • Bouncy Castle - это библиотека шифрования с открытым исходным кодом; существует облегченная версия, подходящая для использования с J2ME. Загрузите lcrypto-j2me-121.zip (http://www.bouncycastle.org/download/lcrypto-j2me-121.zip).
  • ProGuard - это Java-обфускатор class-файлов с открытым исходным кодом. Загрузите ProGuard 2.0 (http://prdownloads.sourceforge.net/proguard/proguard2.0.zip?download).

Установка JDK и Wireless Toolkit

Java Development Kit (JDK)

Для установки JDK используйте документацию по JDK. Вы можете выбрать либо каталог по умолчанию, либо указать другой каталог. Если вы выбрали другой каталог, запомните его. Во время установки Wireless Toolkit программа попытается обнаружить Java Virtual Machine (JVM); если она не сможет найти JVM, появится запрос на ввод пути установки JDK.

The Wireless Toolkit (WTK)
Данное руководство основывается на более ранней статье developerWorks "Разработка мидлетов при помощи Wireless Toolkit" (см. раздел "Ресурсы"), в которой объясняются основы создания мидлетов. Это руководство является отличной отправной точкой, если вы являетесь новичком в Wireless Toolkit.

Wireless Toolkit содержится в одном исполняемом файле. Запустите этот файл для начала процесса установки. Рекомендуется использовать каталог по умолчанию. Однако если вы используете другой каталог, выбранный вами путь не должен содержать пробелы.

В конце каждой темы я рассмотрю, как настроить Wireless toolkit для Bouncy Castle и ProGuard.

Шифрование

Обзор

После выхода MIDP 2.0 поддержка HTTPS теперь требуется во всех реализации MIDP. Однако, даже с выходом новой спецификации, пройдет некоторое время, пока устройства, поддерживающие MIDP 2.0, станут повсеместны. Кроме того, если шифрование данных необходимо вам в самом мобильном устройстве (а не как часть сетевого взаимодействия между устройством и удаленной системой), поддержки HTTPS недостаточно.

В данном разделе рассмотрены некоторые основы того, почему шифрование важно для мобильных приложений, и приведено краткое введение в Bouncy Castle, API для шифрования и дешифрования данных с открытым исходным кодом. Я завершу этот раздел описанием шагов по установке Bouncy Castle, что подготовит нас к следующему разделу, в котором мы напишем мидлет, способный шифровать и дешифровать простые текстовые строки.

Почему важно шифрование

Вместе с распространением и портативностью мобильных устройств возник уникальный набор вопросов по безопасности. Многие люди носят мобильные телефоны. По мере того, как устройства становятся все более умелыми, предлагая доступ к корпоративной электронной почте, базам данных и другой корпоративной информации, вопрос, как защитить эту информацию, становится приоритетным.

Если устройство будет похищено или утеряно, появится риск компрометации конфиденциальных данных. Это относится не только к данным, находящимся на устройстве, но и к приложениям, которые предоставляют доступ к данным на удаленном сервере.

Кроме того, взаимодействие по беспроводной сети с использованием мобильного устройства формирует свой собственный набор присущих ему рисков. Обладая знаниями и оборудованием, можно перехватить данные почти повсюду между вашим устройством и оконечной точкой вашего взаимодействия.

Хотя предметом рассмотрения в данном руководстве является, главным образом, шифрование данных на мобильных устройствах, важно понимать, что это только один шаг в обеспечении конфиденциальности.

Ключи

Так же как обычный ключ позволяет вам закрывать и открывать двери вашего дома, термин ключ (key) при обсуждении шифрования имеет такую же аналогию - закрытие, или шифрование, данных для ограничения доступа, и открытие, или дешифровка, данных для разрешения доступа.

В мире шифрования ключ - это ничто иное, как набор символов или цифр, используемых во время процессов шифрования и дешифрования. Вы узнаете о ключах подробнее при обсуждении шифров.

Шифры

Шифр - это способ преобразования конфиденциальной информации в зашифрованный формат. Для данного обсуждения представляйте шифр ни чем иным, как алгоритмом. Так же как существует большое число алгоритмов сортировки данных в компьютере (пузырьковая сортировка, быстрая сортировка и т.д.), существует много алгоритмов для шифрования и дешифрования данных.

Типы шифров

Шифры могут быть симметричными или асимметричными. Симметричные шифры используют один и тот же ключ для шифрования и дешифрования данных, и часто называются системами с секретным ключом. То есть, значение ключа хранится в секрете двумя участниками - тем, кто шифрует данные, и тем, кто дешифрует их.

В асимметричных шифрах используются два различных ключа: один для шифрования и один для дешифрования. Такие системы называются системами с открытым ключом , что означает общедоступность ключа для шифрования данных. Иначе говоря, каждый имеет доступ к ключу для шифрования информации, но только имеющий закрытый ключ может дешифровать данные.

Алгоритмы

Существует большая путаница, связанная с шифрованием. Это касается и выбора алгоритма, часто называемого механизмом (engine). Существует масса доступных алгоритмов, вот лишь несколько из них:

  • IDEA - International Data Encryption Algorithm
  • DES - Data Encryption Standard
  • AES - Advanced Encryption Standard

Ни один алгоритм не является наилучшим. Некоторые взломаны, тогда как другие были заменены более эффективными алгоритмами. Решение о том, какой алгоритм использовать - это не точная наука. Я бы рекомендовал поработать с данным руководством, для того чтобы узнать, как использовать шифрование в мидлете с применением Bouncy Castle. После освоения основ процесса вы можете исследовать различные алгоритмы и решить, какой является наилучшим для требований вашего приложения.

Обзор Bouncy Castle

Bouncy Castle - это Java API для шифрования и дешифрования данных с открытым исходным кодом. Bouncy Castle является провайдером Java Cryptography Extension (JCE). JCE - это необязательный пакет, обеспечивающий поддержку шифров, ключей и аутентификацию сообщений в Java 2 Platform, Standard Edition (J2SE). По существу, провайдер предлагает один или несколько алгоритмов для шифрования и дешифрования данных.

На сегодняшний день Bouncy Castle поддерживает более 20 механизмов. Это звучит хорошо, поскольку обеспечивается большая гибкость в способе шифрования данных. С другой стороны, приходится платить объемом кода: Bouncy Castle занимает свыше 900 KB. К счастью, только часть API используется в один и тот же момент времени. И, как вы увидите дальше в этом руководстве, я удалю все классы, методы и поля, которые не нужны для использования шифратора.

Установка Bouncy Castle

Для добавления поддержки Bouncy Castle во все приложения, разрабатываемые в WTK, разархивируйте файл midp_classes.zip в каталоге C:\WTK21\apps\lib. Если вы хотите ограничить использование библиотеки Bouncy Castle и добавить ее поддержку только для конкретного проекта, разархивируйте ZIP-файл в каталог \lib вашего проекта. Например, если бы вы создали проект EncryptTest, то должны были бы разархивировать файл midp_classes.zip в каталог C:\WTK21\apps\EncryptTest\lib.

После помещения Bouncy ZIP-файла в нужное место, вы можете добавить необходимые выражения import для любых классов, выбранных для использования в вашем мидлете. WTK найдет class-файлы и включит их в ваш пакетированный мидлет.

Резюме по разделу

В данном разделе было рассмотрено шифрование и причины, почему оно является важным при разработке мобильных приложений. Были рассмотрены также некоторые ключевые концепции шифрования, такие как шифры и ключи, и перечислено несколько хорошо известных алгоритмов шифрования. Я завершил раздел обзором Bouncy Castle API с открытым исходным кодом с последующим кратким описанием процесса его установки.

Теперь вы узнаете о том, как использовать Bouncy Castle API в мидлете.

Мидлет EncryptText

Основные этапы шифрования

Существует много алгоритмов, стандартов и процессов для защиты данных. Моя цель заключается не в подробном обсуждении шифрования, а в демонстрации нескольких простых шагов для обеспечения элементарного шифрования в мидлете. Вы должны понимать, что для хорошей защиты ваших приложений вам понадобится намного более трудоемкая и хорошо спланированная работа. Данный обзор приведен просто для того, чтобы вы начали работу, и демонстрирует стандартный способ шифрования информации.

Для шифрования текстовой строки я буду выполнять следующие действия:

  1. Выбрать механизм шифрования и разместить экземпляр этого механизма.
  2. Записать шифруемый текст в байтовый массив.
  3. Инициализировать шифр ключом.
  4. Вызвать механизм шифрования для шифрования данных.

Как вы можете догадаться, дешифрование данных - это аналогичный процесс:

  1. Сохранить дешифруемый текст в байтовый массив.
  2. Инициализировать шифр ключом.
  3. Вызвать механизм для дешифрования данных.

Представляя в памяти эту общую картину, я напишу мидлет, чтобы продемонстрировать использование Bouncy Castle API.

Обзор мидлета: запрос текста

Лучшим способом узнать, как шифровать данные при помощи Bouncy Castle, является создание мидлета для демонстрации основ. Этот пример будет отображать TextBox для ввода пользователем строки для шифрования. Здесь будет три варианта меню: Encrypt, Decrypt и Exit. Далее приведено несколько иллюстраций, которые показывают, как выглядит мидлет в эмуляторе WTK. На рисунке 1 показан мидлет и TextBox , который будет содержать текст для шифрования/дешифрования.

Рисунок 1. Запрос текста

Обзор мидлета: шифрование текста

После ввода текста выберите в меню вариант Encrypt . Текст будет пропущен через шифрующий механизм Bouncy Castle, а затем TextBox будет обновлен зашифрованным текстом. На рисунке 2 показаны эти два шага.

Рисунок 2. Шифрование текста

Обзор мидлета: дешифрование текста

Последним шагом является дешифрование содержимого TextBox обратно в оригинальную строку. На рисунке 3 показан выбор Decrypt из меню вместе с полученным текстом, помещенным назад в TextBox .

Рисунок 3. Дешифрование текста

Создание мидлета

Ниже перечислены шаги, которым я следовал при создании мидлета в WTK:

  1. Создать проект.
  2. Написать исходный код.
  3. Откомпилировать и предварительно проверить код.
  4. Запустить мидлет.

В следующем разделе показано, как начать создание нового проекта.

Создание проекта

  1. Выберите New Project.
  2. Введите название проекта и название класса мидлета, как показано на рисунке 4.
  3. Выберите Create Project.

Рисунок 4. Создание проекта EncryptText

Написание Java-кода

Для данного мидлета имеется один файл с Java-кодом: EncryptText.java.

Скопируйте следующий код EncryptText.java в текстовый редактор:

  /*--------------------------------------------------
  * EncryptText.java
  *
  * Мидлет, демонстрирующий простое шифрование/дешифрование 
  * данных приложения.
  *-------------------------------------------------*/
  import org.bouncycastle.crypto.CryptoException;
  import org.bouncycastle.crypto.engines.IDEAEngine;
  import org.bouncycastle.crypto.params.KeyParameter;
  import org.bouncycastle.crypto.modes.CBCBlockCipher;
  import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
  import org.bouncycastle.util.encoders.Hex;
  import javax.microedition.midlet.*;
  import javax.microedition.lcdui.*;

  public class EncryptText extends MIDlet implements CommandListener
  {
    private Display display;	    // Ссылка на объект Display
    private TextBox tbTest;       // Main textbox
    private Command cmExit;       // Command для выхода
    private Command cmEncrypt;    // Command для шифрования текста
    private Command cmDecrypt;    // Command для дешифрования текста
    private PaddedBufferedBlockCipher cipher = null;  // Шифр
    private String key = "x-392kla%3$*1f";            // Kлюч

    /*--------------------------------------------------
    * Конструктор 
    *-------------------------------------------------*/
    public EncryptText()
    {
      display = Display.getDisplay(this);

      // Создать несколько Command.
      cmExit = new Command("Exit", Command.EXIT, 1);
      cmEncrypt = new Command("Encrypt", Command.SCREEN, 2);
      cmDecrypt = new Command("Decrypt", Command.SCREEN, 3);

      tbTest = new TextBox("Text to encrypt/decrypt", 
                           "IBM developerworks", 250, TextField.ANY);
      tbTest.addCommand(cmExit);
      tbTest.addCommand(cmEncrypt);
      tbTest.addCommand(cmDecrypt);
      tbTest.setCommandListener(this);

      // Создать новый шифр (механизм) для шифрования/дешифрования
      cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new IDEAEngine()));
    }

    public void startApp()
    {
      display.setCurrent(tbTest);
    }

    public void pauseApp()
    {
    }

    public void destroyApp(boolean unconditional)
    {
    }

    /*--------------------------------------------------
    * Обработка событий
    *-------------------------------------------------*/
    public void commandAction(Command c, Displayable s)
    {
      if (c == cmExit)
      {
        destroyApp(false);
        notifyDestroyed();
      }
      else if (c == cmEncrypt)   // Encrypt text
      {
        encryptData();
      }
      else if (c == cmDecrypt)  // Decrypt text
      {
        decryptData();
      }
    }

    /*--------------------------------------------------
    * Зашифровать данные из текстового поля. Поместить данные 
    * назад в текстовое поле по завершении.
    *-------------------------------------------------*/
    private void encryptData()
    {
      // Получить содержимое из текстового поля
      byte[] inBytes = tbTest.getString().getBytes();

      // Инициализировать шифр. 'true' указывает шифрование
      cipher.init(true, new KeyParameter(key.getBytes()));

      // Определить минимальный размер выходного буфера
      byte[] outBytes = new byte[cipher.getOutputSize(inBytes.length)];

      // 'len' - это возвращенная реальная длина
      int len = cipher.processBytes(inBytes, 0, inBytes.length, outBytes, 0);
      try
      {
        // Обработать последний блок в буфере, начиная с позиции 'len' 
        cipher.doFinal(outBytes, len);

        // Обновить текстовое поле новой зашифрованной строкой
        tbTest.setString(new String(Hex.encode(outBytes)));

        // Отладочное сообщение
        System.out.println("encrypted: " + new String(Hex.encode(outBytes)));
      }
      catch(CryptoException e)
      {
        System.out.println("Exception: " + e.toString());
      }
    }

    /*--------------------------------------------------
    * Дешифрование данных в текстовом поле. Помещает данные
    * назад в текстовое поле по завершении.
    *-------------------------------------------------*/
    private void decryptData()
    {
      // Получить текст для дешифрования из текстового поля
      byte[] inBytes = Hex.decode(tbTest.getString().getBytes());

      // Инициализация шифра. 'false' указывает дешифрование
      cipher.init(false, new KeyParameter(key.getBytes()));

      // Определить минимальный размер выходного буфера
      byte[] outBytes = new byte[cipher.getOutputSize(inBytes.length)];

      // 'len' - это возвращенная реальная длина
      int len = cipher.processBytes(inBytes, 0, inBytes.length, outBytes, 0);

      try
      {
        // Обработать последний блок в буфере, начиная с позиции 'len' 
        cipher.doFinal(outBytes, len);

        // Обновить текстовое поле дешифрованной строкой
        tbTest.setString(new String(outBytes).trim());

        System.out.println("decrypted: " + new String(outBytes).trim());
      }
      catch(CryptoException e)
      {
        System.out.println("Exception: " + e.toString());
      }
    }

  }
  

При создании нового проекта WTK создает правильную структуру каталогов за вас. В данном примере WTK создал каталог C:\WTK21\apps\EncryptText и необходимые подкаталоги. Сохраните ваш Java-файл с исходным кодом как EncryptText.java в каталог src, как показано на рисунке 5 (обратите внимание на то, что названия диска и каталога WTK могут отличаться в зависимости от того, куда вы установили этот пакет инструментальных программ).

Рисунок 5. Сохранение кода EncrpytText

Примечание: В следующих разделах Java-код будет показан детально.

Сохранение, компилирование и предварительная проверка

Выберите Build для компилирования, предварительной проверки и пакетирования мидлета, как показано на рисунке 6.

Рисунок 6. Компоновка проекта EncryptText

Выберите Run для запуска Application Manager.

Запуск мидлета EncryptText

Для запуска мидлета EncryptText выберите Launch, как показано на рисунке 7.

Рисунок 7. Запуск мидлета EncryptText

Изображение в левой части рисунка 8 показывает оригинальный текст, который будет шифроваться, и вариант меню для начала процесса шифрования. Изображение справа показывает текст в его зашифрованной форме.

Рисунок 8. Шифрование текста

В левой части рисунка 9 находится зашифрованный текст и вариант меню для начала процесса дешифрования. Изображение справа показывает результаты после завершения дешифрования.

Рисунок 9. Дешифрование текста

Обзор кода: конструктор

Я начинаю с определения команд для обработки событий и TextBox для ввода текста пользователем. Последним шагом является создание экземпляра шифра. Хотя существует много механизмов шифрования, доступных в Bouncy Castle, для этого мидлета я выбрал механизм IDEA (International Data Encryption Algorithm).

  public EncryptText()
  {
    display = Display.getDisplay(this);

    // Создать несколько Command.
    cmExit = new Command("Exit", Command.EXIT, 1);
    cmEncrypt = new Command("Encrypt", Command.SCREEN, 2);
    cmDecrypt = new Command("Decrypt", Command.SCREEN, 3);

    tbTest = new TextBox("Text to encrypt/decrypt", 
                         "IBM developerworks", 250, TextField.ANY);
    tbTest.addCommand(cmExit);
    tbTest.addCommand(cmEncrypt);
    tbTest.addCommand(cmDecrypt);
    tbTest.setCommandListener(this);

    // Создать новый шифр (механизм) для шифрования/дешифрования
    cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new IDEAEngine()));

    // Создать ключ
    key = new String("x-392kla%3$*1f");
  }
  

Процесс шифрования, который я выбрал, будет обрабатывать блоки текста, то есть, последовательность битов шифруется как один блок, а ключ применяется к блоку в целом. CBCBlockCipher() запрашивает, чтобы текущий шифруемый блок был объединен с предыдущим зашифрованным блоком. С таким подходом одинаковые шаблоны в разных сообщениях будут зашифрованы в различный текст.

PaddedBufferedBlockCipher() - это класс-конверт, обеспечивающий выравнивание текста, что необходимо для подгонки выбранного вами для шифрования текста, размер которого не равен размеру блока, обрабатываемого алгоритмом шифрования. Например, механизм IDEA обрабатывает 8-байтные блоки.

Для шифрования и дешифрования текста будет использоваться шифр (cipher), что я вскоре продемонстрирую.

Последний вызов в конструкторе - распределение памяти для ключа. IDEA является симметричным шифром, это означает, что для шифрования и дешифрования данных используется один и тот же ключ.

Обзор кода: event handling

Для этого мидлета существует небольшая обработка событий для управления. Я вызываю соответствующий метод на основе выбранного пользователем варианта.

      public void commandAction(Command c, Displayable s)
      {
        if (c == cmExit)
        {
          destroyApp(false);
          notifyDestroyed();
        }
        else if (c == cmEncrypt)   // Шифровать текст
        {
          encryptData();
        }
        else if (c == cmDecrypt)  // Дешифровать текст
        {
          decryptData();
        }
      }
    

Обзор кода: шифрование данных

Я начал процесс шифрования с записи введенных пользователем символов (в текстовом поле) в байтовый массив inBytes . Следующим шагом является инициализация шифра, который был распределен ранее. Первым параметром метода cipher.init() является булево значение, указывающее, выполняется ли шифрование данных. В зависимости от значения этого параметра механизм будет инициализироваться либо для шифрования, либо для дешифрования данных. Значение true запрашивает шифрование, а false указывает дешифрование. Второй параметр cipher.init() - это ключ, указанный в виде байтового массива.

outBytes - это массив, который будет содержать результаты зашифрованного текста. Я начинаю процесс шифрования вызовом cipher.processBytes(), передавая входной и выходной массивы, а также значения индексов для этих массивов, указывающих, откуда начинать чтение входной информации и куда записывать выходную информацию.

    private void encryptData()
    {
      // Получить содержимое из текстового поля
      byte[] inBytes = tbTest.getString().getBytes();

      // Инициализировать шифр. Значение 'true' указывает шифрование
      cipher.init(true, new KeyParameter(key.getBytes()));

      // Определить минимальный размер выходного буфера
      byte[] outBytes = new byte[cipher.getOutputSize(inBytes.length)];

      // 'len' - это возвращенная реальная длина
      int len = cipher.processBytes(inBytes, 0, inBytes.length, outBytes, 0);
      try
      {
        // Обработать последний блок в буфере, начиная с позиции 'len' 
        cipher.doFinal(outBytes, len);

        // Обновить текстовое поле новой зашифрованной строкой
        tbTest.setString(new String(Hex.encode(outBytes)));

        // Отладочное сообщение
        System.out.println("encrypted: " + new String(Hex.encode(outBytes)));
      }
      catch(CryptoException e)
      {
        System.out.println("Exception: " + e.toString());
      }
    }
    

Код внутри блока try-catch обрабатывает последний блок в выходном массиве. Затем я обновляю TextBox зашифрованным текстом.

Обзор кода: decrypt data

Аналогично шифрованию, при дешифровании я начинаю с записи содержимого TextBox в массив. Я инициализирую шифр, передавая в этот раз, в качестве первого параметра, значение false. Это указывает на то, что я не шифрую (то есть, не инициализирую механизм для дешифрования). Распределяется выходной массив и вызывается cipher.processBytes() для запуска процесса дешифрования.

    private void decryptData()
    {
      // Получить текст для дешифрования из текстового поля
      byte[] inBytes = Hex.decode(tbTest.getString().getBytes());

      // Инициализация шифра. 'false' указывает дешифрование
      cipher.init(false, new KeyParameter(key.getBytes()));

      // Определить минимальный размер выходного буфера
      byte[] outBytes = new byte[cipher.getOutputSize(inBytes.length)];

      // 'len' - это возвращенная реальная длина
      int len = cipher.processBytes(inBytes, 0, inBytes.length, outBytes, 0);

      try
      {
        // Обработать последний блок в буфере, начиная с позиции 'len' 
        cipher.doFinal(outBytes, len);

        // Обновить текстовое поле дешифрованной строкой
        tbTest.setString(new String(outBytes).trim());

        System.out.println("decrypted: " + new String(outBytes).trim());
      }
      catch(CryptoException e)
      {
        System.out.println("Exception: " + e.toString());
      }
    }
    

Я завершаю процесс дешифрования обновлением TextBox новыми дешифрованными данными.

Резюме по разделу

В этом разделе мы работали с API шифрования Bouncy Castle. Я начал с простого дизайна мидлета (ничего, кроме TextBox): запрос строки для шифрования и три команды управления - для шифрования, для дешифрования и для выхода из мидлета. После этого я создал новый проект в WTK, написал Java-код, скомпилировал его и выполнил предварительную проверку мидлета. Последними действиями были детальный обзор исходного кода, обзор подробностей обработки данных с использованием Bouncy Castle.

Закончив мидлет, я перехожу к теме засекречивания.

Засекречивание

Обзор

При работе с мобильными устройствами размер приложения чрезвычайно важен. Показав, как включить Bouncy Castle API в J2ME-приложение, я, в то же время, добавил значительный объем кода. Многие из современных мобильных устройств просто не смогли бы загрузить мидлет из-за ограниченной памяти. Вот где приходит на помощь засекречивание (obfuscation).

В данном разделе я начну с краткого обзора засекречивания: что это такое, как оно помогает в уменьшении размера приложения и почему оно является важным. Я также приведу шаги по настройке обфускатора ProGuard для использования в WTK. Наконец, я покажу, как активизировать ProGuard и начать процесс генерирования засекреченных class-файлов.

Что такое засекречивание

Обратный анализ Java-программ не является трудным процессом. Собственно говоря, существуют бесплатные декомпиляторы, которые делают работу за вас. Чтобы создать немного больше трудностей для приложений обратного анализа, многие Java-программисты используют так называемые обфускаторы для переименования классов, методов и полей. Назначение такого процесса переименования - сделать исходный код нечитаемым.

Побочным эффектом процесса засекречивания является уменьшение размера class-файлов. Это достигается двумя шагами. Во-первых, может быть сэкономлено неожиданное количество байт при замене имен классов, методов и полей именами, состоящими из одного или двух символов. Кроме того, обфускаторы удаляют неиспользуемые классы, методы и поля. Комбинирование этих двух шагов может значительно уменьшить размер окончательного приложения. В следующем разделе я выполню сравнение мидлетов с засекречиванием и без него, чтобы дать вам почувствовать, ради чего я потратил столько времени на разговоры об этом.

Установка ProGuard

WTK имеет встроенную среду для поддержки обфускаторов байт-кода. Если у вас установлена версия 2.1, как было подчеркнуто в разделе "Установка", вы имеете дополнительную выгоду, заключающуюся в наличии подключаемого модуля, спроектированного специально для ProGuard.

Установить ProGuard так же просто, как и разархивировать proguard.jar и сохранить файл в каталоге bin WTK. Например, если WTK установлен в C:\WTK21, скопируйте файл proguard.jar в C:\WTK21\bin. См. рисунок 10.

Рисунок 10. Установка ProGuard

Выполнение ProGuard: Шаг 1

Я рассмотрю действия по выполнению ProGuard из WTK. Начнем с запуска WTK и открытия мидлета EncryptText. Запустите процесс засекречивания, выбрав меню Project -> Package -> Create Obfuscated Package. См. рисунок 11.

Рисунок 11. Запуск ProGuard


Выполнение ProGuard: Шаг 2

Шаги для пакетирования мидлета включают в себя перекомпилирование всех исходных Java-файлов, выполнение предварительной проверки class-файлов и, наконец, засекречивание. На рисунке 12 показан снимок экрана WTK, отображающий выполнение процесса засекречивания.

Рисунок 12. Засекречивание


Выполнение ProGuard: Шаг 3

После завершения засекречивания вы должны найти JAR-файл, содержащий засекреченные классы. WTK сохраняет все JAR-файлы мидлетов в каталоге bin проекта. Например, для созданного мной в предыдущем разделе мидлета вы найдете JAR в каталоге C:\WTK21\apps\EncryptText\bin. См. рисунок 13.

Рисунок 13. Результаты ProGuard


Результат засекречивания

Чтобы иметь представление о том, на что похожи результаты работы обфускатора, давайте рассмотрим один метод в мидлете до и после засекречивания. Метод encryptData(), написанный ранее, показан ниже:

    private void encryptData()
    {
      // Получить содержимое из текстового поля
      byte[] inBytes = tbTest.getString().getBytes();

      // Инициализировать шифр. Значение 'true' указывает засекречивание
      cipher.init(true, new KeyParameter(key.getBytes()));

      // Определить минимальный размер выходного буфера
      byte[] outBytes = new byte[cipher.getOutputSize(inBytes.length)];

      // 'len' - это возвращенная реальная длина
      int len = cipher.processBytes(inBytes, 0, inBytes.length, outBytes, 0);
      try
      {
        // Обработать последний блок в буфере, начиная с позиции 'len' 
        cipher.doFinal(outBytes, len);

        // Обновить текстовое поле новой засекреченной строкой
        tbTest.setString(new String(Hex.encode(outBytes)));

        // Отладочное сообщение
        System.out.println("encrypted: " + new String(Hex.encode(outBytes)));
      }
      catch(CryptoException e)
      {
        System.out.println("Exception: " + e.toString());
      }
    }
    

Ниже вы можете увидеть, как выглядит засекреченный код:

private void a()
{
    byte abyte0[] = f.getString().getBytes();
    g.a(true, new b(a.getBytes()));
    byte abyte1[] = new byte[g.a(abyte0.length)];
    int i = g.a(abyte0, 0, abyte0.length, abyte1, 0);
    try
    {
     g.a(abyte1, i);
     f.setString(new String(org.bouncycastle.util.encoders.a.b(abyte1)));
     System.out.println("encrypted: " + new String(org.bouncycastle.util.encoders.a.b(abyte1)));
    }
    catch(org.bouncycastle.crypto.b b1)
    {
     System.out.println("Exception: " + b1.toString());
    }
}
    

Очевидно, что его намного труднее прочитать. Обратите внимание на короткие имена переменных и методов. Это один из методов, которые использует обфускатор для уменьшения размера приложения. Как вы помните, обфускатор также удаляет неиспользуемые классы, методы и поля, что обычно является самым существенным методом удаления большого количества кода из мидлета.

Резюме по разделу

В этом разделе был представлен обзор засекречивания, в том числе такие темы: почему засекречивание важно, как работает процесс, и как установить и запустить ProGuard из WTK.

Давайте закончим сравнением размера мидлетов до засекречивания и после.

Объединяем все вместе

Обзор

Теперь вы имеете достаточно информации для шифрования и дешифрования данных в приложениях, а также для засекречивания мидлетов. В данном разделе я подробно рассмотрю, что было сделано для экономии байтов в JAR-файлах с засекречиванием и без него.

Я начну с рассмотрения мидлета без засекречивания.

Пакетирование мидлета

Когда бы я ни выбрал пункт меню Build в WTK, исходный Java-код компилируется и проверяется. Однако по умолчанию WTK не пакетирует мидлет в JAR-файл. Для того чтобы увидеть размер мидлета, включающего весь добавленный код из дополнительных библиотек, я должен указать WTK спакетировать весь код. Из меню Project выберите Package / Create Package. См. рисунок 14.

Рисунок 14. Пакетирование мидлета

Размер спакетированного мидлета

Для поиска созданного WTK JAR-файла перейдите в каталог bin проекта EncryptText, например, C:\WTK21\apps\EncryptTest\lib. На рисунке 15 показан размер файла - 547 KB. Излишне говорить, что один этот факт делает мидлет бесполезным для многих мобильных устройств.

Рисунок 15. Размер спакетированного мидлета

Засекречивание мидлета

Теперь я заново создам JAR-файл, на этот раз запрашивая ProGuard засекретить содержимое. Из меню Project выберите Package / Create Obfuscated Package. См. рисунок 16.

Рисунок 16. Засекречивание мидлета

Размер засекреченного мидлета

Снова найдите каталог bin для того чтобы увидеть размер JAR-файла. Вы заметите значительную разницу после удаления обфускатором всех ненужных классов, методов и полей. На рисунке 17 видно, что было сэкономлено более 500 KB!

Рисунок 17. Размер засекреченного мидлета

Сжатие еще на несколько байт

Ранее говорилось, что WTK 2.x предварительно настроен на работу с ProGuard. Как вы видели в данном руководстве, процесс установки минимален. Однако, выполнив несколько дополнительных действий, вы можете сэкономить еще несколько байтов. Это можно сделать путем даже незначительного изменения конфигурации, запрашивая, чтобы WTK вызывал ProGuard непосредственно во время процесса пакетирования. Отличие в том, как вызывается ProGuard. При существующей конфигурации WTK, когда вы запрашиваете пакетирование/засекречивание с ProGuard, конфигурационный файл записывается на диск, в то время как отдельная виртуальная машина читает файл и выполняет ProGuard на основе содержимого файла. При подходе, который вы здесь увидите, WTK будет вызывать ProGuard напрямую, пропуская необходимость чтения и обработки конфигурационного файла.

Сжатие еще на несколько байт, шаг 1

Во-первых, вы должны найти конфигурационный файл ktools.properties. В Windows он находится в каталоге C:\WTK21\wtklib\Windows. На рисунке 18 показано его месторасположение.

Рисунок 18. Конфигурационный файл

Сжатие еще на несколько байт, шаг 2

Для запроса прямого вызова ProGuard в WTK вы должны изменить в конфигурационном файле строки, сообщающие WTK предпочтительный класс и classpath для ProGuard. Выполните изменения в конфигурационном файле, показанные ниже:

Конфигурация по умолчанию

      obfuscator.runner.class.name: com.sun.kvem.ktools.RunPro
      obfuscator.runner.classpath: wtklib\\ktools.zip
      obfuscate.script.name:
        

Обновленная конфигурация

      #obfuscator.runner.class.name: com.sun.kvem.ktools.RunPro
      #obfuscator.runner.classpath: wtklib\\ktools.zip
      #obfuscate.script.name:

      obfuscator.runner.class.name: proguard.wtk.ProGuardObfuscator
      obfuscator.runner.classpath: bin\\proguard.jar
        

Здесь я закомментировал оригинальную конфигурацию и указал класс и classpath расположения JAR-файла ProGuard. Это простое изменение приводит к более эффективному засекречиванию.

Важное замечание: Для того чтобы эти изменения вступили в силу, вы должны перезапустить WTK.

Сжатие еще на несколько байт, шаг 3

После перезапуска WTK создайте засекреченный пакет так, как я делал ранее. Теперь найдите каталог bin, куда WTK записывает засекреченный JAR-файл. Если вы сравните размер нового JAR-файла с JAR-файлом из предыдущего раздела, то увидите разницу примерно в 3 K. На рисунках 19 и 20 показаны JAR-файлы для оригинального мидлета и для новой конфигурации соответственно.

Рисунок 19. Оригинальный засекреченный JAR-файл

Используя WTK, вызывающий ProGuard напрямую, вы можете сэкономить еще несколько байт.

Рисунок 20. Новый засекреченный JAR-файл

Хотя 3 K могут показаться не очень значительным достижением, в мире мобильных приложений эта разница может повлиять на то, загрузится ваш мидлет в устройство или нет.

Резюме по разделу

Кроме того, что мидлеты становятся более трудными для обратного анализа, засекречивание может иметь значительное влияние на размер вашего приложения. В данном разделе вы увидели, сколько сохраняется пространства после удаления неиспользуемых классов, методов и полей. Для короткого мидлета эта экономия составила более 500 K.

Резюме

В данном руководстве был представлен обзор защиты мобильных приложений. Оно началось с короткого обсуждения шифрования, включая шифры, ключи и алгоритмы. Также был рассмотрен процесс установки API шифрования Bouncy Castle.

Предметом рассмотрения следующего раздела была разработка мидлета для демонстрации использования Bouncy Castle API для шифрования и дешифрования простых текстовых строк. После этого вы узнали о засекречивании class-файлов. Специальное внимание было уделено ProGuard, в том числе, интеграции этого инструментального средства с открытым исходным кодом в WTK. В последнем разделе руководства было проведено сравнение размеров JAR-файлов до засекречивания class-файлов и после.

Хотя вы только поверхностно познакомились с созданием защищенных мобильных приложений, это руководство послужит отправной точкой в теме шифрования данных. В вашем распоряжении также имеется новый инструмент (обфускатор class-файлов) предназначенный для того, чтобы сделать обратный анализ вашего приложения более трудным. Не забудьте о способности обфускатора удалять неиспользуемые классы, методы и поля, что может привести к созданию значительно меньших JAR-файлов.


Страница сайта http://www.interface.ru
Оригинал находится по адресу http://www.interface.ru/home.asp?artId=3860