1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-09-09 18:29:39 +02:00

Expose encryption mechanism during decryption

This commit is contained in:
Paul Schaub 2025-04-30 15:57:54 +02:00
parent 3f6d4755e0
commit ef9fed2844
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
4 changed files with 167 additions and 24 deletions

View file

@ -9,6 +9,7 @@ import javax.annotation.Nonnull
import org.bouncycastle.bcpg.KeyIdentifier import org.bouncycastle.bcpg.KeyIdentifier
import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPKeyRing
import org.bouncycastle.openpgp.PGPLiteralData import org.bouncycastle.openpgp.PGPLiteralData
import org.bouncycastle.openpgp.api.MessageEncryptionMechanism
import org.bouncycastle.openpgp.api.OpenPGPCertificate import org.bouncycastle.openpgp.api.OpenPGPCertificate
import org.pgpainless.algorithm.CompressionAlgorithm import org.pgpainless.algorithm.CompressionAlgorithm
import org.pgpainless.algorithm.StreamEncoding import org.pgpainless.algorithm.StreamEncoding
@ -32,22 +33,34 @@ class MessageMetadata(val message: Message) {
* The [SymmetricKeyAlgorithm] of the outermost encrypted data packet, or null if message is * The [SymmetricKeyAlgorithm] of the outermost encrypted data packet, or null if message is
* unencrypted. * unencrypted.
*/ */
@Deprecated("Deprecated in favor of encryptionMechanism",
replaceWith = ReplaceWith("encryptionMechanism")
)
val encryptionAlgorithm: SymmetricKeyAlgorithm? val encryptionAlgorithm: SymmetricKeyAlgorithm?
get() = encryptionAlgorithms.let { if (it.hasNext()) it.next() else null } get() = encryptionAlgorithms.let { if (it.hasNext()) it.next() else null }
val encryptionMechanism: MessageEncryptionMechanism?
get() = encryptionMechanisms.let { if (it.hasNext()) it.next() else null }
/** /**
* [Iterator] of each [SymmetricKeyAlgorithm] encountered in the message. The first item * [Iterator] of each [SymmetricKeyAlgorithm] encountered in the message. The first item
* returned by the iterator is the algorithm of the outermost encrypted data packet, the next * returned by the iterator is the algorithm of the outermost encrypted data packet, the next
* item that of the next nested encrypted data packet and so on. The iterator might also be * item that of the next nested encrypted data packet and so on. The iterator might also be
* empty, in case of an unencrypted message. * empty, in case of an unencrypted message.
*/ */
@Deprecated("Deprecated in favor of encryptionMechanisms",
replaceWith = ReplaceWith("encryptionMechanisms")
)
val encryptionAlgorithms: Iterator<SymmetricKeyAlgorithm> val encryptionAlgorithms: Iterator<SymmetricKeyAlgorithm>
get() = encryptionLayers.asSequence().map { it.algorithm }.iterator() get() = encryptionLayers.asSequence().map { it.algorithm }.iterator()
val encryptionMechanisms: Iterator<MessageEncryptionMechanism>
get() = encryptionLayers.asSequence().map { it.mechanism }.iterator()
val isEncrypted: Boolean val isEncrypted: Boolean
get() = get() =
if (encryptionAlgorithm == null) false if (encryptionMechanism == null) false
else encryptionAlgorithm != SymmetricKeyAlgorithm.NULL else encryptionMechanism!!.symmetricKeyAlgorithm != SymmetricKeyAlgorithm.NULL.algorithmId
fun isEncryptedFor(cert: OpenPGPCertificate): Boolean { fun isEncryptedFor(cert: OpenPGPCertificate): Boolean {
return encryptionLayers.asSequence().any { return encryptionLayers.asSequence().any {
@ -472,7 +485,8 @@ class MessageMetadata(val message: Message) {
* @param algorithm symmetric key algorithm used to encrypt the packet. * @param algorithm symmetric key algorithm used to encrypt the packet.
* @param depth nesting depth at which this packet was encountered. * @param depth nesting depth at which this packet was encountered.
*/ */
class EncryptedData(val algorithm: SymmetricKeyAlgorithm, depth: Int) : Layer(depth), Nested { class EncryptedData(val mechanism: MessageEncryptionMechanism, depth: Int) :
Layer(depth), Nested {
/** [SessionKey] used to decrypt the packet. */ /** [SessionKey] used to decrypt the packet. */
var sessionKey: SessionKey? = null var sessionKey: SessionKey? = null
@ -480,6 +494,9 @@ class MessageMetadata(val message: Message) {
/** List of all recipient key ids to which the packet was encrypted for. */ /** List of all recipient key ids to which the packet was encrypted for. */
val recipients: List<KeyIdentifier> = mutableListOf() val recipients: List<KeyIdentifier> = mutableListOf()
val algorithm: SymmetricKeyAlgorithm =
SymmetricKeyAlgorithm.requireFromId(mechanism.symmetricKeyAlgorithm)
fun addRecipients(keyIds: List<KeyIdentifier>) = apply { fun addRecipients(keyIds: List<KeyIdentifier>) = apply {
(recipients as MutableList).addAll(keyIds) (recipients as MutableList).addAll(keyIds)
} }

View file

@ -11,9 +11,11 @@ import java.io.OutputStream
import java.util.zip.Inflater import java.util.zip.Inflater
import java.util.zip.InflaterInputStream import java.util.zip.InflaterInputStream
import openpgp.openPgpKeyId import openpgp.openPgpKeyId
import org.bouncycastle.bcpg.AEADEncDataPacket
import org.bouncycastle.bcpg.BCPGInputStream import org.bouncycastle.bcpg.BCPGInputStream
import org.bouncycastle.bcpg.CompressionAlgorithmTags import org.bouncycastle.bcpg.CompressionAlgorithmTags
import org.bouncycastle.bcpg.KeyIdentifier import org.bouncycastle.bcpg.KeyIdentifier
import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket
import org.bouncycastle.bcpg.UnsupportedPacketVersionException import org.bouncycastle.bcpg.UnsupportedPacketVersionException
import org.bouncycastle.openpgp.PGPCompressedData import org.bouncycastle.openpgp.PGPCompressedData
import org.bouncycastle.openpgp.PGPEncryptedData import org.bouncycastle.openpgp.PGPEncryptedData
@ -27,6 +29,8 @@ import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData
import org.bouncycastle.openpgp.PGPSessionKey import org.bouncycastle.openpgp.PGPSessionKey
import org.bouncycastle.openpgp.PGPSignature import org.bouncycastle.openpgp.PGPSignature
import org.bouncycastle.openpgp.PGPSignatureException import org.bouncycastle.openpgp.PGPSignatureException
import org.bouncycastle.openpgp.api.EncryptedDataPacketType
import org.bouncycastle.openpgp.api.MessageEncryptionMechanism
import org.bouncycastle.openpgp.api.OpenPGPCertificate import org.bouncycastle.openpgp.api.OpenPGPCertificate
import org.bouncycastle.openpgp.api.OpenPGPKey import org.bouncycastle.openpgp.api.OpenPGPKey
import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPPrivateKey import org.bouncycastle.openpgp.api.OpenPGPKey.OpenPGPPrivateKey
@ -318,20 +322,38 @@ class OpenPgpMessageInputStream(
} }
private fun processEncryptedData(): Boolean { private fun processEncryptedData(): Boolean {
LOGGER.debug( // TODO: Replace by dedicated encryption packet type input symbols
"Symmetrically Encrypted Data Packet at depth ${layerMetadata.depth} encountered.")
syntaxVerifier.next(InputSymbol.ENCRYPTED_DATA) syntaxVerifier.next(InputSymbol.ENCRYPTED_DATA)
val encDataList = packetInputStream!!.readEncryptedDataList() val encDataList = packetInputStream!!.readEncryptedDataList()
if (!encDataList.isIntegrityProtected && !encDataList.get(0).isAEAD) { val esks = ESKsAndData(encDataList)
LOGGER.warn("Symmetrically Encrypted Data Packet is not integrity-protected.")
if (!options.isIgnoreMDCErrors()) { when (EncryptedDataPacketType.of(encDataList)!!) {
throw MessageNotIntegrityProtectedException() EncryptedDataPacketType.SEIPDv2 ->
LOGGER.debug(
"Symmetrically Encrypted Integrity Protected Data Packet version 2 at depth " +
"${layerMetadata.depth} encountered.")
EncryptedDataPacketType.SEIPDv1 ->
LOGGER.debug(
"Symmetrically Encrypted Integrity Protected Data Packet version 1 at depth " +
"${layerMetadata.depth} encountered.")
EncryptedDataPacketType.LIBREPGP_OED ->
LOGGER.debug(
"LibrePGP OCB-Encrypted Data Packet at depth " +
"${layerMetadata.depth} encountered.")
EncryptedDataPacketType.SED -> {
LOGGER.debug(
"(Deprecated) Symmetrically Encrypted Data Packet at depth " +
"${layerMetadata.depth} encountered.")
LOGGER.warn("Symmetrically Encrypted Data Packet is not integrity-protected.")
if (!options.isIgnoreMDCErrors()) {
throw MessageNotIntegrityProtectedException()
}
} }
} }
val esks = SortedESKs(encDataList)
LOGGER.debug( LOGGER.debug(
"Symmetrically Encrypted Integrity-Protected Data has ${esks.skesks.size} SKESK(s) and" + "Encrypted Data has ${esks.skesks.size} SKESK(s) and" +
" ${esks.pkesks.size + esks.anonPkesks.size} PKESK(s) from which ${esks.anonPkesks.size} PKESK(s)" + " ${esks.pkesks.size + esks.anonPkesks.size} PKESK(s) from which ${esks.anonPkesks.size} PKESK(s)" +
" have an anonymous recipient.") " have an anonymous recipient.")
@ -359,7 +381,7 @@ class OpenPgpMessageInputStream(
val pgpSk = PGPSessionKey(sk.algorithm.algorithmId, sk.key) val pgpSk = PGPSessionKey(sk.algorithm.algorithmId, sk.key)
val decryptorFactory = api.implementation.sessionKeyDataDecryptorFactory(pgpSk) val decryptorFactory = api.implementation.sessionKeyDataDecryptorFactory(pgpSk)
val layer = EncryptedData(sk.algorithm, layerMetadata.depth + 1) val layer = esks.toEncryptedData(sk, layerMetadata.depth + 1)
val skEncData = encDataList.extractSessionKeyEncryptedData() val skEncData = encDataList.extractSessionKeyEncryptedData()
try { try {
val decrypted = skEncData.getDataStream(decryptorFactory) val decrypted = skEncData.getDataStream(decryptorFactory)
@ -506,7 +528,7 @@ class OpenPgpMessageInputStream(
} }
private fun decryptWithPrivateKey( private fun decryptWithPrivateKey(
esks: SortedESKs, esks: ESKsAndData,
privateKey: PGPKeyPair, privateKey: PGPKeyPair,
decryptionKeyId: SubkeyIdentifier, decryptionKeyId: SubkeyIdentifier,
pkesk: PGPPublicKeyEncryptedData pkesk: PGPPublicKeyEncryptedData
@ -529,7 +551,7 @@ class OpenPgpMessageInputStream(
} }
private fun decryptSKESKAndStream( private fun decryptSKESKAndStream(
esks: SortedESKs, esks: ESKsAndData,
skesk: PGPPBEEncryptedData, skesk: PGPPBEEncryptedData,
decryptorFactory: PBEDataDecryptorFactory decryptorFactory: PBEDataDecryptorFactory
): Boolean { ): Boolean {
@ -537,7 +559,7 @@ class OpenPgpMessageInputStream(
val decrypted = skesk.getDataStream(decryptorFactory) val decrypted = skesk.getDataStream(decryptorFactory)
val sessionKey = SessionKey(skesk.getSessionKey(decryptorFactory)) val sessionKey = SessionKey(skesk.getSessionKey(decryptorFactory))
throwIfUnacceptable(sessionKey.algorithm) throwIfUnacceptable(sessionKey.algorithm)
val encryptedData = EncryptedData(sessionKey.algorithm, layerMetadata.depth + 1) val encryptedData = esks.toEncryptedData(sessionKey, layerMetadata.depth + 1)
encryptedData.sessionKey = sessionKey encryptedData.sessionKey = sessionKey
encryptedData.addRecipients(esks.pkesks.map { it.keyIdentifier }) encryptedData.addRecipients(esks.pkesks.map { it.keyIdentifier })
LOGGER.debug("Successfully decrypted data with passphrase") LOGGER.debug("Successfully decrypted data with passphrase")
@ -555,7 +577,7 @@ class OpenPgpMessageInputStream(
} }
private fun decryptPKESKAndStream( private fun decryptPKESKAndStream(
esks: SortedESKs, esks: ESKsAndData,
decryptionKeyId: SubkeyIdentifier, decryptionKeyId: SubkeyIdentifier,
decryptorFactory: PublicKeyDataDecryptorFactory, decryptorFactory: PublicKeyDataDecryptorFactory,
pkesk: PGPPublicKeyEncryptedData pkesk: PGPPublicKeyEncryptedData
@ -565,11 +587,7 @@ class OpenPgpMessageInputStream(
val sessionKey = SessionKey(pkesk.getSessionKey(decryptorFactory)) val sessionKey = SessionKey(pkesk.getSessionKey(decryptorFactory))
throwIfUnacceptable(sessionKey.algorithm) throwIfUnacceptable(sessionKey.algorithm)
val encryptedData = val encryptedData = esks.toEncryptedData(sessionKey, layerMetadata.depth)
EncryptedData(
SymmetricKeyAlgorithm.requireFromId(
pkesk.getSymmetricAlgorithm(decryptorFactory)),
layerMetadata.depth + 1)
encryptedData.decryptionKey = decryptionKeyId encryptedData.decryptionKey = decryptionKeyId
encryptedData.sessionKey = sessionKey encryptedData.sessionKey = sessionKey
encryptedData.addRecipients(esks.pkesks.plus(esks.anonPkesks).map { it.keyIdentifier }) encryptedData.addRecipients(esks.pkesks.plus(esks.anonPkesks).map { it.keyIdentifier })
@ -730,7 +748,32 @@ class OpenPgpMessageInputStream(
} }
} }
private class SortedESKs(esks: PGPEncryptedDataList) { private class ESKsAndData(private val esks: PGPEncryptedDataList) {
fun toEncryptedData(sk: SessionKey, depth: Int): EncryptedData {
return when (EncryptedDataPacketType.of(esks)!!) {
EncryptedDataPacketType.SED ->
EncryptedData(
MessageEncryptionMechanism.legacyEncryptedNonIntegrityProtected(
sk.algorithm.algorithmId),
depth)
EncryptedDataPacketType.SEIPDv1 ->
EncryptedData(
MessageEncryptionMechanism.integrityProtected(sk.algorithm.algorithmId),
depth)
EncryptedDataPacketType.SEIPDv2 -> {
val seipd2 = esks.encryptedData as SymmetricEncIntegrityPacket
EncryptedData(
MessageEncryptionMechanism.aead(
seipd2.cipherAlgorithm, seipd2.aeadAlgorithm),
depth)
}
EncryptedDataPacketType.LIBREPGP_OED -> {
val oed = esks.encryptedData as AEADEncDataPacket
EncryptedData(MessageEncryptionMechanism.librePgp(oed.algorithm.toInt()), depth)
}
}.also { it.sessionKey = sk }
}
val skesks: List<PGPPBEEncryptedData> val skesks: List<PGPPBEEncryptedData>
val pkesks: List<PGPPublicKeyEncryptedData> val pkesks: List<PGPPublicKeyEncryptedData>
val anonPkesks: List<PGPPublicKeyEncryptedData> val anonPkesks: List<PGPPublicKeyEncryptedData>
@ -739,6 +782,7 @@ class OpenPgpMessageInputStream(
skesks = mutableListOf() skesks = mutableListOf()
pkesks = mutableListOf() pkesks = mutableListOf()
anonPkesks = mutableListOf() anonPkesks = mutableListOf()
for (esk in esks) { for (esk in esks) {
if (esk is PGPPBEEncryptedData) { if (esk is PGPPBEEncryptedData) {
skesks.add(esk) skesks.add(esk)

View file

@ -4,6 +4,7 @@
package org.pgpainless.decryption_verification; package org.pgpainless.decryption_verification;
import org.bouncycastle.openpgp.api.MessageEncryptionMechanism;
import org.junit.JUtils; import org.junit.JUtils;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.pgpainless.algorithm.CompressionAlgorithm; import org.pgpainless.algorithm.CompressionAlgorithm;
@ -29,8 +30,8 @@ public class MessageMetadataTest {
MessageMetadata.Message message = new MessageMetadata.Message(); MessageMetadata.Message message = new MessageMetadata.Message();
MessageMetadata.CompressedData compressedData = new MessageMetadata.CompressedData(CompressionAlgorithm.ZIP, message.getDepth() + 1); MessageMetadata.CompressedData compressedData = new MessageMetadata.CompressedData(CompressionAlgorithm.ZIP, message.getDepth() + 1);
MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(SymmetricKeyAlgorithm.AES_128, compressedData.getDepth() + 1); MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData(MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithm.AES_128.getAlgorithmId()), compressedData.getDepth() + 1);
MessageMetadata.EncryptedData encryptedData1 = new MessageMetadata.EncryptedData(SymmetricKeyAlgorithm.AES_256, encryptedData.getDepth() + 1); MessageMetadata.EncryptedData encryptedData1 = new MessageMetadata.EncryptedData(MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithm.AES_256.getAlgorithmId()), encryptedData.getDepth() + 1);
MessageMetadata.LiteralData literalData = new MessageMetadata.LiteralData(); MessageMetadata.LiteralData literalData = new MessageMetadata.LiteralData();
message.setChild(compressedData); message.setChild(compressedData);

View file

@ -15,18 +15,25 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Set; import java.util.Set;
import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.api.MessageEncryptionMechanism;
import org.bouncycastle.openpgp.api.OpenPGPCertificate; import org.bouncycastle.openpgp.api.OpenPGPCertificate;
import org.bouncycastle.openpgp.api.OpenPGPKey; import org.bouncycastle.openpgp.api.OpenPGPKey;
import org.bouncycastle.util.io.Streams; import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.TestTemplate;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.AEADAlgorithm;
import org.pgpainless.algorithm.AEADCipherMode;
import org.pgpainless.algorithm.DocumentSignatureType; import org.pgpainless.algorithm.DocumentSignatureType;
import org.pgpainless.algorithm.Feature;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.algorithm.OpenPGPKeyVersion;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.decryption_verification.ConsumerOptions; import org.pgpainless.decryption_verification.ConsumerOptions;
import org.pgpainless.decryption_verification.DecryptionStream; import org.pgpainless.decryption_verification.DecryptionStream;
@ -34,7 +41,11 @@ import org.pgpainless.decryption_verification.MessageMetadata;
import org.pgpainless.exception.KeyException; import org.pgpainless.exception.KeyException;
import org.pgpainless.key.SubkeyIdentifier; import org.pgpainless.key.SubkeyIdentifier;
import org.pgpainless.key.TestKeys; import org.pgpainless.key.TestKeys;
import org.pgpainless.key.generation.KeySpec;
import org.pgpainless.key.generation.type.KeyType;
import org.pgpainless.key.generation.type.eddsa_legacy.EdDSALegacyCurve;
import org.pgpainless.key.generation.type.rsa.RsaLength; import org.pgpainless.key.generation.type.rsa.RsaLength;
import org.pgpainless.key.generation.type.xdh_legacy.XDHLegacySpec;
import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.key.protection.UnprotectedKeysProtector; import org.pgpainless.key.protection.UnprotectedKeysProtector;
import org.pgpainless.util.ArmoredOutputStreamFactory; import org.pgpainless.util.ArmoredOutputStreamFactory;
@ -306,4 +317,74 @@ public class EncryptDecryptTest {
EncryptionOptions.encryptCommunications(api) EncryptionOptions.encryptCommunications(api)
.addRecipient(publicKeys)); .addRecipient(publicKeys));
} }
@TestTemplate
@ExtendWith(TestAllImplementations.class)
public void testEncryptToOnlyV4CertWithOnlySEIPD1Feature() throws PGPException, IOException {
PGPainless api = PGPainless.getInstance();
OpenPGPKey v4Key = api.buildKey(OpenPGPKeyVersion.v4)
.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA_LEGACY(EdDSALegacyCurve._Ed25519), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)
.overridePreferredSymmetricKeyAlgorithms(SymmetricKeyAlgorithm.AES_192)
.overrideFeatures(Feature.MODIFICATION_DETECTION)) // the key only supports SEIPD1
.addSubkey(KeySpec.getBuilder(KeyType.XDH_LEGACY(XDHLegacySpec._X25519), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE))
.build();
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
EncryptionStream eOut = api.generateMessage().onOutputStream(bOut)
.withOptions(ProducerOptions.encrypt(EncryptionOptions.encryptCommunications()
.addRecipient(v4Key.toCertificate())));
eOut.write(testMessage.getBytes(StandardCharsets.UTF_8));
eOut.close();
ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray());
DecryptionStream dIn = PGPainless.decryptAndOrVerify()
.onInputStream(bIn)
.withOptions(ConsumerOptions.get().addDecryptionKey(v4Key));
bOut = new ByteArrayOutputStream();
Streams.pipeAll(dIn, bOut);
dIn.close();
assertEquals(testMessage, bOut.toString());
MessageMetadata metadata = dIn.getMetadata();
MessageEncryptionMechanism encryptionMechanism = metadata.getEncryptionMechanism();
assertEquals(
MessageEncryptionMechanism.integrityProtected(SymmetricKeyAlgorithm.AES_192.getAlgorithmId()),
encryptionMechanism);
}
@TestTemplate
@ExtendWith(TestAllImplementations.class)
public void testEncryptToOnlyV6CertWithOnlySEIPD2Features() throws IOException, PGPException {
PGPainless api = PGPainless.getInstance();
OpenPGPKey v6Key = api.buildKey(OpenPGPKeyVersion.v6)
.setPrimaryKey(KeySpec.getBuilder(KeyType.Ed25519(), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)
.overridePreferredAEADAlgorithms(new AEADCipherMode(AEADAlgorithm.OCB, SymmetricKeyAlgorithm.AES_128))
.overrideFeatures(Feature.MODIFICATION_DETECTION_2)) // the key only supports SEIPD2
.addSubkey(KeySpec.getBuilder(KeyType.X25519(), KeyFlag.ENCRYPT_COMMS, KeyFlag.ENCRYPT_STORAGE))
.build();
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
EncryptionStream eOut = api.generateMessage().onOutputStream(bOut)
.withOptions(ProducerOptions.encrypt(EncryptionOptions.encryptCommunications()
.addRecipient(v6Key.toCertificate())));
eOut.write(testMessage.getBytes(StandardCharsets.UTF_8));
eOut.close();
ByteArrayInputStream bIn = new ByteArrayInputStream(bOut.toByteArray());
DecryptionStream dIn = PGPainless.decryptAndOrVerify()
.onInputStream(bIn)
.withOptions(ConsumerOptions.get().addDecryptionKey(v6Key));
bOut = new ByteArrayOutputStream();
Streams.pipeAll(dIn, bOut);
dIn.close();
assertEquals(testMessage, bOut.toString());
MessageMetadata metadata = dIn.getMetadata();
MessageEncryptionMechanism encryptionMechanism = metadata.getEncryptionMechanism();
assertEquals(
MessageEncryptionMechanism.aead(SymmetricKeyAlgorithm.AES_128.getAlgorithmId(), AEADAlgorithm.OCB.getAlgorithmId()),
encryptionMechanism);
}
} }