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

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

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

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

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

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

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

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

Давайте начнем с первой вещи, которую, вероятно, захочет сделать каждый пользователь; Создайте новый ключ OpenPGP:

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

Выйти из полноэкранного режима

Результатом является защищенный паролем ключ OpenPGP, в котором используются современные подразделы EdDSA и XDH. API абстрагировался от всех надоедливых вещей, таких как алгоритмы, которые следует использовать для ключей, какие предпочтения алгоритмов (хэш-, симметричные- и алгоритмы сжатия) установить, и, конечно же, связывание ключей и идентификатора пользователя вместе с помощью подписей привязки. . Между тем, есть более гибкий 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 способен на большее, например симметричное шифрование с использованием парольной фразы, создание подписей на сертификатах других пользователей, изменение ключей (добавление идентификаторов пользователей и подразделов, изменение парольных фраз, отзыв и истечение срока действия ключей и идентификаторов пользователей… ), и так далее. Есть пример пакет, который демонстрирует многие из этих вариантов использования.

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

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

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

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