Java development 2.0: Вторая волна разработки Java-приложений. Защита данных Java-приложений в облачной среде

Применение шифрования с закрытым ключом для защиты облачных данных

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

Эндрю Гловер, президент компании, Stelligent Incorporated

Эндрю ГловерЭндрю Гловер является президентом компании Stelligent Incorporated , которая помогает другим фирмам решать проблемы качества программного обеспечения. Для этого используются эффективные стратегии тестирования и технологии непрерывной интеграции, которые позволяют коллективам разработчиков постоянно контролировать качество кода, начиная с ранних стадий разработки. Просмотрите блог Энди , там можно найти список его публикаций.



12.10.2012

Об этой серии

С момента первого появления технологии Java подходы к разработке ПО на этом языке существенно изменились. Благодаря появлению мощных систем с открытым исходным кодом и надежных инфраструктур для предоставления средств разработки в аренду появилась возможность быстро и дешево транслировать, тестировать, исполнять и поддерживать приложения Java. В этой серии статей Эндрю Гловер описывает широкий спектр средств и технологий, благодаря которым стала возможной эта новая парадигма разработки на языке Java.

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

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

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

Защита облачных данных

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

Защита данных в Европейском союзе

Подход Европейского союза к защите данных в облачных платформах отличается существенно большей строгостью, чем подход США: личные данные, принадлежащие гражданам Европейского союза (например, медицинские карты граждан Франции), должны храниться на серверах, установленных на территории Европейского союза.

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

  • Защищены ли данные в процессе передачи?
  • Защищены ли данные в процессе хранения?

Под передачей данных понимается способ переноса данных из одного места в другое; то есть используемые вами коммуникационная технология и инфраструктура. От способа хранения данных зависит как — и насколько хорошо — сохранены ваши данные. Если, например, вы сохраните в базе данных имена пользователей и пароли без всякого шифрования, то хранение ваших данных нельзя признать безопасным.

Для защиты данных при передаче их через Интернет обычно используется протокол HTTPS. Это HTTP с шифрованием данных при их передаче из браузера клиентам. Важным преимуществом HTTPS является его доступность: большинство разработчиков уже имеют серверы Apache, Tomcat или Jetty, настроенные на работу с HTTPS.

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


Шифрование с закрытым ключом

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

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

Открытые ключи и конфиденциальность

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

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

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


Шифрование Java-приложения

Существует множество вариантов защиты Java-приложений, в том числе стандартные библиотеки платформы Java. Кроме того, существует множество стандартов и пакетов шифрования, из которых вы можете выбирать те, которые вам нравятся. В следующих примерах я буду использовать базовые библиотеки Java и AES (Advanced Encryption Standard). Я использую закрытый ключ для шифрования простого текста и расшифровки зашифрованного текста, представляющего собой тот самый простой текст, который был зашифрован. Мне нравится AES, поскольку он одобрен Агентством национальной безопасности и стандартизован правительством США.

Для обеспечения максимальной гибкости и для упрощения тестирования я создам несколько криптографически интерфейсов и соответствующих классов реализации, которые просто упаковывают базовые классы Java. Затем я покажу, как использовать эти классы для защищенного сохранения данных и для обращения к ним в таких облачных хранилищах, как Amazon SimpleDB или даже MongoHQ MongoDB.

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

Листинг 1. Криптографический интерфейс
package com.b50.crypto;

public interface Cryptographical {
 String encrypt(String plaintext);
 String decrypt(String ciphertext);
}

С помощью интерфейса Cryptographical я могу шифровать текст или расшифровывать зашифрованный текст. Затем в листинге 2 я использую API инструментов безопасности Java для создания другого интерфейса, который представляет ключ:

Листинг 2. Интерфейс ключа
package com.b50.crypto;

import java.security.Key;

public interface CryptoKeyable {
  Key getKey();
}

Как видите, мой интерфейс CryptoKeyable выступает в роли упаковки для базового типа Key платформы Java.

Если вы используете шифрование AES, то двоичные данные, генерируемые при шифровании простого текста, должны иметь кодировку base-64 — или по крайней мере так должно быть, если вы собираетесь использовать их в Web-запросах (например, в доменах SimpleDB). Таким образом я буду кодировать все шифруемые строки и декодировать все дешифрируемые строки.

Показанный в листинге 3 класс реализации Cryptographical для AES выполняет не только шифрование AES, но и кодирование и декодирование base-64:

Листинг 3. Реализация криптографического интерфейса для AES
package com.b50.crypto;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;

public class AESCryptoImpl implements Cryptographical {

 private Key key;
 private Cipher ecipher;
 private Cipher dcipher;

 private AESCryptoImpl(Key key) throws NoSuchAlgorithmException,
   NoSuchPaddingException, InvalidKeyException {
  this.key = key;
  this.ecipher = Cipher.getInstance("AES");
  this.dcipher = Cipher.getInstance("AES");
  this.ecipher.init(Cipher.ENCRYPT_MODE, key);
  this.dcipher.init(Cipher.DECRYPT_MODE, key);
 }

