From 85a4c961ea1b2e83739b91bd1d9845ee031e57e7 Mon Sep 17 00:00:00 2001 From: gefeili Date: Tue, 3 Sep 2024 10:55:55 +0930 Subject: [PATCH 1/3] Fix LibrePGP OCBEncryptedData packet decryption using SKESKv5 and PKESKv3 by Paul Schaub --- .../bcpg/SymmetricKeyEncSessionPacket.java | 36 ++- .../bouncycastle/openpgp/PGPPublicKey.java | 4 + .../openpgp/operator/RFC6637Utils.java | 11 +- .../bc/BcPBEDataDecryptorFactory.java | 25 +- .../JcePBEDataDecryptorFactoryBuilder.java | 25 +- .../openpgp/test/PGPv5KeyTest.java | 4 + .../test/PGPv5MessageDecryptionTest.java | 245 ++++++++++++++++++ 7 files changed, 330 insertions(+), 20 deletions(-) create mode 100644 pg/src/test/java/org/bouncycastle/openpgp/test/PGPv5MessageDecryptionTest.java diff --git a/pg/src/main/java/org/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java index 93d01dc76b..5d3a31054c 100644 --- a/pg/src/main/java/org/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java +++ b/pg/src/main/java/org/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java @@ -1,6 +1,5 @@ package org.bouncycastle.bcpg; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.EOFException; import java.io.IOException; @@ -19,6 +18,7 @@ public class SymmetricKeyEncSessionPacket /** * Version 5 SKESK packet. + * LibrePGP only. * Used only with {@link AEADEncDataPacket AED} packets. */ public static final int VERSION_5 = 5; @@ -40,8 +40,8 @@ public class SymmetricKeyEncSessionPacket private byte[] authTag; // V5, V6 public SymmetricKeyEncSessionPacket( - BCPGInputStream in) - throws IOException + BCPGInputStream in) + throws IOException { this(in, false); } @@ -61,7 +61,32 @@ public SymmetricKeyEncSessionPacket( this.secKeyData = in.readAll(); } - else if (version == VERSION_5 || version == VERSION_6) + else if (version == VERSION_5) + { + encAlgorithm = in.read(); + aeadAlgorithm = in.read(); + + s2k = new S2K(in); + + int ivLen = AEADUtils.getIVLength(aeadAlgorithm); + iv = new byte[ivLen]; // also called nonce + if (in.read(iv) != iv.length) + { + throw new EOFException("Premature end of stream."); + } + + int authTagLen = AEADUtils.getAuthTagLength(aeadAlgorithm); + authTag = new byte[authTagLen]; + + // Read all trailing bytes + byte[] sessKeyAndAuthTag = in.readAll(); + // determine session key length by subtracting auth tag + this.secKeyData = new byte[sessKeyAndAuthTag.length - authTagLen]; + + System.arraycopy(sessKeyAndAuthTag, 0, secKeyData, 0, secKeyData.length); + System.arraycopy(sessKeyAndAuthTag, secKeyData.length, authTag, 0, authTagLen); + } + else if (version == VERSION_6) { // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.3.2-3.2 // SymAlg + AEADAlg + S2KCount + S2K + IV @@ -108,7 +133,6 @@ else if (version == VERSION_5 || version == VERSION_6) { throw new UnsupportedPacketVersionException("Unsupported PGP symmetric-key encrypted session key packet version encountered: " + version); } - } /** @@ -361,4 +385,4 @@ else if (version == VERSION_5 || version == VERSION_6) out.writePacket(hasNewPacketFormat(), SYMMETRIC_KEY_ENC_SESSION, bOut.toByteArray()); } -} +} \ No newline at end of file diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java index 2c788db3df..cdb9ad772a 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java @@ -70,6 +70,10 @@ else if (publicPk.getVersion() == PublicKeyPacket.VERSION_4) { this.keyID = Pack.bigEndianToLong(fingerprint, fingerprint.length - 8); } + else if (publicPk.getVersion() == PublicKeyPacket.LIBREPGP_5) + { + this.keyID = Pack.bigEndianToLong(fingerprint, 0); + } else if (publicPk.getVersion() == PublicKeyPacket.VERSION_6) { this.keyID = Pack.bigEndianToLong(fingerprint, 0); diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/RFC6637Utils.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/RFC6637Utils.java index aa65e34a76..416ac0459f 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/RFC6637Utils.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/RFC6637Utils.java @@ -115,8 +115,15 @@ public static byte[] createUserKeyingMaterial(PublicKeyPacket pubKeyData, KeyFin pOut.write(ecKey.getHashAlgorithm()); pOut.write(ecKey.getSymmetricKeyAlgorithm()); pOut.write(ANONYMOUS_SENDER); - pOut.write(fingerPrintCalculator.calculateFingerprint(pubKeyData)); - + byte[] fp = fingerPrintCalculator.calculateFingerprint(pubKeyData); + if (pubKeyData.getVersion() == PublicKeyPacket.LIBREPGP_5) + { + pOut.write(fp, 0, 20); + } + else + { + pOut.write(fp); + } return pOut.toByteArray(); } diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java index 62aad81ba1..1ed930a52e 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/bc/BcPBEDataDecryptorFactory.java @@ -5,6 +5,7 @@ import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket; import org.bouncycastle.bcpg.SymmetricKeyUtils; +import org.bouncycastle.bcpg.UnsupportedPacketVersionException; import org.bouncycastle.crypto.BlockCipher; import org.bouncycastle.crypto.BufferedBlockCipher; import org.bouncycastle.crypto.InvalidCipherTextException; @@ -89,12 +90,24 @@ public byte[] recoverAEADEncryptedSessionData(SymmetricKeyEncSessionPacket keyDa } byte[] hkdfInfo = keyData.getAAData(); // Between v5 and v6, these bytes differ - int kekLen = SymmetricKeyUtils.getKeyLengthInOctets(keyData.getEncAlgorithm()); - // HKDF - // secretKey := HKDF_sha256(ikm, hkdfInfo).generate() - byte[] kek = BcAEADUtil.generateHKDFBytes(ikm, null, hkdfInfo, kekLen); - final KeyParameter secretKey = new KeyParameter(kek); + KeyParameter secretKey; + if (keyData.getVersion() == SymmetricKeyEncSessionPacket.VERSION_5) + { + secretKey = new KeyParameter(ikm); + } + else if (keyData.getVersion() == SymmetricKeyEncSessionPacket.VERSION_6) + { + // HKDF + // secretKey := HKDF_sha256(ikm, hkdfInfo).generate() + int kekLen = SymmetricKeyUtils.getKeyLengthInOctets(keyData.getEncAlgorithm()); + byte[] kek = BcAEADUtil.generateHKDFBytes(ikm, null, hkdfInfo, kekLen); + secretKey = new KeyParameter(kek); + } + else + { + throw new UnsupportedPacketVersionException("Unsupported SKESK packet version encountered: " + keyData.getVersion()); + } // AEAD AEADBlockCipher aead = BcAEADUtil.createAEADCipher(keyData.getEncAlgorithm(), keyData.getAeadAlgorithm()); @@ -149,4 +162,4 @@ public PGPDataDecryptor createDataDecryptor(SymmetricEncIntegrityPacket seipd, P { return BcAEADUtil.createOpenPgpV6DataDecryptor(seipd, sessionKey); } -} +} \ No newline at end of file diff --git a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java index 3a2677d8e9..74f69dc398 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/operator/jcajce/JcePBEDataDecryptorFactoryBuilder.java @@ -12,6 +12,7 @@ import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket; import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket; import org.bouncycastle.bcpg.SymmetricKeyUtils; +import org.bouncycastle.bcpg.UnsupportedPacketVersionException; import org.bouncycastle.jcajce.util.DefaultJcaJceHelper; import org.bouncycastle.jcajce.util.NamedJcaJceHelper; import org.bouncycastle.jcajce.util.ProviderJcaJceHelper; @@ -140,12 +141,24 @@ public byte[] recoverAEADEncryptedSessionData(SymmetricKeyEncSessionPacket keyDa } byte[] hkdfInfo = keyData.getAAData(); // between v5 and v6, these bytes differ - int kekLen = SymmetricKeyUtils.getKeyLengthInOctets(keyData.getEncAlgorithm()); - // HKDF - // secretKey := HKDF_sha256(ikm, hkdfInfo).generate() - byte[] kek = JceAEADUtil.generateHKDFBytes(ikm, null, hkdfInfo, kekLen); - final SecretKey secretKey = new SecretKeySpec(kek, PGPUtil.getSymmetricCipherName(keyData.getEncAlgorithm())); + SecretKey secretKey; + if (keyData.getVersion() == SymmetricKeyEncSessionPacket.VERSION_5) + { + secretKey = new SecretKeySpec(ikm, PGPUtil.getSymmetricCipherName(keyData.getEncAlgorithm())); + } + else if (keyData.getVersion() == SymmetricKeyEncSessionPacket.VERSION_6) + { + // HKDF + // secretKey := HKDF_sha256(ikm, hkdfInfo).generate() + int kekLen = SymmetricKeyUtils.getKeyLengthInOctets(keyData.getEncAlgorithm()); + byte[] kek = JceAEADUtil.generateHKDFBytes(ikm, null, hkdfInfo, kekLen); + secretKey = new SecretKeySpec(kek, PGPUtil.getSymmetricCipherName(keyData.getEncAlgorithm())); + } + else + { + throw new UnsupportedPacketVersionException("Unsupported SKESK packet version encountered: " + keyData.getVersion()); + } // AEAD Cipher aead = aeadHelper.createAEADCipher(keyData.getEncAlgorithm(), keyData.getAeadAlgorithm()); @@ -201,4 +214,4 @@ public PGPDataDecryptor createDataDecryptor(SymmetricEncIntegrityPacket seipd, P } }; } -} +} \ No newline at end of file diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPv5KeyTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPv5KeyTest.java index 2d7e3638b2..441f1ab9f5 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPv5KeyTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPv5KeyTest.java @@ -90,6 +90,10 @@ private void parseAndEncodeKey() isEncodingEqual("Fingerprint mismatch for the subkey.", Hex.decode("E4557C2B02FFBF4B04F87401EC336AF7133D0F85BE7FD09BAEFD9CAEB8C93965"), it.next().getFingerprint()); + it = secretKeys.getPublicKeys(); + isEquals( "Primary key ID mismatch", 1816212655223104514L, it.next().getKeyID()); + isEquals("Subkey ID mismatch", -1993550735865823413L, it.next().getKeyID()); + bOut = new ByteArrayOutputStream(); BCPGOutputStream pOut = new BCPGOutputStream(bOut, PacketFormat.LEGACY); secretKeys.encode(pOut); diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/PGPv5MessageDecryptionTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPv5MessageDecryptionTest.java new file mode 100644 index 0000000000..574476c008 --- /dev/null +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/PGPv5MessageDecryptionTest.java @@ -0,0 +1,245 @@ +package org.bouncycastle.openpgp.test; + +import org.bouncycastle.bcpg.ArmoredInputStream; +import org.bouncycastle.bcpg.BCPGInputStream; +import org.bouncycastle.bcpg.test.AbstractPacketTest; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openpgp.PGPCompressedData; +import org.bouncycastle.openpgp.PGPEncryptedDataList; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPLiteralData; +import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPPBEEncryptedData; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSessionKey; +import org.bouncycastle.openpgp.PGPSessionKeyEncryptedData; +import org.bouncycastle.openpgp.bc.BcPGPObjectFactory; +import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory; +import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.bc.BcPBEDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.bc.BcSessionKeyDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JceSessionKeyDataDecryptorFactoryBuilder; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.bouncycastle.util.io.Streams; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +public class PGPv5MessageDecryptionTest + extends AbstractPacketTest +{ + // LibrePGP v5 test key "emma" + private static final String V5KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "\n" + + "lGEFXJH05BYAAAAtCSsGAQQB2kcPAQEHQFhZlVcVVtwf+21xNQPX+ecMJJBL0MPd\n" + + "fj75iux+my8QAAAAAAAiAQCHZ1SnSUmWqxEsoI6facIVZQu6mph3cBFzzTvcm5lA\n" + + "Ng5ctBhlbW1hLmdvbGRtYW5AZXhhbXBsZS5uZXSIlgUTFggASCIhBRk0e8mHJGQC\n" + + "X5nfPsLgAA7ZiEiS4fez6kyUAJFZVptUBQJckfTkAhsDBQsJCAcCAyICAQYVCgkI\n" + + "CwIEFgIDAQIeBwIXgAAA9cAA/jiR3yMsZMeEQ40u6uzEoXa6UXeV/S3wwJAXRJy9\n" + + "M8s0AP9vuL/7AyTfFXwwzSjDnYmzS0qAhbLDQ643N+MXGBJ2BZxmBVyR9OQSAAAA\n" + + "MgorBgEEAZdVAQUBAQdA+nysrzml2UCweAqtpDuncSPlvrcBWKU0yfU0YvYWWAoD\n" + + "AQgHAAAAAAAiAP9OdAPppjU1WwpqjIItkxr+VPQRT8Zm/Riw7U3F6v3OiBFHiHoF\n" + + "GBYIACwiIQUZNHvJhyRkAl+Z3z7C4AAO2YhIkuH3s+pMlACRWVabVAUCXJH05AIb\n" + + "DAAAOSQBAP4BOOIR/sGLNMOfeb5fPs/02QMieoiSjIBnijhob2U5AQC+RtOHCHx7\n" + + "TcIYl5/Uyoi+FOvPLcNw4hOv2nwUzSSVAw==\n" + + "=IiS2\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; + // Test message using an OCB encrypted data packet created using GnuPG 2.4.4 + private static final String V5OEDMessage = "-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "hF4D5FV8KwL/v0sSAQdAWGU5E5xLsO57USnkfhhedf5CZCzw7gGsDAkVCyC421Ew\n" + + "d9+XWS6iJEB/+yZRYainM9d9YzFeD4PmqgrDArYD3sBBm/6BAUI8/h1+cbV+BUl5\n" + + "1FMBCQIQT5VZWWb7s7hZ7QlJgK/M5/Ikw+CiShMQgoADRoUw78BL+XSVMKBx/79S\n" + + "/OyxT6obt6eZLt9a7vG+SIA4Wym+IXEkqxVp3KOpIlDJoAzwKw==\n" + + "=syKJ\n" + + "-----END PGP MESSAGE-----\n"; + private static final String V5OEDMessageSessionKey = "9:E376D03AEFB2F6E9EFEB33FDFEFCF92A562D20585B63CE1EC09B57A33B780C3A"; + + // https://www.ietf.org/archive/id/draft-koch-librepgp-01.html#name-sample-ocb-encryption-and-d + private static final byte[] MSG0_SKESK5 = Hex.decode("c33d05070203089f0b7da3e5ea647790" + + "99e326e5400a90936cefb4e8eba08c67" + + "73716d1f2714540a38fcac529949dac5" + + "29d3de31e15b4aeb729e330033dbed"); + private static final byte[] MSG0_OCBED = Hex.decode("d4490107020e5ed2bc1e470abe8f1d64" + + "4c7a6c8a567b0f7701196611a154ba9c" + + "2574cd056284a8ef68035c623d93cc70" + + "8a43211bb6eaf2b27f7c18d571bcd83b" + + "20add3a08b73af15b9a098"); + + @Override + public String getName() + { + return "PGPv5MessageDecryptionTest"; + } + + @Override + public void performTest() + throws Exception + { + decryptSKESK5OCBED1_bc(); + decryptSKESK5OCBED1_jce(); + + decryptOCBED1viaSessionKey_bc(); + decryptOCBED1viaSessionKey_jca(); + + decryptPKESK3OCBED1_bc(); + decryptPKESK3OCBED1_jce(); + } + + private void decryptSKESK5OCBED1_bc() + throws IOException, PGPException + { + String passphrase = "password"; + + ByteArrayInputStream bIn = new ByteArrayInputStream(Arrays.concatenate(MSG0_SKESK5, MSG0_OCBED)); + BCPGInputStream pIn = new BCPGInputStream(bIn); + PGPObjectFactory objFac = new BcPGPObjectFactory(pIn); + PGPEncryptedDataList encList = (PGPEncryptedDataList) objFac.nextObject(); + PGPPBEEncryptedData encData = (PGPPBEEncryptedData) encList.get(0); + InputStream decIn = encData.getDataStream( + new BcPBEDataDecryptorFactory(passphrase.toCharArray(), + new BcPGPDigestCalculatorProvider())); + objFac = new BcPGPObjectFactory(decIn); + PGPLiteralData lit = (PGPLiteralData) objFac.nextObject(); + byte[] plaintext = Streams.readAll(lit.getDataStream()); + isEncodingEqual("Plaintext mismatch", plaintext, "Hello, world!\n".getBytes(StandardCharsets.UTF_8)); + } + + private void decryptSKESK5OCBED1_jce() + throws IOException, PGPException + { + // https://www.ietf.org/archive/id/draft-koch-librepgp-01.html#name-sample-ocb-encryption-and-d + String passphrase = "password"; + ByteArrayInputStream bIn = new ByteArrayInputStream(Arrays.concatenate(MSG0_SKESK5, MSG0_OCBED)); + BCPGInputStream pIn = new BCPGInputStream(bIn); + PGPObjectFactory objFac = new JcaPGPObjectFactory(pIn); + PGPEncryptedDataList encList = (PGPEncryptedDataList) objFac.nextObject(); + PGPPBEEncryptedData encData = (PGPPBEEncryptedData) encList.get(0); + InputStream decIn = encData.getDataStream( + new JcePBEDataDecryptorFactoryBuilder() + .setProvider(new BouncyCastleProvider()) + .build(passphrase.toCharArray())); + objFac = new JcaPGPObjectFactory(decIn); + PGPLiteralData lit = (PGPLiteralData) objFac.nextObject(); + byte[] plaintext = Streams.readAll(lit.getDataStream()); + isEncodingEqual("Plaintext mismatch", plaintext, "Hello, world!\n".getBytes(StandardCharsets.UTF_8)); + } + + private void decryptOCBED1viaSessionKey_bc() + throws IOException, PGPException + { + ByteArrayInputStream bIn = new ByteArrayInputStream(V5OEDMessage.getBytes(StandardCharsets.UTF_8)); + ArmoredInputStream aIn = new ArmoredInputStream(bIn); + BCPGInputStream pIn = new BCPGInputStream(aIn); + PGPObjectFactory objFac = new BcPGPObjectFactory(pIn); + + PGPEncryptedDataList encList = (PGPEncryptedDataList) objFac.nextObject(); + PGPSessionKeyEncryptedData encData = encList.extractSessionKeyEncryptedData(); + SessionKeyDataDecryptorFactory decFac = new BcSessionKeyDataDecryptorFactory( + PGPSessionKey.fromAsciiRepresentation(V5OEDMessageSessionKey)); + InputStream decIn = encData.getDataStream(decFac); + objFac = new BcPGPObjectFactory(decIn); + PGPCompressedData comData = (PGPCompressedData) objFac.nextObject(); + InputStream comIn = comData.getDataStream(); + objFac = new BcPGPObjectFactory(comIn); + PGPLiteralData lit = (PGPLiteralData) objFac.nextObject(); + byte[] plaintext = Streams.readAll(lit.getDataStream()); + isEncodingEqual("Hello World :)".getBytes(StandardCharsets.UTF_8), plaintext); + } + + private void decryptOCBED1viaSessionKey_jca() + throws IOException, PGPException + { + ByteArrayInputStream bIn = new ByteArrayInputStream(V5OEDMessage.getBytes(StandardCharsets.UTF_8)); + ArmoredInputStream aIn = new ArmoredInputStream(bIn); + BCPGInputStream pIn = new BCPGInputStream(aIn); + PGPObjectFactory objFac = new JcaPGPObjectFactory(pIn); + + PGPEncryptedDataList encList = (PGPEncryptedDataList) objFac.nextObject(); + PGPSessionKeyEncryptedData encData = encList.extractSessionKeyEncryptedData(); + SessionKeyDataDecryptorFactory decFac = new JceSessionKeyDataDecryptorFactoryBuilder() + .setProvider(new BouncyCastleProvider()) + .build(PGPSessionKey.fromAsciiRepresentation(V5OEDMessageSessionKey)); + InputStream decIn = encData.getDataStream(decFac); + objFac = new JcaPGPObjectFactory(decIn); + PGPCompressedData comData = (PGPCompressedData) objFac.nextObject(); + InputStream comIn = comData.getDataStream(); + objFac = new JcaPGPObjectFactory(comIn); + PGPLiteralData lit = (PGPLiteralData) objFac.nextObject(); + byte[] plaintext = Streams.readAll(lit.getDataStream()); + isEncodingEqual("Hello World :)".getBytes(StandardCharsets.UTF_8), plaintext); + } + + private void decryptPKESK3OCBED1_bc() + throws IOException, PGPException + { + ByteArrayInputStream bIn = new ByteArrayInputStream(V5KEY.getBytes(StandardCharsets.UTF_8)); + ArmoredInputStream aIn = new ArmoredInputStream(bIn); + BCPGInputStream pIn = new BCPGInputStream(aIn); + PGPObjectFactory objFac = new BcPGPObjectFactory(pIn); + PGPSecretKeyRing secretKeys = (PGPSecretKeyRing) objFac.nextObject(); + + bIn = new ByteArrayInputStream(V5OEDMessage.getBytes(StandardCharsets.UTF_8)); + aIn = new ArmoredInputStream(bIn); + pIn = new BCPGInputStream(aIn); + objFac = new BcPGPObjectFactory(pIn); + PGPEncryptedDataList encList = (PGPEncryptedDataList) objFac.nextObject(); + PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) encList.get(0); + PGPSecretKey decryptionKey = secretKeys.getSecretKey(encData.getKeyID()); + PGPPrivateKey privateKey = decryptionKey.extractPrivateKey(null); + InputStream decIn = encData.getDataStream(new BcPublicKeyDataDecryptorFactory(privateKey)); + pIn = new BCPGInputStream(decIn); + objFac = new BcPGPObjectFactory(pIn); + PGPCompressedData com = (PGPCompressedData) objFac.nextObject(); + InputStream comIn = com.getDataStream(); + objFac = new BcPGPObjectFactory(comIn); + PGPLiteralData lit = (PGPLiteralData) objFac.nextObject(); + byte[] plaintext = Streams.readAll(lit.getDataStream()); + isEncodingEqual("Plaintext mismatch", plaintext, "Hello World :)".getBytes(StandardCharsets.UTF_8)); + } + + private void decryptPKESK3OCBED1_jce() + throws IOException, PGPException + { + ByteArrayInputStream bIn = new ByteArrayInputStream(V5KEY.getBytes(StandardCharsets.UTF_8)); + ArmoredInputStream aIn = new ArmoredInputStream(bIn); + BCPGInputStream pIn = new BCPGInputStream(aIn); + PGPObjectFactory objFac = new JcaPGPObjectFactory(pIn); + PGPSecretKeyRing secretKeys = (PGPSecretKeyRing) objFac.nextObject(); + + bIn = new ByteArrayInputStream(V5OEDMessage.getBytes(StandardCharsets.UTF_8)); + aIn = new ArmoredInputStream(bIn); + pIn = new BCPGInputStream(aIn); + objFac = new JcaPGPObjectFactory(pIn); + PGPEncryptedDataList encList = (PGPEncryptedDataList) objFac.nextObject(); + PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) encList.get(0); + PGPSecretKey decryptionKey = secretKeys.getSecretKey(encData.getKeyID()); + PGPPrivateKey privateKey = decryptionKey.extractPrivateKey(null); + InputStream decIn = encData.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder() + .setProvider(new BouncyCastleProvider()) + .build(privateKey)); + pIn = new BCPGInputStream(decIn); + objFac = new JcaPGPObjectFactory(pIn); + PGPCompressedData com = (PGPCompressedData) objFac.nextObject(); + InputStream comIn = com.getDataStream(); + objFac = new JcaPGPObjectFactory(comIn); + PGPLiteralData lit = (PGPLiteralData) objFac.nextObject(); + byte[] plaintext = Streams.readAll(lit.getDataStream()); + isEncodingEqual("Plaintext mismatch", plaintext, "Hello World :)".getBytes(StandardCharsets.UTF_8)); + } + + public static void main(String[] args) + { + runTest(new PGPv5MessageDecryptionTest()); + } +} \ No newline at end of file From 980bffe721963c988a640fe8690c2e711655400f Mon Sep 17 00:00:00 2001 From: gefeili Date: Tue, 3 Sep 2024 11:51:36 +0930 Subject: [PATCH 2/3] Refactor SymmetricKeyEncSessionPacket --- .../bcpg/SymmetricKeyEncSessionPacket.java | 61 ++++++------------- .../openpgp/test/RegressionTest.java | 5 +- 2 files changed, 21 insertions(+), 45 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java b/pg/src/main/java/org/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java index 5d3a31054c..0885e2dd15 100644 --- a/pg/src/main/java/org/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java +++ b/pg/src/main/java/org/bouncycastle/bcpg/SymmetricKeyEncSessionPacket.java @@ -45,6 +45,7 @@ public SymmetricKeyEncSessionPacket( { this(in, false); } + public SymmetricKeyEncSessionPacket( BCPGInputStream in, boolean newPacketFormat) @@ -61,57 +62,29 @@ public SymmetricKeyEncSessionPacket( this.secKeyData = in.readAll(); } - else if (version == VERSION_5) + else if (version == VERSION_5 || version == VERSION_6) { - encAlgorithm = in.read(); - aeadAlgorithm = in.read(); - - s2k = new S2K(in); - - int ivLen = AEADUtils.getIVLength(aeadAlgorithm); - iv = new byte[ivLen]; // also called nonce - if (in.read(iv) != iv.length) + int ivLen = 0; + if (version == VERSION_6) { - throw new EOFException("Premature end of stream."); + // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.3.2-3.2 + // SymAlg + AEADAlg + S2KCount + S2K + IV + ivLen = in.read(); // next5Fields5Count } - - int authTagLen = AEADUtils.getAuthTagLength(aeadAlgorithm); - authTag = new byte[authTagLen]; - - // Read all trailing bytes - byte[] sessKeyAndAuthTag = in.readAll(); - // determine session key length by subtracting auth tag - this.secKeyData = new byte[sessKeyAndAuthTag.length - authTagLen]; - - System.arraycopy(sessKeyAndAuthTag, 0, secKeyData, 0, secKeyData.length); - System.arraycopy(sessKeyAndAuthTag, secKeyData.length, authTag, 0, authTagLen); - } - else if (version == VERSION_6) - { - // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.3.2-3.2 - // SymAlg + AEADAlg + S2KCount + S2K + IV - int next5Fields5Count = in.read(); encAlgorithm = in.read(); aeadAlgorithm = in.read(); + if (version == VERSION_6) + { + int s2kOctetCount = in.read(); + ivLen = ivLen - 3 - s2kOctetCount; + } + else + { + ivLen = AEADUtils.getIVLength(aeadAlgorithm); + } - // https://www.ietf.org/archive/id/draft-ietf-openpgp-crypto-refresh-07.html#section-5.3.2-3.5 - int s2kOctetCount = in.read(); - - //TODO: use this line to replace the following code? s2k = new S2K(in); -// s2kBytes = new byte[s2kOctetCount]; -// in.readFully(s2kBytes); -// try -// { -// s2k = new S2K(new ByteArrayInputStream(s2kBytes)); -// } -// catch (UnsupportedPacketVersionException e) -// { -// -// // We gracefully catch the error. -// } - - int ivLen = next5Fields5Count - 3 - s2kOctetCount; + iv = new byte[ivLen]; // also called nonce if (in.read(iv) != iv.length) { diff --git a/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java b/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java index 6c5ea8dd24..82bb68e4b8 100644 --- a/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java +++ b/pg/src/test/java/org/bouncycastle/openpgp/test/RegressionTest.java @@ -76,7 +76,10 @@ public class RegressionTest new Curve25519PrivateKeyEncodingTest(), new EdDSAKeyConversionWithLeadingZeroTest(), - new ECDSAKeyPairTest() + new ECDSAKeyPairTest(), + + new PGPv5KeyTest(), + new PGPv5MessageDecryptionTest(), }; public static void main(String[] args) From 1bbbd2f90faad1ab12fb3dd84538a2a9850abe6b Mon Sep 17 00:00:00 2001 From: gefeili Date: Tue, 3 Sep 2024 12:23:36 +0930 Subject: [PATCH 3/3] Refactor PGPPublicKey.init --- pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java index cdb9ad772a..d69af97799 100644 --- a/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java +++ b/pg/src/main/java/org/bouncycastle/openpgp/PGPPublicKey.java @@ -70,11 +70,7 @@ else if (publicPk.getVersion() == PublicKeyPacket.VERSION_4) { this.keyID = Pack.bigEndianToLong(fingerprint, fingerprint.length - 8); } - else if (publicPk.getVersion() == PublicKeyPacket.LIBREPGP_5) - { - this.keyID = Pack.bigEndianToLong(fingerprint, 0); - } - else if (publicPk.getVersion() == PublicKeyPacket.VERSION_6) + else if (publicPk.getVersion() == PublicKeyPacket.LIBREPGP_5 || publicPk.getVersion() == PublicKeyPacket.VERSION_6) { this.keyID = Pack.bigEndianToLong(fingerprint, 0); }