45

Given a certificate¹ and a private key file², how can I determine if the public key on the certificate matches the private key?

My initial thought was to simply encrypt some text with the public key on the cert, and attempt to decrypt it with the private key. If it roundtrips, we've got a winner. I just can't figure out how to do this with OpenSSL.

Alternatively, if I could generate the public key from the private key, I could just compare their fingerprints. SSH seems to have a command for this:

ssh-keygen -y -f my_key > my_key.pub

But the hashes don't match. (I'm nearly certain I have the key corresponding to the cert, as the webserver is serving with it, but I'd like an easier way that spinning up a server to check.)

¹ a .crt file, in x509 format, I think. OpenSSL can read it with:

openssl x509 -text -in that_cert.crt

² An RSA private key.

1
  • 2
    One can 'spin up' a test server with openssl s_server -key $k -cert $c and then control-C, which takes me only about 3-4 seconds. Your idea of encrypt-then-decrypt, and the equally good idea of sign-then-verify, can be done with rsautl, or since 1.0.0 in 2010 also pkeyutl which is more flexible and thus a little more complicated; see the man pages. But I prefer directly checking the key material, as others have already answered. Apr 1 '20 at 3:54
54

I'm going to assume you have ssl.crt and ssl.key in your current directory.

If you want to see what's in your certificate it's

# openssl x509 -in ssl.crt -text -noout

Two of the things in here will be the RSA public Key's Modulus and Exponent (in hex).

If you want to see what's in your private key it's

# openssl rsa -in ssl.key -text -noout

Note the public key is usually in there (at the very least the modulus is required to be in there for the private key to work, and the public exponent is usually 65537 or 3). So you can simply check if the modulus and public exponent match. Granted, if you want to check that the private key is actually valid (that is d and e are valid RSA exponents for the modulus m), you would need to run

# openssl rsa -check -in ssl.key -noout

EDIT (2018): Please note if you are checking that a private key coming from an untrusted source corresponds with a certificate, you MUST CHECK that the private key is valid. See here for an example where not checking the validity of a "leaked" private key lead to a CA improperly revoking a certificate. You may skip this step if you know you validly generated the keypair.

Now you can simply generate the public key from both the certificate and the private key and then use diff to check that they don't differ:

# openssl x509 -in ssl.crt -pubkey -noout > from_crt.pub
# openssl rsa -in ssl.key -pubout > from_key.pub
# diff from_crt.pub from_key.pub

Or as a one liner that doesn't create files (using process substitution):

# diff  <(openssl x509 -in ssl.crt -pubkey -noout) <(openssl rsa -in ssl.key -pubout)

If the keys match, diff shouldn't return anything. (You probably will see "writing RSA key" output to stderr from the second command).

Note your webserver probably would loudly complain if the certificate and private key didn't match. E.g., with nginx using the wrong key (same size, same public exponent, but last year's key) for the certificate nginx is using:

# sudo /etc/init.d/nginx restart
* Restarting nginx nginx                                                                                         
nginx: [emerg] SSL_CTX_use_PrivateKey_file("/etc/nginx/ssl/private/wrong_key.key") failed 
(SSL: error:0B080074:x509 certificate routines:X509_check_private_key:key values mismatch)
nginx: configuration file /etc/nginx/nginx.conf test failed
7
  • How this solution compares with the one proposed at sslshopper.com/certificate-key-matcher.html ?? Sep 19 '16 at 22:55
  • 3
    @LeandroDavid They only check the modulus is the same for the cert and key (by comparing MD5 hashes). In common practice with randomly generated keypairs and the same public exponent (typically e=65537) that would work, though you could make two different keypairs with the same prime numbers and modulus but different e. For toy #s, let p=29, q=59, so N=1711 and totient phi=(p-1)(q-1)=1624, you could have one keypair with e1 = 3 and private exponent d1 = 1083 (note e1*d1 % phi = 1) and another keypair with e2=17 and d2=1433 (note e2*d2 % phi = 1), but note e1*d2 % phi != 1.
    – dr jimbob
    Sep 20 '16 at 3:02
  • 2
    That is it is possible to have one certificate with modulus N=1711, public exponent e=3, that doesn't work with a private key with same modulus N=1711 but a private exponent d=1433 that corresponds to a different public exponent. Again, in practice, this doesn't matter as e is almost always set to 65537 and unless you manually specify the primes and they are randomly selected, it is very improbable that you would generate have two keypairs with the same primes. But in principle if you want to check that a private key corresponds to a certificate, you need to check e*d % phi = 1.
    – dr jimbob
    Sep 20 '16 at 3:05
  • 2
    Interestingly, according to How I tricked Symantec with a Fake Private Key this Q&A was the only place on the 'net to describe a proper check of a private key.
    – TripeHound
    Apr 24 '18 at 10:23
  • 2
    It's interesting in that it was somewhat of an aside (and my main method of checking that a private key corresponds with a public certificate via diff will fail for forged invalid private keys). Probably should edit to emphasize this point.
    – dr jimbob
    Apr 25 '18 at 16:11
