Шифрование закрытого ключа RSA с AES 256 в Java

Я пишу безопасное приложение для обмена файлами на Java. Общая архитектура выглядит так:

  1. пользователь хочет зашифровать файл для безопасного обмена между несколькими пользователями.
  2. приложение генерирует случайный UUID на клиенте и использует его в качестве пароля AES 256 и шифрует данные с помощью UUID.
  3. UUID затем шифруется RSA с открытым ключом каждого человека. Один раз для общего пользователя.
  4. каждый зашифрованный UUID пакет хранится как часть файла в пользовательском заголовке файла.
  5. файл затем загружается на сервер, где другие могут получить к нему доступ.
  6. каждый пользователь может использовать свой закрытый ключ для чтения ключа шифрования AES и расшифровки файла.

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

Я хотел бы сделать это с помощью AES 256-битного шифрования. И я хотел бы сделать все это, не полагаясь на библиотеки BouncyCastle или любые сторонние библиотеки. Он должен использовать стандартные библиотеки Java 5, поэтому я решил использовать шифрование AES 256 и RSA, а не что-то вроде PGP.

может ли кто-нибудь найти что-либо изначально небезопасное с этим подходом или подумать о более эффективном как это сделать?

Edit:

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

4 ответов


схема, которую вы описываете, эквивалентна CMS (стандартному базовому S/MIME) и PGP; по сути, она безопасна. В CMS этот режим называется "ключевой транспорт". Вы также можете использовать многопартийное "ключевое соглашение" с алгоритмом, таким как DH или ECDH.

единственная проблема заключается в том, что вы используете плохо подобранные ключи для AES.

Я не могу придумать никакой причины использовать случайный UUID, который содержит неслучайные биты. Просто используйте обычный механизм генерации ключей Java Архитектура Криптографии. Ключи, открытый текст и зашифрованный текст должны быть представлены в виде последовательностей байтов, если только вам не нужно разместить какое-то внешнее хранилище или транспорт, который только вмещает текст.

Iterable<Certificate> recipients = null;
KeyGenerator gen = KeyGenerator.getInstance("AES");
gen.init(256);
SecretKey contentEncryptionKey = gen.generateKey();

инициализируйте шифр AES и позвольте провайдеру выбрать IV.

Cipher contentCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
contentCipher.init(Cipher.ENCRYPT_MODE, contentEncryptionKey);
AlgorithmParameters params = contentCipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();

для каждого получателя инициализируйте шифр RSA и зашифруйте ключ AES.

Cipher keyEncryptionCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
for (Certificate recipient : recipients) {
  keyEncryptionCipher.init(Cipher.WRAP_MODE, recipient);
  byte[] encryptedKey = keyEncryptionCipher.wrap(contentEncryptionKey);
  /* Store the encryptedKey with an identifier for the recipient... */
}
/* Store the IV... */ 
/* Encrypt the file... */

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

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

(Я не могу вспомнить, поддерживает ли Java 5 "RSA/ECB/OAEPWithSHA-512AndMGF1Padding"; если это так, вы должны использовать это вместо PKCS1Padding.)


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

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

редактировать: как я делаю это:

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

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

при этом две очевидные линии атаки направлены против генератора случайных чисел и против самой RSA. Для генератора случайных чисел я бы использовал SecureRandom Java - это именно та цель, для которой он предназначен, и если память служит, она была довольно тщательно изучена, и значительные перерывы против нее кажутся довольно маловероятными.

Я не буду пытаться комментировать безопасность самой RSA. На данный момент, я думаю, ваша главная забота-протокол, а не алгоритм шифрования. Достаточно сказать, что если бы RSA были значительно нарушены, Вам, очевидно, нужно было бы изменить свой код, но у вас будет много компания.

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

Edit 2: Что касается работы с несколькими устройствами, я думаю, что я бы просто рассматривал каждое устройство как отдельного пользователя с его собственной парой открытых/закрытых ключей. Я бы тогда (вероятно) сгруппируйте их вместе с помощью реальных пользователей, поэтому можно легко выбрать "пустобрех", чтобы дать ему доступ на всех своих устройствах, но с иерархического просмотра, я могу также довольно легко ограничить доступ к некоторым из них, так что если я хочу поделиться им с Джо на его офисной машины, но она чувствительна достаточно того, что я не хочу идти туда, где кто-то смотрит через его плечо, пока он на это смотрит, я могу довольно легко сделать это.

Это держит жизнь простой для потребителей, но сохраняет та же базовая модель безопасности (т. е. закрытые ключи остаются закрытыми).


хорошо, этот вопрос требует обсуждения протокола, поэтому он не полностью соответствует стандартам stackoverflow. Тем не менее, давайте посмотрим, можем ли мы сделать некоторые замечания в любом случае :) :

  1. библиотеки Bouncy Castle PGP имеют очень разрешительную лицензию, поэтому вы даже можете скопировать их в подпакет в своем коде;
  2. помимо PGP существуют и другие стандартные форматы контейнеров, такие как CMS или XML-шифрование, хотя последнее может быть не таким хорошим формат общего назначения;
  3. вместо UUID я бы настоятельно рекомендовал использовать хорошо засеянный PRNG, такой как Java JCE "SHA1PRNG", для создания ключей AES - я не вижу никакой веской причины, почему вы должны полагаться на что-то вроде UUID в своей схеме;
  4. ключи AES должны состоять из случайных битов, чтобы иметь достаточную энтропию, думая о них как о "паролях", ведет в ловушку: вы не можете использовать строку в качестве безопасного ключа AES;
  5. пользователь будет доверять приложение и сервер, вы are действуя как доверенная третья сторона: вы можете отправлять пароли пользователей на свой сервер, вы можете отправлять неправильные открытые ключи пользователям и т. д. так далее. так далее.
  6. ваша схема не защищена от любого человека (и многие утверждают, это не может быть сделано без использования SSL)
  7. вместо прямого шифрования паролем, вы должны посмотреть на что-то вроде шифрования на основе пароля PBKDF2 для шифрования RSA private ключ;
  8. попробуйте добавить защиту целостности при шифровании, начиная с Java 7, Вы можете использовать AES в режиме GCM, это того стоит.

все зависит от того, насколько "безопасным" вы хотите, чтобы шифрование было. Очевидно, что RSA является хорошим документом / принятым стандартом для PKI. Тем не менее, каждый раз, когда вы предоставляете открытый текст, а также зашифрованный текст, хакеру значительно легче расшифровать зашифрованный текст, зная часть открытого текста. Здесь вы делаете именно это. Хотя вы передаете только зашифрованный UUID, наличие одного и того же открытого текста, зашифрованного несколькими ключами, дает злоумышленнику важное значение полезной нагрузки. Кроме того, если взломанный фактически является одним из получателей, он может декодировать UUID и тем самым автоматически знает открытый текст, который шифруется открытыми ключами других пользователей.

Это, вероятно, не критическая проблема для вас, но просто подумал, что я хотел бы указать на риск безопасности.

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

Я сделал что-то подобное в прошлом, но и сохраняет результаты в БД. Однако в то время я использовал библиотеки BouncyCastle, поэтому не уверен, как это сделать без них.