Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java: OpenSSLRSAPrivateCrtKey vs. OpenSSLRSAPrivateKey

Tags:

java

android

rsa

When I generate a private key using the following code

KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair kp = kpg.genKeyPair();
PrivateKey privateKey = kp.getPrivate();

I get OpenSSLRSAPrivateCrtKey representation of PrivateKey. Then I turn it into byte array, save and then restore using this code:

byte[] encodedPrivateKey = null;
fileInputStream = new FileInputStream(file);
encodedPrivateKey = new byte[(int) file.length()];
fileInputStream.read(encodedPrivateKey);
fileInputStream.close();

KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);

But this time I get OpenSSLRSAPrivateKey representation.

I want to know what is the difference between OpenSSLRSAPrivateCrtKey and OpenSSLRSAPrivateKey. Also which representation of PrivateKey should I use to decrypt information encrypted by public key from this keypair?

like image 212
DH28 Avatar asked Nov 08 '22 18:11

DH28


1 Answers

OpenSSLRSAPrivateCrtKey extends OpenSSLRSAPrivateKey.

New members

private BigInteger publicExponent;

private BigInteger primeP;

private BigInteger primeQ;

private BigInteger primeExponentP;

private BigInteger primeExponentQ;

private BigInteger crtCoefficient;

New constructor, calling init

OpenSSLRSAPrivateCrtKey(RSAPrivateCrtKeySpec rsaKeySpec) throws InvalidKeySpecException {
    super(init(rsaKeySpec));
}

init override

Initially it was

private static OpenSSLKey init(RSAPrivateKeySpec rsaKeySpec) throws InvalidKeySpecException {
    final BigInteger modulus = rsaKeySpec.getModulus();
    final BigInteger privateExponent = rsaKeySpec.getPrivateExponent();

    if (modulus == null) {
        throw new InvalidKeySpecException("modulus == null");
    } else if (privateExponent == null) {
        throw new InvalidKeySpecException("privateExponent == null");
    }

    try {
        return new OpenSSLKey(NativeCrypto.EVP_PKEY_new_RSA(
                modulus.toByteArray(),
                null,
                privateExponent.toByteArray(),
                null,
                null,
                null,
                null,
                null));
    } catch (Exception e) {
        throw new InvalidKeySpecException(e);
    }
}

Now it is

private static OpenSSLKey init(RSAPrivateCrtKeySpec rsaKeySpec) throws InvalidKeySpecException {
    BigInteger modulus = rsaKeySpec.getModulus();
    BigInteger privateExponent = rsaKeySpec.getPrivateExponent();

    if (modulus == null) {
        throw new InvalidKeySpecException("modulus == null");
    } else if (privateExponent == null) {
        throw new InvalidKeySpecException("privateExponent == null");
    }

    try {
        /*
         * OpenSSL uses the public modulus to do RSA blinding. If
         * the public modulus is not available, the call to
         * EVP_PKEY_new_RSA will turn off blinding for this key
         * instance.
         */
        final BigInteger publicExponent = rsaKeySpec.getPublicExponent();
        final BigInteger primeP = rsaKeySpec.getPrimeP();
        final BigInteger primeQ = rsaKeySpec.getPrimeQ();
        final BigInteger primeExponentP = rsaKeySpec.getPrimeExponentP();
        final BigInteger primeExponentQ = rsaKeySpec.getPrimeExponentQ();
        final BigInteger crtCoefficient = rsaKeySpec.getCrtCoefficient();

        return new OpenSSLKey(NativeCrypto.EVP_PKEY_new_RSA(
                modulus.toByteArray(),
                publicExponent == null ? null : publicExponent.toByteArray(),
                privateExponent.toByteArray(),
                primeP == null ? null : primeP.toByteArray(),
                primeQ == null ? null : primeQ.toByteArray(),
                primeExponentP == null ? null : primeExponentP.toByteArray(),
                primeExponentQ == null ? null : primeExponentQ.toByteArray(),
                crtCoefficient == null ? null : crtCoefficient.toByteArray()));
    } catch (Exception e) {
        throw new InvalidKeySpecException(e);
    }
}

As we can see, when OpenSSLKey is instantiated, we pass NativeCrypto.EVP_PKEY_new_RSA in both cases. OpenSSLRSAPrivateCrtKey passes:

  • publicExponent
  • primeP
  • primeQ
  • primeExponentP
  • primeExponentQ
  • crtCoefficient

getInstance

It changed from

static OpenSSLRSAPrivateKey getInstance(OpenSSLKey key) {
    byte[][] params = NativeCrypto.get_RSA_private_params(key.getNativeRef());
    if (params[1] != null) {
        return new OpenSSLRSAPrivateCrtKey(key, params);
    }
    return new OpenSSLRSAPrivateKey(key, params);
}

