Skip to content

Fix EC Private Key equality mismatch after reconstruction from ECPrivateKeySpec #1053

@farshadasl

Description

@farshadasl

Description

There is a discrepancy in the byte array returned by getEncoded() when an Elliptic Curve (EC) private key is reconstructed using KeyFactory. While the mathematical private scalar () remains identical, the DER-encoded PKCS#8 structure changes in length and content.

That is causing the JTREG java/security/KeyAgreement/KeySpecTest.java failure.

Steps to Reproduce

  1. Generate an EC Private Key using KeyPairGenerator.
  2. Extract the ECPrivateKeySpec from the generated key.
  3. Reconstruct a new PrivateKey object using KeyFactory.generatePrivate(spec).
  4. Compare original.getEncoded() and reconstructed.getEncoded().

Observed Behavior

Arrays.equals() returns false.

  • Original Key: Often a "minimal" PKCS#8 structure (approx. 67 bytes).
  • Reconstructed Key: Often a "full" PKCS#8 structure (approx. 150 bytes) containing optional attributes like the Public Key bits.

Expected Behavior

While the binary encodings differ due to ASN.1 optional fields, the keys should be recognized as logically/cryptographically equivalent.

Technical Analysis

The OpenJCEPlus (or SunEC) provider expands the encoded format during reconstruction.

  • Key 1 (Bare): VERSION + ALGO_ID + OCTET_STRING(S)
  • Key 2 (Full): VERSION + ALGO_ID + OCTET_STRING(S) + [0]PARAMS + [1]PUBLIC_KEY

The test to reproduce:

import java.security.*;
import java.security.interfaces.ECPrivateKey;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.KeySpec;
import java.util.HexFormat;

public class ECDiffTest {

    public static void main(String[] args) throws Exception {
        // Use your specific provider
        String provider = "OpenJCEPlus";

        // 1. Generate an initial EC Private Key (e.g., NIST P-256)
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", provider);
        kpg.initialize(256);
        PrivateKey originalKey = kpg.generateKeyPair().getPrivate();

        byte[] originalEncoded = originalKey.getEncoded();

        // 2. Build a new key using the KeySpec of the first one
        // This process often triggers the Provider to add optional metadata (like the Public Key)
        KeyFactory kf = KeyFactory.getInstance("EC", provider);
        KeySpec priSpec = kf.getKeySpec(originalKey, ECPrivateKeySpec.class);
        PrivateKey reconstructedKey = kf.generatePrivate(priSpec);

        byte[] reconstructedEncoded = reconstructedKey.getEncoded();

        // 3. Comparison Output
        System.out.println("=== Comparison Results ===");
        System.out.println("Original Length:      " + originalEncoded.length + " bytes");
        System.out.println("Reconstructed Length: " + reconstructedEncoded.length + " bytes");

        // Binary check
        boolean isBinaryEqual = java.util.Arrays.equals(originalEncoded, reconstructedEncoded);
        System.out.println("Are encoded bytes identical? " + (isBinaryEqual ? "YES" : "NO"));

        // Mathematical check
        boolean isMathematicallyEqual = areKeysEqual(originalKey, reconstructedKey);
        System.out.println("Are the keys mathematically identical? " + (isMathematicallyEqual ? "YES" : "NO"));

        if (!isBinaryEqual) {
            System.out.println("\n--- Hex Comparison ---");
            System.out.println("Original Hex:\n" + HexFormat.of().formatHex(originalEncoded));
            System.out.println("\nReconstructed Hex:\n" + HexFormat.of().formatHex(reconstructedEncoded));
            System.out.println("\nNote: The second key is longer because it likely includes the Public Key bits and Curve OID metadata.");
        }
    }

    /**
     * Compares two PrivateKeys by looking at the Private Scalar (S)
     * and the Curve Parameters rather than the encoded bytes.
     */
    public static boolean areKeysEqual(PrivateKey k1, PrivateKey k2) {
        if (k1 == k2) return true;
        if (!(k1 instanceof ECPrivateKey) || !(k2 instanceof ECPrivateKey)) return false;

        ECPrivateKey ec1 = (ECPrivateKey) k1;
        ECPrivateKey ec2 = (ECPrivateKey) k2;

        // 1. Compare the secret scalar (S)
        if (!ec1.getS().equals(ec2.getS())) {
            return false;
        }

        // 2. Compare the Curve Parameters
        ECParameterSpec s1 = ec1.getParams();
        ECParameterSpec s2 = ec2.getParams();

        return s1.getCurve().equals(s2.getCurve()) &&
                s1.getGenerator().equals(s2.getGenerator()) &&
                s1.getOrder().equals(s2.getOrder()) &&
                s1.getCofactor() == s2.getCofactor();
    }
}

The failing JTREG

java/security/KeyAgreement/KeySpecTest.java

The root cause PR

#682

Suggested fix

Revert the EC private key equals method to check getS() and params (the same as the above test).

FYI @jasonkatonica @KostasTsiounis

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions