JDK-8243338 : RSSSSA-PSS keys generated from similar KeySpec don't match
  • Type: Bug
  • Component: security-libs
  • Sub-Component: javax.crypto
  • Affected Version: 15
  • Priority: P3
  • Status: Resolved
  • Resolution: Not an Issue
  • Submitted: 2020-04-22
  • Updated: 2020-04-25
  • Resolved: 2020-04-25
Related Reports
Blocks :  
Relates :  
Description
Steps to reproduce,

- Generate a KeyPair for RSASSA-PSS.
- Generate another using any of following way,
KeyFactory kf = KeyFactory.getInstance(ALGO, PROVIDER);
        switch (type) {
            case PUBLIC_KEY:
                return new Key[]{
                    kf.generatePublic(kf.getKeySpec(key, RSAPublicKeySpec.class)),
                    kf.generatePublic(new X509EncodedKeySpec(key.getEncoded())),
                    kf.generatePublic(new RSAPublicKeySpec(
                    ((RSAPublicKey) key).getModulus(),
                    ((RSAPublicKey) key).getPublicExponent())),
                    kf.translateKey(key)
                };
            case PRIVATE_KEY:
                return new Key[]{
                    kf.generatePrivate(kf.getKeySpec(key, RSAPrivateKeySpec.class)),
                    kf.generatePrivate(new PKCS8EncodedKeySpec(key.getEncoded())),
                    kf.generatePrivate(new RSAPrivateKeySpec(((RSAPrivateKey) key)
                    .getModulus(),
                    ((RSAPrivateKey) key).getPrivateExponent())),
                    kf.translateKey(key)
                };
        }
Compare the original key with the generated one from above step,
- Keys equals() will fail
- Keys hashCode() will not same too. (In fact i see it's -ve, which may be because of overflow)
- Keys getEncoded() will generate different binaries too.

It is also same when the keys are serialized Or generated from any other source like OpenSSL.
Comments
In the description, there are 4 tests each for PublicKey and PrivateKey. If I change the key algorithm to RSA, the 4 PublicKey tests pass but the 1st and 3rd PrivateKey test (case 0 and case 2) using RSAPrivateKeySpec fail. The reason for the failed check is that you are comparing a RSAPrivateCrtKey object against a RSAPrivateKey object. By default, SunRsaSign provider generates RSAPrivateCrtKey and when you use RSAPrivateKeySpec to retrieve the values, you lose the CRT fields which cause the comparison to fail. If you use RSAPrivateCrtKeySpec, the tests shall pass. Now, after fixing the CRT problem stated above and change the key algorithm to RSASSA-PSS, the test fails and there are additional PSS-specific things you need to fix in the test. RSASSA-PSS keys may contain parameters vs RSA keys must NOT contain parameters. Thus, for PSS keys, you need to make sure to supply the key parameters to the converted/regenerated key objects in the 3rd test (case 2) for PublicKey and PrivateKey. Here is my updated test based on the supplied sample code which runs fine: ------------------------------------------------ import java.io.*; import java.util.*; import java.math.BigInteger; import java.security.*; import java.security.interfaces.*; import java.security.spec.*; public class TestPSSKeySupport2 { private static final String ALGO = "RSASSA-PSS"; //private static final String ALGO = "RSA"; private static boolean status = true; /** * Test that key1 (reference key) and key2 (key to be tested) are * equivalent */ private static void checkEquality(Key key1, Key key2) throws Exception { if (key1.equals(key2) == false) { status = false; System.out.println("Keys not equal"); } if (key1.hashCode() != key2.hashCode()) { status = false; System.out.println("Hashcode not equal"); } if (Arrays.equals(key1.getEncoded(), key2.getEncoded()) == false) { status = false; System.out.println("Encodings not equal"); } } private static void test(KeyFactory kf, Key key, int testIdx) throws Exception { System.out.println("Running test " + testIdx); if (key instanceof PublicKey) { PublicKey pk = (PublicKey) key; PublicKey pk2 = null; switch (testIdx) { case 0: pk2 = kf.generatePublic(kf.getKeySpec (pk, RSAPublicKeySpec.class)); break; case 1: pk2 = kf.generatePublic (new X509EncodedKeySpec(pk.getEncoded())); break; case 2: pk2 = kf.generatePublic(new RSAPublicKeySpec( ((RSAPublicKey) pk).getModulus(), ((RSAPublicKey) pk).getPublicExponent(), ((RSAPublicKey) pk).getParams())); break; case 3: pk2 = (PublicKey) kf.translateKey(pk); break; }; checkEquality(pk, pk2); } else { PrivateKey pk = (PrivateKey) key; PrivateKey pk2 = null; switch (testIdx) { case 0: pk2 = kf.generatePrivate(kf.getKeySpec (pk, RSAPrivateCrtKeySpec.class)); break; case 1: pk2 = kf.generatePrivate(new PKCS8EncodedKeySpec (pk.getEncoded())); break; case 2: RSAPrivateCrtKey crtKey = (RSAPrivateCrtKey) pk; pk2 = kf.generatePrivate(new RSAPrivateCrtKeySpec (crtKey.getModulus(), crtKey.getPublicExponent(), crtKey.getPrivateExponent(), crtKey.getPrimeP(), crtKey.getPrimeQ(), crtKey.getPrimeExponentP(), crtKey.getPrimeExponentQ(), crtKey.getCrtCoefficient(), crtKey.getParams() )); break; case 3: pk2 = (PrivateKey) kf.translateKey(pk); break; }; checkEquality(pk, pk2); } } private static void checkKeyPair(KeyPair kp) throws Exception { PublicKey pubKey = kp.getPublic(); if (!(pubKey instanceof RSAPublicKey)) { throw new Exception("Error: public key should be RSAPublicKey"); } PrivateKey privKey = kp.getPrivate(); if (!(privKey instanceof RSAPrivateKey)) { throw new Exception("Error: private key should be RSAPrivateKey"); } } public static void main(String[] args) throws Exception { KeyPairGenerator kpg = KeyPairGenerator.getInstance(ALGO, "SunRsaSign"); // Algorithm-Independent Initialization kpg.initialize(2048); KeyPair kp = kpg.generateKeyPair(); checkKeyPair(kp); BigInteger pubExp = ((RSAPublicKey)kp.getPublic()).getPublicExponent(); // Algorithm-specific Initialization PSSParameterSpec params = new PSSParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, 32, 1); kpg.initialize(new RSAKeyGenParameterSpec(2048, pubExp, params)); KeyPair kp2 = kpg.generateKeyPair(); checkKeyPair(kp2); KeyFactory kf = KeyFactory.getInstance(ALGO, "SunRsaSign"); System.out.println("PublicKey: " + kp.getPublic()); test(kf, kp.getPublic(), 0); test(kf, kp.getPublic(), 1); test(kf, kp.getPublic(), 2); test(kf, kp.getPublic(), 3); System.out.println("PrivateKey: " + kp.getPrivate()); test(kf, kp.getPrivate(), 0); test(kf, kp.getPrivate(), 1); test(kf, kp.getPrivate(), 2); test(kf, kp.getPrivate(), 3); test(kf, kp2.getPublic(), 0); test(kf, kp2.getPublic(), 1); test(kf, kp2.getPublic(), 2); test(kf, kp2.getPublic(), 3); test(kf, kp2.getPrivate(), 0); test(kf, kp2.getPrivate(), 1); test(kf, kp2.getPrivate(), 2); test(kf, kp2.getPrivate(), 3); if (status) { System.out.println("Test Passed"); } else { throw new RuntimeException("One or more tests failed"); } } }
25-04-2020