2

I'm generating a public PEM-format key from a private PEM-format key. I noticed that if I change a few characters near the end of the private PEM that the public PEM generated from the modified private PEM is exactly the same as the result from the unmodified private PEM.

For example, this private PEM

-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCAAZXj8YAEQya8jF8l6Hy56BcRBgplxPd8ZM5LIAWm0w1k/CgB
gvrI28W+5orOyqST2gC4EBEGmLw9s3NC8McL3qqFIQvd6SpWzCJEiI9n+wCkJLYf
715t6BwZo8F82AdqRHwL1lL0T4JeakgcFO5zno2l/NYKpZtS78vIq2F8pwIDAQAB
AoGAIAdFh+lPTMG4mYjN7eBEBQgrbVkDlP85pWhbrbRvdZRtT41APVCWi1diHSf2
J1PQ5iWv9F4gxHPG9fFGr8MrKk+jCM0/rDJsN7yC1yiSerS9zNvoSWu4D1cizRn2
ZOFi+TJZVHAoVvuh1vJdsFLkDmfoXelR6v6ojQxp6IftblECQQD7W4F92yHVIkRt
ZEz5TMYxtFF2d5uwi6XA/1MDuWpK0dK20a7DUGmgQ8faNfSZ/Dr1JmLx0OK/+Ocg
5HnzNo5NAkEAgl7Wa/Lr0TUH5h4L3SsJ1aeYUC6CbzJHBpNTvhuC7YEcs0AJL2k3
qYSM8VpU7q9iGHvfQhnKH9eBXlQbJBt4wwJBAJ1Pj6Ns2afCYoD0HRiJbCD/cVxr
Tw0W2Q4IvbO+/z8EQpQYdv/V+8VJpnJzAjq9GUkEVThyOvdal4yGcaw9oKECQC8V
/a+jXxSCaMXuGC7bOoQWMebTxXxP1mNDlr1UxmbteOYsvKSJBfeNzjHlhENoyK87
HhmLovr5JNpi2iKiYW0CQDyniRLXCyBVayJd3QkuMFKVNUuOytXUFWNXTpLA1Nbb
2K+leKb2KSyyEmRFC2X+QwF9ZBP1C3b0lgBBlBVbpWQ=
-----END RSA PRIVATE KEY-----

and this private PEM

-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCAAZXj8YAEQya8jF8l6Hy56BcRBgplxPd8ZM5LIAWm0w1k/CgB
gvrI28W+5orOyqST2gC4EBEGmLw9s3NC8McL3qqFIQvd6SpWzCJEiI9n+wCkJLYf
715t6BwZo8F82AdqRHwL1lL0T4JeakgcFO5zno2l/NYKpZtS78vIq2F8pwIDAQAB
AoGAIAdFh+lPTMG4mYjN7eBEBQgrbVkDlP85pWhbrbRvdZRtT41APVCWi1diHSf2
J1PQ5iWv9F4gxHPG9fFGr8MrKk+jCM0/rDJsN7yC1yiSerS9zNvoSWu4D1cizRn2
ZOFi+TJZVHAoVvuh1vJdsFLkDmfoXelR6v6ojQxp6IftblECQQD7W4F92yHVIkRt
ZEz5TMYxtFF2d5uwi6XA/1MDuWpK0dK20a7DUGmgQ8faNfSZ/Dr1JmLx0OK/+Ocg
5HnzNo5NAkEAgl7Wa/Lr0TUH5h4L3SsJ1aeYUC6CbzJHBpNTvhuC7YEcs0AJL2k3
qYSM8VpU7q9iGHvfQhnKH9eBXlQbJBt4wwJBAJ1Pj6Ns2afCYoD0HRiJbCD/cVxr
Tw0W2Q4IvbO+/z8EQpQYdv/V+8VJpnJzAjq9GUkEVThyOvdal4yGcaw9oKECQC8V
/a+jXxSCaMXuGC7bOoQWMebTxXxP1mNDlr1UxmbteOYsvKSJBfeNzjHlhENoyK87
HhmLovr5JNpi2iKiYW0CQDyniRLXCyBVayJd3QkuMFKVNUuOytXUFWNXTpLA1Nbb
2K+leKb2KSyyEmRFC2X+QwF9ZBP1C3b0lgBBlBVbpWdddddddddddddddddddddddddQ=
-----END RSA PRIVATE KEY-----

