MM Ary
MM Ary

Reputation: 11

Invalid header encountered while PGP verifying signature

My requirement:

I am having the below exception while verifying the signature:

Exception in thread "main" java.io.IOException: invalid header encountered at org.bouncycastle.bcpg.BCPGInputStream.readPacket(Unknown Source) at org.bouncycastle.openpgp.PGPLiteralData.(Unknown Source) at org.bouncycastle.openpgp.PGPObjectFactory.nextObject(Unknown Source) at com.groupId.auth.PGPExample.verify(PGPExample.java:253) at com.groupId.auth.PGPExample.main(PGPExample.java:52)

The above exception is on line -> while ((obj = pgpObjectFactory.nextObject()) != null) {... when I click on 'PGPExample.java:253', my cursor goes to 'pgpObjectFactory.'

I am signing the message using the below code snippet:

private static byte[] sign(String message) throws IOException, PGPException {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    OutputStream signedOut = new ArmoredOutputStream(out);

    // Sign the message using sender's private key
    PGPSecretKey senderPrivateKey = PGPUtils.getSenderPrivateKey();

   BCPGOutputStream bOut = new BCPGOutputStream(signedOut);
    PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
            new JcaPGPContentSignerBuilder(senderPrivateKey.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA256)
                    .setProvider("BC")
    );

   String password = "password";

   signatureGenerator.init(PGPSignature.BINARY_DOCUMENT,
            senderPrivateKey.extractPrivateKey(
                    new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(password.toCharArray())));

    Iterator<?> it = senderPrivateKey.getPublicKey().getUserIDs();
    if (it.hasNext()) {
        PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
        spGen.addSignerUserID(false, (String) it.next());
        signatureGenerator.setHashedSubpackets(spGen.generate());
    }

  InputStream in = PGPUtil.getDecoderStream(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)));
    byte[] buf = new byte[8192];
    int len;
    while ((len = in.read(buf)) > 0) {
        signatureGenerator.update(buf, 0, len);
    }
    in.close();

    // Generate the signature
    signatureGenerator.generate().encode(bOut);
    bOut.flush();

    // Close streams
    bOut.close();
    signedOut.close();

    return out.toByteArray();

 }

I am verifying the signature using the below code snippet:

private static boolean verify(String message, byte[] signature) throws IOException, PGPException {
      ByteArrayInputStream in = new ByteArrayInputStream(signature);
    PGPObjectFactory pgpObjectFactory = new PGPObjectFactory(in, new JcaKeyFingerprintCalculator());

    Object obj;
    while ((obj = pgpObjectFactory.nextObject()) != null) {
        if (obj instanceof PGPSignatureList) {
            PGPSignatureList signatureList = (PGPSignatureList) obj;
            if (signatureList.isEmpty()) {
                throw new IllegalArgumentException("No signature found in the data");
            }
            PGPSignature pgpSignature = signatureList.get(0);
            pgpSignature.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), PGPUtils.getReceiverPublicKey());
            pgpSignature.update(message.getBytes(StandardCharsets.UTF_8));
            return pgpSignature.verify();
        }
    }
    throw new IllegalArgumentException("No signature found in the data");

}

My main method call:

  public static void main(String[] args) throws Exception {
        Security.addProvider(new BouncyCastleProvider());

    byte[] signature = sign(message);
    System.out.println("Signature: ");
    System.out.println(new String(signature));

    boolean verified = verify(message, signature);
    System.out.println("Signature Verified: " + verified);
  }

Below is my pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.8</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.groupId</groupId>
<artifactId>auth</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>auth</name>
<description>Demo project for Spring Boot</description>
<properties>
    <java.version>1.8</java.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcpg-jdk18on</artifactId>
        <version>1.77</version>
    </dependency>

    <dependency>
        <groupId>commons-codec</groupId>
        <artifactId>commons-codec</artifactId>
        <version>1.16.1</version>
    </dependency>

</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

I tried hard to resolve this issue but couldn't find the solution, kindly give me suggestions or solution for this.

I want to sign my message & then I tried to verify it but when I use the above code snippets I have provided I am not getting verified successfully or true result instead I am having this exception "invalid header encountered".

Upvotes: 0

Views: 888

Answers (1)

dave_thompson_085
dave_thompson_085

Reputation: 38821

You're writing the signature in armored format but trying to read it in binary format; that's wrong. After fixing that I get the PGPSignatureList as expected, but the signature doesn't verify because you aren't using the same data in verify as you used in sign and of course the whole point of digital signature is that it rejects any change or difference in the data. Also it's misleading to say you're verifying with the receiver public-key because you must use the public-key of the sender copied to the receiver.

The following changes read the signature and verify successfully:

        InputStream in = new ArmoredInputStream( new ByteArrayInputStream(signature) );
        PGPObjectFactory pgpObjectFactory = new PGPObjectFactory(in, new JcaKeyFingerprintCalculator());
        Object obj;
        while ((obj = pgpObjectFactory.nextObject()) != null) {
            if (obj instanceof PGPSignatureList) {
                PGPSignatureList signatureList = (PGPSignatureList) obj;
                if (signatureList.isEmpty()) {
                    throw new IllegalArgumentException("No signature found in the data");
                }
                PGPSignature pgpSignature = signatureList.get(0);
                pgpSignature.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), senderPublicKey);
                //--pgpSignature.update(message.getBytes(/*default*/));
                try( InputStream data = new ArmoredInputStream(new ByteArrayInputStream(message.getBytes())) ){
                    byte[] buf2 = new byte[1024]; 
                    for(int len2; (len2 = data.read(buf2))>0; ) pgpSignature.update(buf2,0,len2);
                }
                System.out.println( pgpSignature.verify() );
            }
        }
        throw new IllegalArgumentException("No signature found in the data");

and then throw an exception claiming no signature was found even though it was -- which I assume you wanted because you coded it that way.

Note if message contains a PGP armored message (and PGP signatures are only defined to be valid on messages) you don't need message.getBytes("UTF-8") because armored uses a restricted characterset (plus in Java 20 up the default is now UTF-8 anyway). OTOH you can't safely store any binary data in a Java String and if you tried to store a PGP binary message then .getBytes("UTF-8") will destroy it; the constraints of UTF-8 encoding are massively incompatible with those for PGP binary.

Upvotes: 0

Related Questions