 public static Cryptographical initialize(CryptoKeyable key) throws CryptoException {
  try {
   return new AESCryptoImpl(key.getKey());
  } catch (NoSuchAlgorithmException e) {
   throw new CryptoException(e);
  } catch (NoSuchPaddingException e) {
   throw new CryptoException(e);
  } catch (InvalidKeyException e) {
   throw new CryptoException(e);
  }
 }

 public String encrypt(String plaintext) {
  try {
   return new BASE64Encoder().encode(ecipher.doFinal(plaintext.getBytes("UTF8")));
  } catch (Exception e) {
   throw new RuntimeException(e);
  }
 }

 public String decrypt(String ciphertext) {
  try {
   return new String(dcipher.doFinal(new BASE64Decoder().decodeBuffer(ciphertext)), 
     "UTF8");
  } catch (Exception e) {
   throw new RuntimeException(e);
  }
 }
}

Хранилище ключей Java

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

KeyStore содержит набор классов, позволяющих сохранять ключ в защищенном паролем двоичном файле, который называется хранилищем ключей. Ключи в Java можно проверить с помощью нескольких сценариев. Сначала я создаю два экземпляра ключа Key и показываю, что соответствующие им зашифрованные строки String отличаются друг от друга, как показано в листинге 4:

Листинг 4. Простое шифрование с помощью двух разных ключей
@Test
public void testEncryptRandomKey() throws Exception {
 SecretKey key = KeyGenerator.getInstance("AES").generateKey();
 Cryptographical crypto = AESCryptoImpl.initialize(new AESCryptoKey(key));
 String enc = crypto.encrypt("Andy");
 Assert.assertEquals("Andy", crypto.decrypt(enc));

 SecretKey anotherKey = KeyGenerator.getInstance("AES").generateKey();
 Cryptographical anotherInst = AESCryptoImpl.initialize(new AESCryptoKey(anotherKey));
 String anotherEncrypt = anotherInst.encrypt("Andy");
 Assert.assertEquals("Andy", anotherInst.decrypt(anotherEncrypt));

 Assert.assertFalse(anotherEncrypt.equals(enc));
}

Теперь, в листинге 5, я покажу, что данный экземпляр ключа всегда производит один и тот же зашифрованный текст для соответствующей строки String:

Листинг 5. Закрытый ключ соответствует одной строке
@Test
public void testEncrypt() throws Exception {
 SecretKey key = KeyGenerator.getInstance("AES").generateKey();

 KeyStore ks = KeyStore.getInstance("JCEKS");
 ks.load(null, null);
 KeyStore.SecretKeyEntry skEntry = new KeyStore.SecretKeyEntry(key);
 ks.setEntry("mykey", skEntry, 
   new KeyStore.PasswordProtection("mykeypassword".toCharArray()));
 FileOutputStream fos = new FileOutputStream("agb50.keystore");
 ks.store(fos, "somepassword".toCharArray());
 fos.close();

 Cryptographical crypto = AESCryptoImpl.initialize(new AESCryptoKey(key));
 String enc = crypto.encrypt("Andy");
 Assert.assertEquals("Andy", crypto.decrypt(enc));

 //альтернативно, для получения ключа можно считать сам файл хранилища

 Cryptographical anotherInst = AESCryptoImpl.initialize(new AESCryptoKey(key));
 String anotherEncrypt = anotherInst.encrypt("Andy");
 Assert.assertEquals("Andy", anotherInst.decrypt(anotherEncrypt));

 Assert.assertTrue(anotherEncrypt.equals(enc));
}

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

Замечания о хранилище ключей

Код в листингах 4 и 5— это всего лишь шаблон, цель которого — продемонстрировать следующее:

  • Хранилище ключей имеет имя.
  • Сохраненный файл защищен паролем.
  • Хранилище ключей может хранить несколько ключей.
  • Каждый ключ в хранилище имеет связанный с ним пароль.

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

Когда речь идет о шифровании, ключ играет решающую роль. Каким бы стойким ни был применяемый шифр, если ключ скомпрометирован, данные становятся доступными. Это значит, что само хранилище ключей и его пароли всегда должны быть защищены. (Например, в реальном приложении я бы не стал писать пароли в программном коде, как я сделал это для демонстрации в листингах 4 и 5.)


Криптография в облаке

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

Запросы с шифрованием

Поиск в зашифрованных данных выполняется очень легко, если мы ищем точные совпадения, такие как: "Найди мне счета с именем 'foo' (где имя 'foo' зашифровано)". Но, естественно, это не работает для условных запросов, вроде такого: "Найди мне все счета, просроченный остаток которых превышает 450 долларов, причем сумма 450 долларов зашифрована".