both result in the same public PEM being generated:

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCAAZXj8YAEQya8jF8l6Hy56BcR
BgplxPd8ZM5LIAWm0w1k/CgBgvrI28W+5orOyqST2gC4EBEGmLw9s3NC8McL3qqF
IQvd6SpWzCJEiI9n+wCkJLYf715t6BwZo8F82AdqRHwL1lL0T4JeakgcFO5zno2l
/NYKpZtS78vIq2F8pwIDAQAB
-----END PUBLIC KEY-----

I thought that changing one character of the private PEM would result in an entirely new public PEM. Am I missing something, or can some characters in fact be modified without affecting the public PEM result?

The JavaScript library I'm using to generate the public PEM from a private PEM is rzcoder's node-rsa, which uses Tom Wu's jsbn crypto library.

3

The PEM format is a remnant from an old, forgotten standard called Privacy Enhanced Mail (it was never widely adopted, but served as inspiration for ulterior protocols, in particular S/MIME and PGP). A private RSA key in PEM is really the Base64 encoding of the DER encoding of an ASN.1 structure which looks like this:

RSAPrivateKey ::= SEQUENCE {
    version           Version,
    modulus           INTEGER,  -- n
    publicExponent    INTEGER,  -- e
    privateExponent   INTEGER,  -- d
    prime1            INTEGER,  -- p
    prime2            INTEGER,  -- q
    exponent1         INTEGER,  -- d mod (p-1)
    exponent2         INTEGER,  -- d mod (q-1)
    coefficient       INTEGER,  -- (inverse of q) mod p
    otherPrimeInfos   OtherPrimeInfos OPTIONAL
}

(This is taken straight from PKCS#1, the most used RSA standard.)

What must be noted here is that a private RSA key consists in numerous fields, and among them the modulus and public exponent. This means that the public key can merely be "extracted" from the private key.

With your extra characters, you did two things:

  1. You "damaged" the coefficient value. This is the inverse of q modulo p, where p and q are the prime factors of the modulus. It is used for private key operations, specifically the ones which are implemented with the CRT (a nifty trick which quadruples speed of private key operations).

  2. You added "garbage" after the ASN.1 structure. DER encoding has explicit length headers, so the decoder knows very well where the main structure stops.

In your case, the PEM/DER decoder which you use appears to simply ignore trailing garbage (some other libraries would reject the key with an error; e.g. that's what OpenSSL does). Since public key extraction does not use the coefficient, you get the same public key as with the undamaged private key. However, if you try to use your modified private key, chances are that you won't get proper results (depending on whether your implementation uses the CRT or not).

1

Sometimes, it depends on what you change. A normal RSA private key file is a PEM encoded DER form ASN.1 data structure which contains a version number, plus eight numeric key components, in order:

  1. version (0)
  2. the modulus, n
  3. public exponent, e
  4. the private exponent, d
  5. the original pair of primes used to generate the key, p and q
  6. three additional values used in the CRT calculations, e1, e2, q

Only the first pair of numbers (n and e) end up in the public key, as long as they remain unchanged the output will be the same. (You're not using a signed X.509 cert here, if you were then other details such as serial number, date ranges and more must also be the same for this to hold true for the certificate.)

You are modifying one of the five CRT related values at the end of the structure, this will result in errors or incorrect results when anything tries to use those for decryption, which is almost certainly every standard library given the performance gain. (Those numbers are only for optimisation though, as long enough numbers are intact to recover the primes, decryption is possible.)

Also note, the ASN.1 DER format uses "counted" lengths, the data type and length appear in a short header preceding each value, much of your extra content will appear as trailing junk, and may be ignored. You should also observe the base64 padding structure (which you are not, so your decoder should be complaining about invalid input with the modified key data).

You can inspect the ASN.1 structure of the keys by converting them to binary DER (i.e. base64-decoding the payload) and then using dumpasn1.

openssl rsa -in prv.pem -outform DER -out prv.der
dumpasn1 -tilda prv.der 
openssl rsa -pubin -in pub.pem -outform DER -out pub.der
dumpasn1 -tilda pub.der 

openssl x509 will only deal with trusted or signed certificates and won't convert this public key, openssl rsa -pubin processes unsigned public keys. You can do something similar to the above with openssl asn1parse, but it's not as informative or simple to use for this.

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.