to

static OpenSSLKey getInstance(RSAPrivateCrtKey rsaPrivateKey) throws InvalidKeyException {
    /**
     * If the key is not encodable (PKCS11-like key), then wrap it and use
     * JNI upcalls to satisfy requests.
     */
    if (rsaPrivateKey.getFormat() == null) {
        return wrapPlatformKey(rsaPrivateKey);
    }

    BigInteger modulus = rsaPrivateKey.getModulus();
    BigInteger privateExponent = rsaPrivateKey.getPrivateExponent();

    if (modulus == null) {
        throw new InvalidKeyException("modulus == null");
    } else if (privateExponent == null) {
        throw new InvalidKeyException("privateExponent == null");
    }

    try {
        /*
         * OpenSSL uses the public modulus to do RSA blinding. If
         * the public modulus is not available, the call to
         * EVP_PKEY_new_RSA will turn off blinding for this key
         * instance.
         */
        final BigInteger publicExponent = rsaPrivateKey.getPublicExponent();
        final BigInteger primeP = rsaPrivateKey.getPrimeP();
        final BigInteger primeQ = rsaPrivateKey.getPrimeQ();
        final BigInteger primeExponentP = rsaPrivateKey.getPrimeExponentP();
        final BigInteger primeExponentQ = rsaPrivateKey.getPrimeExponentQ();
        final BigInteger crtCoefficient = rsaPrivateKey.getCrtCoefficient();

        return new OpenSSLKey(NativeCrypto.EVP_PKEY_new_RSA(
                modulus.toByteArray(),
                publicExponent == null ? null : publicExponent.toByteArray(),
                privateExponent.toByteArray(),
                primeP == null ? null : primeP.toByteArray(),
                primeQ == null ? null : primeQ.toByteArray(),
                primeExponentP == null ? null : primeExponentP.toByteArray(),
                primeExponentQ == null ? null : primeExponentQ.toByteArray(),
                crtCoefficient == null ? null : crtCoefficient.toByteArray()));
    } catch (Exception e) {
        throw new InvalidKeyException(e);
    }
}

The new version computes

  • modulus
  • privateExponent

If this failed, then some exception is thrown. Otherwise, it continues to compute stuff, namely:

  • publicExponent
  • primeP
  • primeQ
  • primeExponentP
  • primeExponentQ
  • crtCoefficient

and then generates an OpenSSLKey similarly as in init. I think this could be refactored, because it involves some code duplication, but my opinion is less important than the differences.

readParams

This changed from

void readParams(byte[][] params) {
    if (params[0] == null) {
        throw new NullPointerException("modulus == null");
    } else if (params[2] == null && !key.isHardwareBacked()) {
        throw new NullPointerException("privateExponent == null");
    }

    modulus = new BigInteger(params[0]);

    // ENGINE-based keys are not guaranteed to have a private exponent.
    if (params[2] != null) {
        privateExponent = new BigInteger(params[2]);
    }
}

to

@Override
synchronized void readParams(byte[][] params) {
    super.readParams(params);
    // params[0] read in super.readParams
    if (params[1] != null) {
        publicExponent = new BigInteger(params[1]);
    }
    // params[2] read in super.readParams
    if (params[3] != null) {
        primeP = new BigInteger(params[3]);
    }
    if (params[4] != null) {
        primeQ = new BigInteger(params[4]);
    }
    if (params[5] != null) {
        primeExponentP = new BigInteger(params[5]);
    }
    if (params[6] != null) {
        primeExponentQ = new BigInteger(params[6]);
    }
    if (params[7] != null) {
        crtCoefficient = new BigInteger(params[7]);
    }
}

because of the new members

getters

@Override
public BigInteger getPublicExponent() {
    ensureReadParams();
    return publicExponent;
}

@Override
public BigInteger getPrimeP() {
    ensureReadParams();
    return primeP;
}

@Override
public BigInteger getPrimeQ() {
    ensureReadParams();
    return primeQ;
}

@Override
public BigInteger getPrimeExponentP() {
    ensureReadParams();
    return primeExponentP;
}

@Override
public BigInteger getPrimeExponentQ() {
    ensureReadParams();
    return primeExponentQ;
}

@Override
public BigInteger getCrtCoefficient() {
    ensureReadParams();
    return crtCoefficient;
}

equals

Has changed from