14

The accepted answer is correct, but it only works for RSA keys.

At least since openssl 1.1.1 it is possible to test validity of all types of private keys and here's a one-liner that works for all sorts of keys that openssl supports

 cmp <(openssl x509 -pubkey -in certificate.pem -noout) <(openssl pkey -check -pubout -in private-key.pem -outform PEM)

It will return 'true' if and only if the private key matches the public key in the certificate.

4
  • 2
    Does this take into consideration the "if you want to check that the private key is actually valid", meaning it covers fake private keys?
    – Nathan H
    Feb 6 '19 at 12:09
  • 1
    @NathanH New enough openssl pkey subcommand supports flags "-check" and "-pubcheck", which seem to be missing here. Since there is no "-nocheck", I am afraid checking is OFF BY DEFAULT.
    – user185953
    Mar 12 '20 at 17:39
  • @user185953, good suggetion! You wanna suggest an edit?
    – Manish
    Jul 5 '20 at 18:10
  • 1
    Already done: security.stackexchange.com/posts/141620/revisions
    – user185953
    Jul 7 '20 at 7:10
2

OpenSSL can create a test TLS server that will verify that a key and certificate match as it initialises:

openssl s_server -key key.pem -cert cert.pem

If the server starts then the key and certificate match, otherwise the server will fail to start and complain:

error setting private key
140606597580096:error:0B080074:x509 certificate routines:X509_check_private_key:\
key values mismatch:../crypto/x509/x509_cmp.c:297:

Acknowledgement goes to dave_thompson_085 for his suggestion in this question's comments.

5
  • Nice idea. I tested it with openssl 1.0.2k and it works, but does not complain about extra lines appended to the certificate.
    – user185953
    Dec 9 '20 at 10:33
  • @user185953 Why should it complain about this? Applications that are using the same libraries will not complain either. Dec 10 '20 at 13:41
  • Hmm, maybe it is OK. I was worried about libraries that for example load the whole chain from the file and that those unverified lines could do something bad.
    – user185953
    Dec 10 '20 at 14:50
  • @user185953 RFC 7468: "Data before the encapsulation boundaries are permitted, and parsers MUST NOT malfunction when processing such data." tools.ietf.org/html/rfc7468#section-2 Dec 10 '20 at 22:54
  • OK. For the record, I meant extra data after the encapsulation boundaries, for example another/rogue leaf certificate, but I am sure the RFC requires use of the first leaf somewhere.
    – user185953
    Dec 11 '20 at 8:54
0

Updating Manish's answer with a function that can then be used. Usage:

ssl-cert-verify <key> <cert>

Here's the code:

ssl-cert-verify() { local key="$1"; local cert="$2"; local opt="-check";
   cmp <(openssl x509 -pubkey -in "$cert" -noout) <(openssl pkey $opt -pubout -in "$key" -outform PEM)
}

With my older version of SSL, I had to leave opt value empty.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.