Представьте на минуту, что я использую простой шифр, который обращает порядок следования символов и добавляет в конце строки символ i. В этом случае строка foo превратится в oofi, а число 450 превратится в 054i. Если имя в таблице зашифровано этим простым шифром, я могу легко найти точное совпадение, например, вот так: "выбери * из таблицы, где имя = 'oofi'". Но сравнение зашифрованного значения 450 делается по-другому: "выбери * из таблицы, где значение > 054i" это совсем не то, что "выбери * из таблицы, где значение > 450".

Чтобы выполнить в этом случае сравнение данных, мое приложение должно произвести некоторую расшифровку — то есть мне нужно выбрать все данные из таблицы, расшифровать поле amount (сумма) и только потом выполнить сравнение. Если используемое хранилище не может выполнить такую операцию, то фильтрация, вероятно, будет выполняться небыстро. Учитывая тот факт, что я стремлюсь повысить эффективность, я должен тщательно обдумать, какие данные я хочу шифровать и как я хочу это делать. Шифрование с учетом будущих запросов позволяет повысить общую эффективность программы.

Можно легко зашифровать имя в MongoDB и выполнить поиск по этому зашифрованному имени, как показано в листинге 6:

Листинг 6. Шифрование в MongoDB
@Test
public void encryptMongoDBRecords() throws Exception {
 KeyStore.SecretKeyEntry pkEntry = getKeyStoreEntry();
 Cryptographical crypto = 
   AESCryptoImpl.initialize(new AESCryptoKey(pkEntry.getSecretKey()));

 DB db = getMongoConnection();
 DBCollection coll = db.getCollection("accounts");

 BasicDBObject encryptedDoc = new BasicDBObject();
 encryptedDoc.put("name", crypto.encrypt("Acme Life, LLC"));
 coll.insert(encryptedDoc);


 BasicDBObject encryptedQuery = new BasicDBObject();
 encryptedQuery.put("name", crypto.encrypt("Acme Life, LLC"));

 DBObject result = coll.findOne(encryptedQuery);
 String value = result.get("name").toString();
 Assert.assertEquals("Acme Life, LLC", crypto.decrypt(value));
}

Первым делом в листинге 6 я с помощью метода getKeyStoreEntry выполняю чтение существующего хранилища ключей. Затем я устанавливаю соединение с экземпляром MongoDB, который в данном случае расположен в облаке в MongoHQ. После этого я получаю ссылку на коллекцию счетов (которую программист реляционных СУБД назвал бы таблицей счетов) и вставляю новый счет с зашифрованным именем. И, наконец, я выполняю поиск этой записи, шифруя искомую строку (где name равно зашифрованной строке "Acme Life, LLC").

Запись в MongoDB будет выглядеть примерно так, как показано в листинге 7. (Обратите внимание, что ваша зашифрованная строка "Acme Life, LLC" будет отличаться от моей, поскольку вы будете пользоваться другим ключом.)

Листинг 7. Проверка шифрования в MongoDB
{
 _id : "4ee0c541300484530bf9c6fa",
 name : "f0wJxYyVhfH0UkkTLKGZng=="
}

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

Эта стратегия не ограничивается реализациями MongoDB. Например, я могу выполнить примерно такую же проверку в SimpleDB, как показано в листинге 8:

Листинг 8. Проверка шифрования в SimpleDB
@Test
public void testSimpleDBEncryptInsert() throws Exception {

 KeyStore.SecretKeyEntry pkEntry = getKeyStoreEntry();
 Cryptographical crypto = 
   AESCryptoImpl.initialize(new AESCryptoKey(pkEntry.getSecretKey()));

 AmazonSimpleDB sdb = getSimpleDB();
 String domain = "accounts";
 sdb.createDomain(new CreateDomainRequest(domain));

 List<ReplaceableItem> data = new ArrayList<ReplaceableItem>();

 String encryptedName = crypto.encrypt("Acme Life, LLC");

 data.add(new ReplaceableItem().withName("account_02").withAttributes(
  new ReplaceableAttribute().withName("name").withValue(encryptedName)));

 sdb.batchPutAttributes(new BatchPutAttributesRequest(domain, data));

 String qry = "select * from " + SimpleDBUtils.quoteName(domain) 
   + " where name = '" + encryptedName + "'";

 SelectRequest selectRequest = new SelectRequest(qry);
 for (Item item : sdb.select(selectRequest).getItems()) {
  Assert.assertEquals("account_02", item.getName());
 }
}

Здесь я проделал те же операции, что и в примере с MongoDB: я выполнил чтение существующего хранилища ключей, установил соединение с Amazon SimpleDB и затем вставил счет с зашифрованным атрибутом name (имя). И, наконец, я выполнил поиск счета по имени, используя в качестве ключа зашифрованное значение.


Заключение

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

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

Ресурсы

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java, Облачные вычисления
ArticleID=840195
ArticleTitle=Java development 2.0: Вторая волна разработки Java-приложений. Защита данных Java-приложений в облачной среде
publish-date=10122012