@Override
public boolean equals(Object o) {
    if (o == this) {
        return true;
    }

    if (o instanceof OpenSSLRSAPrivateKey) {
        OpenSSLRSAPrivateKey other = (OpenSSLRSAPrivateKey) o;
        return key.equals(other.getOpenSSLKey());
    }

    if (o instanceof RSAPrivateKey) {
        ensureReadParams();
        RSAPrivateKey other = (RSAPrivateKey) o;

        return modulus.equals(other.getModulus())
                && privateExponent.equals(other.getPrivateExponent());
    }

    return false;
}

to

@Override
public boolean equals(Object o) {
    if (o == this) {
        return true;
    }

    if (o instanceof OpenSSLRSAPrivateKey) {
        OpenSSLRSAPrivateKey other = (OpenSSLRSAPrivateKey) o;
        return getOpenSSLKey().equals(other.getOpenSSLKey());
    }

    if (o instanceof RSAPrivateCrtKey) {
        ensureReadParams();
        RSAPrivateCrtKey other = (RSAPrivateCrtKey) o;

        if (getOpenSSLKey().isHardwareBacked()) {
            return getModulus().equals(other.getModulus())
                    && publicExponent.equals(other.getPublicExponent());
        } else {
            return getModulus().equals(other.getModulus())
                    && publicExponent.equals(other.getPublicExponent())
                    && getPrivateExponent().equals(other.getPrivateExponent())
                    && primeP.equals(other.getPrimeP()) && primeQ.equals(other.getPrimeQ())
                    && primeExponentP.equals(other.getPrimeExponentP())
                    && primeExponentQ.equals(other.getPrimeExponentQ())
                    && crtCoefficient.equals(other.getCrtCoefficient());
        }
    } else if (o instanceof RSAPrivateKey) {
        ensureReadParams();
        RSAPrivateKey other = (RSAPrivateKey) o;

        if (getOpenSSLKey().isHardwareBacked()) {
            return getModulus().equals(other.getModulus());
        } else {
            return getModulus().equals(other.getModulus())
                    && getPrivateExponent().equals(other.getPrivateExponent());
        }
    }

    return false;
}

because of the new members.

hashCode

@Override
public final int hashCode() {
    int hashCode = super.hashCode();
    if (publicExponent != null) {
        hashCode ^= publicExponent.hashCode();
    }
    return hashCode;
}

If publicExponent is null, then it's the same as in the base class. If not, then hashCode is computed based on the base-classes' hash code and publicExponent's hash code, calculating the bitwise XOR between the two.

toString

Changed from

@Override
public String toString() {
    final StringBuilder sb = new StringBuilder("OpenSSLRSAPrivateKey{");

    ensureReadParams();
    sb.append("modulus=");
    sb.append(modulus.toString(16));

    return sb.toString();
}

to

@Override
public String toString() {
    final StringBuilder sb = new StringBuilder("OpenSSLRSAPrivateCrtKey{");

    ensureReadParams();
    sb.append("modulus=");
    sb.append(getModulus().toString(16));

    if (publicExponent != null) {
        sb.append(',');
        sb.append("publicExponent=");
        sb.append(publicExponent.toString(16));
    }

    sb.append('}');
    return sb.toString();
}

readObject

Changed from

private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
    stream.defaultReadObject();

    key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_RSA(
            modulus.toByteArray(),
            null,
            privateExponent.toByteArray(),
            null,
            null,
            null,
            null,
            null));
    fetchedParams = true;
}

to

private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
    stream.defaultReadObject();

    key = new OpenSSLKey(NativeCrypto.EVP_PKEY_new_RSA(
            modulus.toByteArray(),
            publicExponent == null ? null : publicExponent.toByteArray(),
            privateExponent.toByteArray(),
            primeP == null ? null : primeP.toByteArray(),
            primeQ == null ? null : primeQ.toByteArray(),
            primeExponentP == null ? null : primeExponentP.toByteArray(),
            primeExponentQ == null ? null : primeExponentQ.toByteArray(),
            crtCoefficient == null ? null : crtCoefficient.toByteArray()));
    fetchedParams = true;
}

writeObject

Changed from

private void writeObject(ObjectOutputStream stream) throws IOException {
    if (key.isHardwareBacked()) {
        throw new NotSerializableException("Hardware backed keys can not be serialized");
    }
    ensureReadParams();
    stream.defaultWriteObject();
}

to

private void writeObject(ObjectOutputStream stream) throws IOException {
    if (getOpenSSLKey().isHardwareBacked()) {
        throw new NotSerializableException("Hardware backed keys cannot be serialized");
    }

    ensureReadParams();
    stream.defaultWriteObject();
}
like image 141
Lajos Arpad Avatar answered Nov 15 '22 05:11

Lajos Arpad