Простой OpenPGP с помощью PGPainless

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

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

Протокол OpenPGP имеет механизм сигнализации поддержки алгоритма через поля на открытом ключе получателя. Вы должны сами извлечь их? Как вы узнаете, какие алгоритмы безопасны?

Именно в такой ситуации я оказался четыре года назад. Bouncy Castle OpenPGP API — могучий и мощный, но, к сожалению, очень низкоуровневый и требует, чтобы вы сами выполняли тяжелую работу. Это нормально, если вы знакомы с протоколом OpenPGP, но вы можете не быть экспертом и просто хотите выполнить работу. Оставлять всю критическую для безопасности работу по настройке на потребителя API — плохая идея.

Именно поэтому я решил создать PGPainless. Первоначально я искал другие альтернативы и наткнулся на библиотеку под названием bouncy-gpg, но быстро понял, что она не подходит для моих нужд, и решил пойти своим путем. Тем не менее, bouncy-gpg сильно повлияла на развитие PGPainless, особенно на ранних стадиях.

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

Особенно когда речь идет о проверке подписи, многие сообщения на StackOverflow (и даже собственные примеры Bouncy Castle!!) просто показывают, как проверить правильность подписи. Они не обсуждают проверку подписи на достоверность. Подпись может быть криптографически корректной, но недействительной, например, из-за отозванного ключа подписи. Существует целый набор проверок, которые необходимо выполнить, чтобы проверить, действительно ли подпись действительна в определенное время. PGPainless выполняет эти проверки за вас.

Теперь давайте посмотрим несколько примеров.

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

PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
  .modernKeyRing("Romeo <romeo@montague.lit>", "p4ssw0rd");
Войти в полноэкранный режим Выйти из полноэкранного режима

В результате мы получаем защищенный паролем ключ OpenPGP, использующий современные подключи EdDSA и XDH. API абстрагирует все раздражающие вещи, такие как то, какие алгоритмы использовать для ключей, какие предпочтения алгоритмов (хэш-, симметричный и алгоритм сжатия) установить, и, конечно, связывание ключей и user-id вместе с помощью подписей привязки. Между тем, существует более гибкий API PGPainless.buildKeyRing(), который позволяет пользователю изменять все эти параметры по своему усмотрению.

Теперь давайте подпишем и зашифруем сообщение:

// We generated our key in the previous example
PGPSecretKeyRing secretKey = ...;
SecretKeyRingProtector protector = SecretKeyRingProtector
  .unlockAnyKeyWith(Passphrase.fromPassword("p4ssw0rd"));

// Extract our own public key certificate
PGPPublicKeyRing certificate = PGPainless.extractCertificate(secretKey);
// Read Juliets public key certificate from somewhere
PGPPublicKeyRing julietsCertificate = PGPainless.readKeyRing()
  .publicKeyRing(julietsCertInputStream);

// The message we want to encrypt and sign
InputStream plaintextIn = ...;
// The destination to where we want to write the ciphertext
OutputStream ciphertextOut = ...; // e.g. new ByteArrayOutputStream();
// Set up the stream
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
  .onOutputStream(ciphertextOut)
  .withOptions(ProducerOptions.signAndEncrypt(
     new EncryptionOptions()
       .addRecipient(certificate)
       .addRecipient(julietsCertificate),
     new SigningOptions()
       .addInlineSignature(protector, secretKey)
     )
  );
// Encrypt and sign
Streams.pipeAll(plaintextIn, encryptionStream);
encryptionStream.close();

// Information about the encryption (algorithms, detached signatures etc.)
EncryptionResult result = encryptionStream.getResult();
Вход в полноэкранный режим Выйти из полноэкранного режима

Это все еще не односложно, но это огромное улучшение по сравнению с тем, что вам пришлось бы делать при непосредственном использовании Bouncy Castle.

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

// Set up the stream
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
  .onInputStream(encryptedInputStream)
  .withOptions(new ConsumerOptions()
     .addDecryptionKey(julietsSecretKey, secretKeyProtector)
     .addVerificationCert(romeosCertificate)
  );

// Decrypt and verify the data
Streams.pipeAll(decryptionStream, outputStream);
decryptionStream.close();

// Result contains information like signature status etc.
OpenPgpMetadata metadata = decryptionStream.getResult();
Вход в полноэкранный режим Выход из полноэкранного режима

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

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

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

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

Если вы разрабатываете библиотеку OpenPGP для экосистемы Java, пожалуйста, рассмотрите возможность создания реализации sop-java вместе с ней, так как это не только позволит подключить вашу библиотеку к проектам, использующим sop-java, но также позволит подключить вашу реализацию к набору тестов Sequoia-PGP OpenPGP Interoperability Test Suite, так что вы сможете воспользоваться большим количеством тестовых векторов для выявления ошибок и проблем взаимодействия.

Наконец, это проект свободного программного обеспечения, то есть весь код доступен по лицензии Apache 2.0. Разработка ведется как на GitHub, так и на Codeberg. Если вы считаете этот проект полезным, пожалуйста, распространите информацию о нем и внесите свой вклад.

Оцените статью
devanswers.ru
Добавить комментарий