135

How can I transform between the two styles of public key format, one format is:

-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----

the other format is:

-----BEGIN RSA PUBLIC KEY-----
...
-----END RSA PUBLIC KEY-----

for example I generated id_rsa/id_rsa.pub pair using ssh-keygen command, I calculated the public key from id_rsa using:

openssl rsa -in id_rsa -pubout -out pub2 

then again I calculated the public key from id_rsa.pub using :

ssh-keygen -f id_rsa.pub -e -m pem > pub1

the content is pub1 is :

-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA61BjmfXGEvWmegnBGSuS+rU9soUg2FnODva32D1AqhwdziwHINFa
D1MVlcrYG6XRKfkcxnaXGfFDWHLEvNBSEVCgJjtHAGZIm5GL/KA86KDp/CwDFMSw
luowcXwDwoyinmeOY9eKyh6aY72xJh7noLBBq1N0bWi1e2i+83txOCg4yV2oVXhB
o8pYEJ8LT3el6Smxol3C1oFMVdwPgc0vTl25XucMcG/ALE/KNY6pqC2AQ6R2ERlV
gPiUWOPatVkt7+Bs3h5Ramxh7XjBOXeulmCpGSynXNcpZ/06+vofGi/2MlpQZNhH
Ao8eayMp6FcvNucIpUndo1X8dKMv3Y26ZQIDAQAB
-----END RSA PUBLIC KEY-----

and the content of pub2 is :

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA61BjmfXGEvWmegnBGSuS
+rU9soUg2FnODva32D1AqhwdziwHINFaD1MVlcrYG6XRKfkcxnaXGfFDWHLEvNBS
EVCgJjtHAGZIm5GL/KA86KDp/CwDFMSwluowcXwDwoyinmeOY9eKyh6aY72xJh7n
oLBBq1N0bWi1e2i+83txOCg4yV2oVXhBo8pYEJ8LT3el6Smxol3C1oFMVdwPgc0v
Tl25XucMcG/ALE/KNY6pqC2AQ6R2ERlVgPiUWOPatVkt7+Bs3h5Ramxh7XjBOXeu
lmCpGSynXNcpZ/06+vofGi/2MlpQZNhHAo8eayMp6FcvNucIpUndo1X8dKMv3Y26
ZQIDAQAB
-----END PUBLIC KEY-----

According to my understanding, pub1 and pub2 contain the same public key information, but they are in different format, I wonder how can I transform between the two format? Can anyone show me some concise introduction on the tow formats?

jww
  • 90,984
  • 81
  • 374
  • 818
welkinwalker
  • 1,792
  • 3
  • 14
  • 20
  • Stack Overflow is a site for programming and development questions. This question appears to be off-topic because it is not about programming or development. See [What topics can I ask about here](http://stackoverflow.com/help/on-topic) in the Help Center. Perhaps [Super User](http://superuser.com/) or [Unix & Linux Stack Exchange](http://unix.stackexchange.com/) would be a better place to ask. – jww Apr 19 '15 at 22:44

5 Answers5

396

I wanted to help explain what's going on here.

An RSA "Public Key" consists of two numbers:

  • the modulus (e.g. a 2,048 bit number)
  • the exponent (usually 65,537)

Using your RSA public key as an example, the two numbers are:

  • Modulus: 297,056,429,939,040,947,991,047,334,197,581,225,628,107,021,573,849,359,042,679,698,093,131,908,015,712,695,688,944,173,317,630,555,849,768,647,118,986,535,684,992,447,654,339,728,777,985,990,170,679,511,111,819,558,063,246,667,855,023,730,127,805,401,069,042,322,764,200,545,883,378,826,983,730,553,730,138,478,384,327,116,513,143,842,816,383,440,639,376,515,039,682,874,046,227,217,032,079,079,790,098,143,158,087,443,017,552,531,393,264,852,461,292,775,129,262,080,851,633,535,934,010,704,122,673,027,067,442,627,059,982,393,297,716,922,243,940,155,855,127,430,302,323,883,824,137,412,883,916,794,359,982,603,439,112,095,116,831,297,809,626,059,569,444,750,808,699,678,211,904,501,083,183,234,323,797,142,810,155,862,553,705,570,600,021,649,944,369,726,123,996,534,870,137,000,784,980,673,984,909,570,977,377,882,585,701
  • Exponent: 65,537

The question then becomes how do we want to store these numbers in a computer. First we convert both to hexadecimal:

  • Modulus: EB506399F5C612F5A67A09C1192B92FAB53DB28520D859CE0EF6B7D83D40AA1C1DCE2C0720D15A0F531595CAD81BA5D129F91CC6769719F1435872C4BCD0521150A0263B470066489B918BFCA03CE8A0E9FC2C0314C4B096EA30717C03C28CA29E678E63D78ACA1E9A63BDB1261EE7A0B041AB53746D68B57B68BEF37B71382838C95DA8557841A3CA58109F0B4F77A5E929B1A25DC2D6814C55DC0F81CD2F4E5DB95EE70C706FC02C4FCA358EA9A82D8043A47611195580F89458E3DAB5592DEFE06CDE1E516A6C61ED78C13977AE9660A9192CA75CD72967FD3AFAFA1F1A2FF6325A5064D847028F1E6B2329E8572F36E708A549DDA355FC74A32FDD8DBA65
  • Exponent: 010001

RSA invented the first format

RSA invented a format first:

RSAPublicKey ::= SEQUENCE {
    modulus           INTEGER,  -- n
    publicExponent    INTEGER   -- e
}

They chose to use the DER flavor of the ASN.1 binary encoding standard to represent the two numbers [1]:

SEQUENCE (2 elements)
   INTEGER (2048 bit): EB506399F5C612F5A67A09C1192B92FAB53DB28520D859CE0EF6B7D83D40AA1C1DCE2C0720D15A0F531595CAD81BA5D129F91CC6769719F1435872C4BCD0521150A0263B470066489B918BFCA03CE8A0E9FC2C0314C4B096EA30717C03C28CA29E678E63D78ACA1E9A63BDB1261EE7A0B041AB53746D68B57B68BEF37B71382838C95DA8557841A3CA58109F0B4F77A5E929B1A25DC2D6814C55DC0F81CD2F4E5DB95EE70C706FC02C4FCA358EA9A82D8043A47611195580F89458E3DAB5592DEFE06CDE1E516A6C61ED78C13977AE9660A9192CA75CD72967FD3AFAFA1F1A2FF6325A5064D847028F1E6B2329E8572F36E708A549DDA355FC74A32FDD8DBA65
   INTEGER (24 bit): 010001

The final binary encoding in ASN.1 is:

30 82 01 0A      ;sequence (0x10A bytes long)
   02 82 01 01   ;integer (0x101 bytes long)
      00 EB506399F5C612F5A67A09C1192B92FAB53DB28520D859CE0EF6B7D83D40AA1C1DCE2C0720D15A0F531595CAD81BA5D129F91CC6769719F1435872C4BCD0521150A0263B470066489B918BFCA03CE8A0E9FC2C0314C4B096EA30717C03C28CA29E678E63D78ACA1E9A63BDB1261EE7A0B041AB53746D68B57B68BEF37B71382838C95DA8557841A3CA58109F0B4F77A5E929B1A25DC2D6814C55DC0F81CD2F4E5DB95EE70C706FC02C4FCA358EA9A82D8043A47611195580F89458E3DAB5592DEFE06CDE1E516A6C61ED78C13977AE9660A9192CA75CD72967FD3AFAFA1F1A2FF6325A5064D847028F1E6B2329E8572F36E708A549DDA355FC74A32FDD8DBA65
   02 03         ;integer (3 bytes long)
      010001

If you then run all those bytes together and Base64 encode it, you get:

MIIBCgKCAQEA61BjmfXGEvWmegnBGSuS+rU9soUg2FnODva32D1AqhwdziwHINFa
D1MVlcrYG6XRKfkcxnaXGfFDWHLEvNBSEVCgJjtHAGZIm5GL/KA86KDp/CwDFMSw
luowcXwDwoyinmeOY9eKyh6aY72xJh7noLBBq1N0bWi1e2i+83txOCg4yV2oVXhB
o8pYEJ8LT3el6Smxol3C1oFMVdwPgc0vTl25XucMcG/ALE/KNY6pqC2AQ6R2ERlV
gPiUWOPatVkt7+Bs3h5Ramxh7XjBOXeulmCpGSynXNcpZ/06+vofGi/2MlpQZNhH
Ao8eayMp6FcvNucIpUndo1X8dKMv3Y26ZQIDAQAB

RSA labs then said add a header and trailer:

-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA61BjmfXGEvWmegnBGSuS+rU9soUg2FnODva32D1AqhwdziwHINFa
D1MVlcrYG6XRKfkcxnaXGfFDWHLEvNBSEVCgJjtHAGZIm5GL/KA86KDp/CwDFMSw
luowcXwDwoyinmeOY9eKyh6aY72xJh7noLBBq1N0bWi1e2i+83txOCg4yV2oVXhB
o8pYEJ8LT3el6Smxol3C1oFMVdwPgc0vTl25XucMcG/ALE/KNY6pqC2AQ6R2ERlV
gPiUWOPatVkt7+Bs3h5Ramxh7XjBOXeulmCpGSynXNcpZ/06+vofGi/2MlpQZNhH
Ao8eayMp6FcvNucIpUndo1X8dKMv3Y26ZQIDAQAB
-----END RSA PUBLIC KEY-----

Five hyphens, and the words BEGIN RSA PUBLIC KEY. That is your PEM DER ASN.1 PKCS#1 RSA Public key

  • PEM: synonym for base64
  • DER: a flavor of ASN.1 encoding
  • ASN.1: the binary encoding scheme used
  • PKCS#1: The formal specification that dictates representing a public key as structure that consists of modulus followed by an exponent
  • RSA public key: the public key algorithm being used

Not just RSA

After that, other forms of public key cryptography came along:

  • Diffie-Hellman
  • Ellicptic Curve

When it came time to create a standard for how to represent the parameters of those encryption algorithms, people adopted a lot of the same ideas that RSA originally defined:

  • use ASN.1 binary encoding
  • base64 it
  • wrap it with five hyphens
  • and the words BEGIN PUBLIC KEY

But rather than using:

  • -----BEGIN RSA PUBLIC KEY-----
  • -----BEGIN DH PUBLIC KEY-----
  • -----BEGIN EC PUBLIC KEY-----

They decided instead to include the Object Identifier (OID) of what is to follow. In the case of an RSA public key, that is:

  • RSA PKCS#1: 1.2.840.113549.1.1.1

So for RSA public key it was essentially:

public struct RSAPublicKey {
   INTEGER modulus,
   INTEGER publicExponent 
}

Now they created SubjectPublicKeyInfo which is basically:

public struct SubjectPublicKeyInfo {
   AlgorithmIdentifier algorithm,
   RSAPublicKey subjectPublicKey
}

In actual DER ASN.1 definition is:

SubjectPublicKeyInfo  ::=  SEQUENCE  {
    algorithm  ::=  SEQUENCE  {
        algorithm               OBJECT IDENTIFIER, -- 1.2.840.113549.1.1.1 rsaEncryption (PKCS#1 1)
        parameters              ANY DEFINED BY algorithm OPTIONAL  },
    subjectPublicKey     BIT STRING {
        RSAPublicKey ::= SEQUENCE {
            modulus            INTEGER,    -- n
            publicExponent     INTEGER     -- e
        }
}

That gives you an ASN.1 of:

SEQUENCE (2 elements)
   SEQUENCE (2 elements)
      OBJECT IDENTIFIER 1.2.840.113549.1.1.1
      NULL
   BIT STRING (1 element)
      SEQUENCE (2 elements)
         INTEGER (2048 bit): EB506399F5C612F5A67A09C1192B92FAB53DB28520D859CE0EF6B7D83D40AA1C1DCE2C0720D15A0F531595CAD81BA5D129F91CC6769719F1435872C4BCD0521150A0263B470066489B918BFCA03CE8A0E9FC2C0314C4B096EA30717C03C28CA29E678E63D78ACA1E9A63BDB1261EE7A0B041AB53746D68B57B68BEF37B71382838C95DA8557841A3CA58109F0B4F77A5E929B1A25DC2D6814C55DC0F81CD2F4E5DB95EE70C706FC02C4FCA358EA9A82D8043A47611195580F89458E3DAB5592DEFE06CDE1E516A6C61ED78C13977AE9660A9192CA75CD72967FD3AFAFA1F1A2FF6325A5064D847028F1E6B2329E8572F36E708A549DDA355FC74A32FDD8DBA65
         INTEGER (24 bit): 010001

The final binary encoding in ASN.1 is:

30 82 01 22          ;SEQUENCE (0x122 bytes = 290 bytes)
|  30 0D             ;SEQUENCE (0x0d bytes = 13 bytes) 
|  |  06 09          ;OBJECT IDENTIFIER (0x09 = 9 bytes)
|  |  2A 86 48 86   
|  |  F7 0D 01 01 01 ;hex encoding of 1.2.840.113549.1.1
|  |  05 00          ;NULL (0 bytes)
|  03 82 01 0F 00    ;BIT STRING  (0x10f = 271 bytes)
|  |  30 82 01 0A       ;SEQUENCE (0x10a = 266 bytes)
|  |  |  02 82 01 01    ;INTEGER  (0x101 = 257 bytes)
|  |  |  |  00             ;leading zero of INTEGER
|  |  |  |  EB 50 63 99 F5 C6 12 F5  A6 7A 09 C1 19 2B 92 FA 
|  |  |  |  B5 3D B2 85 20 D8 59 CE  0E F6 B7 D8 3D 40 AA 1C 
|  |  |  |  1D CE 2C 07 20 D1 5A 0F  53 15 95 CA D8 1B A5 D1 
|  |  |  |  29 F9 1C C6 76 97 19 F1  43 58 72 C4 BC D0 52 11 
|  |  |  |  50 A0 26 3B 47 00 66 48  9B 91 8B FC A0 3C E8 A0
|  |  |  |  E9 FC 2C 03 14 C4 B0 96  EA 30 71 7C 03 C2 8C A2  
|  |  |  |  9E 67 8E 63 D7 8A CA 1E  9A 63 BD B1 26 1E E7 A0  
|  |  |  |  B0 41 AB 53 74 6D 68 B5  7B 68 BE F3 7B 71 38 28
|  |  |  |  38 C9 5D A8 55 78 41 A3  CA 58 10 9F 0B 4F 77 A5
|  |  |  |  E9 29 B1 A2 5D C2 D6 81  4C 55 DC 0F 81 CD 2F 4E 
|  |  |  |  5D B9 5E E7 0C 70 6F C0  2C 4F CA 35 8E A9 A8 2D 
|  |  |  |  80 43 A4 76 11 19 55 80  F8 94 58 E3 DA B5 59 2D
|  |  |  |  EF E0 6C DE 1E 51 6A 6C  61 ED 78 C1 39 77 AE 96 
|  |  |  |  60 A9 19 2C A7 5C D7 29  67 FD 3A FA FA 1F 1A 2F 
|  |  |  |  F6 32 5A 50 64 D8 47 02  8F 1E 6B 23 29 E8 57 2F 
|  |  |  |  36 E7 08 A5 49 DD A3 55  FC 74 A3 2F DD 8D BA 65
|  |  |  02 03          ;INTEGER (03 = 3 bytes)
|  |  |  |  010001
   

And as before, you take all those bytes, Base64 encode them, you end up with your second example:

MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA61BjmfXGEvWmegnBGSuS
+rU9soUg2FnODva32D1AqhwdziwHINFaD1MVlcrYG6XRKfkcxnaXGfFDWHLEvNBS
EVCgJjtHAGZIm5GL/KA86KDp/CwDFMSwluowcXwDwoyinmeOY9eKyh6aY72xJh7n
oLBBq1N0bWi1e2i+83txOCg4yV2oVXhBo8pYEJ8LT3el6Smxol3C1oFMVdwPgc0v
Tl25XucMcG/ALE/KNY6pqC2AQ6R2ERlVgPiUWOPatVkt7+Bs3h5Ramxh7XjBOXeu
lmCpGSynXNcpZ/06+vofGi/2MlpQZNhHAo8eayMp6FcvNucIpUndo1X8dKMv3Y26
ZQIDAQAB   

Add the slightly different header and trailer, and you get:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA61BjmfXGEvWmegnBGSuS
+rU9soUg2FnODva32D1AqhwdziwHINFaD1MVlcrYG6XRKfkcxnaXGfFDWHLEvNBS
EVCgJjtHAGZIm5GL/KA86KDp/CwDFMSwluowcXwDwoyinmeOY9eKyh6aY72xJh7n
oLBBq1N0bWi1e2i+83txOCg4yV2oVXhBo8pYEJ8LT3el6Smxol3C1oFMVdwPgc0v
Tl25XucMcG/ALE/KNY6pqC2AQ6R2ERlVgPiUWOPatVkt7+Bs3h5Ramxh7XjBOXeu
lmCpGSynXNcpZ/06+vofGi/2MlpQZNhHAo8eayMp6FcvNucIpUndo1X8dKMv3Y26
ZQIDAQAB   
-----END PUBLIC KEY-----

And this is your X.509 SubjectPublicKeyInfo/OpenSSL PEM public key [2].

Do it right, or hack it

Now that you know that the encoding isn't magic, you can write all the pieces needed to parse out the RSA modulus and exponent. Or you can recognize that the first 24 bytes are just added new stuff on top of the original PKCS#1 standard

30 82 01 22          ;SEQUENCE (0x122 bytes = 290 bytes)
|  30 0D             ;SEQUENCE (0x0d bytes = 13 bytes) 
|  |  06 09          ;OBJECT IDENTIFIER (0x09 = 9 bytes)
|  |  2A 86 48 86   
|  |  F7 0D 01 01 01 ;hex encoding of 1.2.840.113549.1.1
|  |  05 00          ;NULL (0 bytes)
|  03 82 01 0F 00    ;BIT STRING  (0x10f = 271 bytes)
|  |  ...

Those first 24-bytes are "new" stuff added:

30 82 01 22 30 0D 06 09 2A 86 48 86 F7 0D 01 01 01 05 00 03 82 01 0F 00

And due to an extraordinary coincidence of fortune and good luck:

24 bytes happens to correspond exactly to 32 base64 encoded characters

Because in Base64: 3-bytes becomes four characters:

30 82 01  22 30 0D  06 09 2A  86 48 86  F7 0D 01  01 01 05  00 03 82  01 0F 00
\______/  \______/  \______/  \______/  \______/  \______/  \______/  \______/
    |         |         |         |         |         |         |         |
  MIIB      IjAN      Bgkq      hkiG      9w0B      AQEF      AAOC      AQ8A

That means if you take your second X.509 public key, the first 32 characters corresponds only to newly added stuff:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
MIIBCgKCAQEA61BjmfXGEvWmegnBGSuS+rU9soUg2FnODva32D1AqhwdziwHINFa
D1MVlcrYG6XRKfkcxnaXGfFDWHLEvNBSEVCgJjtHAGZIm5GL/KA86KDp/CwDFMSw
luowcXwDwoyinmeOY9eKyh6aY72xJh7noLBBq1N0bWi1e2i+83txOCg4yV2oVXhB
o8pYEJ8LT3el6Smxol3C1oFMVdwPgc0vTl25XucMcG/ALE/KNY6pqC2AQ6R2ERlV
gPiUWOPatVkt7+Bs3h5Ramxh7XjBOXeulmCpGSynXNcpZ/06+vofGi/2MlpQZNhH
Ao8eayMp6FcvNucIpUndo1X8dKMv3Y26ZQIDAQAB
-----END PUBLIC KEY-----

If you remove the first 32 characters, and change it to BEGIN RSA PUBLIC KEY:

-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA61BjmfXGEvWmegnBGSuS+rU9soUg2FnODva32D1AqhwdziwHINFa
D1MVlcrYG6XRKfkcxnaXGfFDWHLEvNBSEVCgJjtHAGZIm5GL/KA86KDp/CwDFMSw
luowcXwDwoyinmeOY9eKyh6aY72xJh7noLBBq1N0bWi1e2i+83txOCg4yV2oVXhB
o8pYEJ8LT3el6Smxol3C1oFMVdwPgc0vTl25XucMcG/ALE/KNY6pqC2AQ6R2ERlV
gPiUWOPatVkt7+Bs3h5Ramxh7XjBOXeulmCpGSynXNcpZ/06+vofGi/2MlpQZNhH
Ao8eayMp6FcvNucIpUndo1X8dKMv3Y26ZQIDAQAB
-----END RSA PUBLIC KEY-----

You have exactly what you wanted - the older RSA PUBLIC KEY format.

Community
  • 1
  • 1
Ian Boyd
  • 233,966
  • 238
  • 834
  • 1,160
  • 45
    Holy balls, that was informative! thank you. This solved my issue with a python guy who was expecting only BEGIN RSA PUBLIC KEY. Though, in your last example, it looks like you forgot to remove the 32 characters. – NullVoxPopuli Oct 16 '15 at 16:33
  • What tool did you use to print out the hex structure of the files? – Buge Jan 18 '16 at 22:10
  • 8
    @Buge I used the excellent, excellent, [ASN.1 JavaScript decoder](https://lapo.it/asn1js/). That and [TRANSLATOR, BINARY](https://paulschou.com/tools/xlate/) are two excellent tools to have in your toolbox of tricks. – Ian Boyd Jan 19 '16 at 18:24
  • Any chance you could explain the certificate process as well? – Ngz Mar 23 '16 at 08:16
  • 2
    The start of the modulus has an extra "1" character. It should begin like this... 297,056,429,939,040,947,991,047,334,197,581,225,628,107,02,573... but NOT this... 297,056,429,939,040,947,991,047,334,197,581,225,628,107,021,573... hope that helps someones from getting angry at their hex conversion. – EmpathicSage May 13 '16 at 20:17
  • 2
    [jsFiddle version of ASN.1 js](https://jsfiddle.net/eeezwbgk/3/). It's also on [github](https://github.com/lapo-luchini/asn1js) – Ian Boyd Apr 13 '17 at 20:24
  • 1
    `INTEGER (12 bit): 010001`: shouldn't it be 24? 010001 is 24 bits, 65537 doesn't fit 12 bits. – George Sovetov Jul 29 '18 at 12:52
  • @GeorgeSovetov You're right; i accidentally did `3 bytes * 4 bits per nibble = 12 bits` – Ian Boyd Jul 29 '18 at 14:08
  • 1
    Sorry for the nitpick, but PEM isn't a synonym for base64. It has a complicated and not very well defined history, but PEM is in itself a data format. Generally now it is accepted to be the header (hyphens with BEGIN XYZ), footer, and optional number of headers under the header and *then* the base64 encoded data. – Luke Joshua Park Jan 10 '19 at 00:46
  • Another tool is [ASN.1 Editor](https://www.sysadmins.lv/projects/asn1editor/default.aspx), a native Windows application. – Franklin Yu May 21 '19 at 21:04
  • Amazing!, can I do the same with BEGIN RSA PRIVATE KEY? just remove the first 32 base64 bytes? – mnesarco Jan 13 '20 at 22:20
  • 1
    whoa. I'm so much grateful for this first so clear & detailed explanation I found after dozen of hours spent in research on the internet. Many, many, many thanks ! – nouknouk Nov 27 '21 at 15:55
  • @nmesarco Oh i have no idea; i doubt it. You have to repeat the same exercise, this time with **[RSAPrivateKey](https://www.rfc-editor.org/rfc/rfc3447#appendix-A.1.2)** – Ian Boyd Nov 28 '21 at 20:26
63

I found this website to be a good technical explanation of the different formats: https://polarssl.org/kb/cryptography/asn1-key-structures-in-der-and-pem

"BEGIN RSA PUBLIC KEY" is PKCS#1, which can only contain RSA keys.

"BEGIN PUBLIC KEY" is PKCS#8, which can contain a variety of formats.

If you just want to convert them with the command-line, "openssl rsa" is good for this.

To convert from PKCS#8 to PKCS#1:

openssl rsa -pubin -in <filename> -RSAPublicKey_out

To convert from PKCS#1 to PKCS#8:

openssl rsa -RSAPublicKey_in -in <filename> -pubout
Esme Povirk
  • 2,820
  • 14
  • 23
  • 4
    I can't find anything about public key in PKCS#8 ([RFC 5208](https://tools.ietf.org/html/rfc5208)). – Franklin Yu Oct 30 '17 at 15:08
  • Doesn't work on MacOS: `unknown option -RSAPublicKey_in` – nakajuice Mar 08 '18 at 11:45
  • 4
    @FranklinYu: yes PKCS8 is privatekey only and polarssl is wrong on that point. The generic form of publickey is defined by X.509 and specifically the type SubjectPublicKeyInfo, as correctly stated in Ian Boyd's (long!) answer; this info is (more conveniently) duplicated in RFC5280 plus other RFCs depending on algorithm, with 'basic' RSA in RFC3279. – dave_thompson_085 Aug 23 '18 at 02:47
  • @nakajuice: You need OpenSSL version 1.0.0 (released 2010) or higher. AIUI Apple stopped supporting OpenSSL on OS(X), so you may need a version from brew or similar. – dave_thompson_085 Aug 23 '18 at 02:48
  • 1
    This got me on the right direction for converting from OpenSSH format. I ended up using ssh-keygen like this: ssh-keygen -i -f ~/.ssh/id_rsa.pub -e -m PKCS8 > ~/.ssh/id_rsa.pub.pem – Bradley Kreider Jul 19 '19 at 19:28
  • This answer is misleading, as others have pointed out - PKCS#8 only sets a standard for private keys. – scottysseus Jun 29 '20 at 19:45
  • @dave_thompson_085 There seems to be much confusion about this on the internet, but multiple sources show successful conversion from the X509 SubjectPublicKeyInfo (SPKI) format into PKCS 1. It looks like PKCS 1 was indeed originally designed for private keys but later utilized also for public keys. See here for example: https://stackoverflow.com/questions/53924326/rsa-should-i-use-x-509-or-pkcs-1 . It is a similar story with PKCS 8 which was designed for Private Keys but later could take OIDs for Public Keys. Documentation needs to be updated to clarify the confusing situation there. – SeligkeitIstInGott Mar 08 '21 at 19:32
  • @dave_thompson_085 "Because RSA is not used exclusively inside X509 and SSL/TLS, a more generic key format is available in the form of PKCS#8, that identifies the type of public key and contains the relevant data." https://tls.mbed.org/kb/cryptography/asn1-key-structures-in-der-and-pem – SeligkeitIstInGott Mar 08 '21 at 19:39
  • @scottysseus PKCS#8 according to the standard, yes, was for private keys but it now used for a variety of things including public keys. Not to say it is the primary format for Public Keys but that a successful conversion to PKCS 8 for public keys is possible. There are OIDs for public keys in PKCS 8 format I believe. – SeligkeitIstInGott Mar 08 '21 at 19:41
  • @SeligkeitIstInGott: mbed(tls) is just the new name of polarssl, which as I said before was wrong then and remains wrong now. Look at PKCS8 (RFC5208) and that (publickey) structure is not present anywhere; look at X.509/PKIX (currently RFC5280) and it is in section 4.1. Also look at RFC7468 sections 10 11 13. You can _convert_ (or extract) a publickey from PKCS8 because (for all schemes now used) you can convert a publickey from any privatekey and PKCS8 is a privatekey, but there is no PKCS8 anywhere that is not a privatekey, period, full stop. – dave_thompson_085 Mar 10 '21 at 04:29
  • @dave_thompson_085 Which standard generates the base64 ASCII readable "BEGIN RSA PUBLIC KEY" heading? I do not believe that fits for the the X509 certificate format which rather says BEGIN CERTIFICATE. The SPKI spec in 4.1.2.7 in RFC5280 doesn't specify a standalone PUBLIC KEY format like that so far as I can tell. So it must be some form of PKCS format I'm thinking. Both PKCS 1 and 8 say they are for private keys. Yet both BEGIN RSA PUBLIC KEY and BEGIN PUBLIC KEY must come from some PKCS format. So which RFC specifies those names and how are they implemented in terms of format? – SeligkeitIstInGott Mar 11 '21 at 21:21
  • The PEM format BEGIN/END PUBLIC KEY (_without_ RSA) is defined in RFC7468 and uses (as I said) SPKI from X.509/PKIX (RFC5280, previously 3280 and 2459); this is an Internet (and ITU) standard not a PKCS. The widely used but AFAIK not official practice of BEGIN/END CERTIFICATE or X.509 CERTIFICATE is also formalized by 7468. There are no standards for OpenSSL's PEM-format algorithm-specific BEGIN/END {RSA,DSA,EC} PUBLIC KEY (and PRIVATE also) although for the underlying DER the RSA cases use PKCS1; DSA uses a format Eric invented and EC uses SEC1 from SECG, which builds on X9.62.) ... – dave_thompson_085 Mar 12 '21 at 05:35
  • ... PKCS1 is mostly about the RSA _algorithm_ and incidentally defines both public and private keys FOR RSA ONLY. PKCS8 is entirely about privatekeys for _all_ algorithms, and similarly X.509/PKIX is mostly about publickeys for _all_ algorithms. – dave_thompson_085 Mar 12 '21 at 05:36
  • Ah, your comment about RFC7468 is clearer now. I'll take a look. Thanks. – SeligkeitIstInGott Mar 12 '21 at 15:01
  • @dave_thompson_085 Going back to my very first comment above, you have failed to address RFC8017. People should stop saying PKCS#1 is only for private keys. That is no longer the case in v2.2 of PKCS1. Sec 1: "The recommendations are intended to be compatible with the standards IEEE 1363... and ANSI X9.44. This document supersedes PKCS #1 version 2.1 [RFC3447] but includes compatible techniques." Sec 3: "Two key types are employed in the primitives and schemes defined in this document: RSA public key and RSA private key. Together, an RSA public key and an RSA private key form an RSA key pair." – SeligkeitIstInGott Mar 12 '21 at 15:38
  • See also: https://hackernoon.com/public-key-cryptography-rsa-keys-izda3ylv – SeligkeitIstInGott Mar 12 '21 at 15:38
  • You are right about BEGIN PUBLIC KEY being from the X509 Cert Spec, though I still don't understand that, since that spec if for an entire certificate. It's like they ripped out and isolated just one portion of it for certain implementations which have nothing to do with certificates (like SSH or something). PKCS#1 RSAPublicKey* (PEM header: BEGIN RSA PUBLIC KEY) || X.509 SubjectPublicKeyInfo** (PEM header: BEGIN PUBLIC KEY) – SeligkeitIstInGott Mar 12 '21 at 16:02
  • @SeligkeitIstInGott: this is not a change, PKCS1 has always defined both public and private keys but always for RSA only. PKCS8 has always been only for privatekeys but all algorithms, and X.509/PKIX/SPKI has always been only for publickeys but all algorithms, and _for RSA only_ the algorithm-specific _part_ of either PKCS8 _or_ X.509/PKIX/SPKI _is_ the formats from PKCS1. I never said PKCS1 is privatekey only and I didn't see anyone say so either, I said PKCS8 is privatekey only, and that was and is correct. ... – dave_thompson_085 Mar 14 '21 at 07:50
  • ... Your own reference 'hackernoon' correctly says that _for RSA_ (not other algorithms) PKCS8 uses PrivateKey (not PublicKey) from PKCS1, although it seems to suggest that PKCS1 is an earlier 'version' of PKCS8 which is completely wrong. Also it is out of date about OpenSSH; since 2018 OpenSSH no longer uses for privatekey the OpenSSL formats ('traditional'=PKCS1+adhoc+SEC1 _or_ PKCS8) but instead its own 'new' format. This is covered by numerous existing Qs on several stacks which you should refer to if interested, since it is not relevant to _this_ Q. – dave_thompson_085 Mar 14 '21 at 07:54
  • Looks like there are multiple formats of public key, and PKIX encoding takes care of that. But why multiple formats of private key is not considered? – Qi Zhang Jul 02 '21 at 15:46
16

While the above comments regarding 32 byte headers, OID formats and such are interesting, I personally don't see the same behavior, assuming I'm getting the point. I thought it might be helpful to explore this further in what most might think is excessive detail. Nothing exceeds like excess.

To start, I created an RSA private key, and checked it:

>openssl rsa -in newclient_privatekey.pem  -check
RSA key ok
writing RSA key
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQCn/OlFk7vLRQ6dBiNQkvjnhm4pOYWo+GeAEmU4N1HPZj1dxv70
4hm80eYc7h12xc7oVcDLBdHByGAGBpQfpjgdPyozC/zSqcuU6iBrvzDTpyG1zhIG
76KrcjdbX6PlKAPO9r/dCRmUijFhVoUlY6ywGknmLBrtZkLkBhchgYnMswIDAQAB
AoGAQaJ5aivspeEXcpahWavzAFLv27+Tz48usUV+stY6arRhqbBEkV19/N5t8EPA
01U6IGDQ8QIXEIW/rtsHKM6DAZhAbakPDJhJRatcMzJ08ryIkP/c3+onkTquiveG
brw7xzn6Xa8ls04aQ6VQR4jxXUjV5bB72pFZnGRoAmS2NiECQQDUoISbmTGjnHM+
kEfunNTXbNmKklwTYhyZaSVsSptnD7CvLWB4qB/g4h2/HjsELag6Z7SlWuYr7tba
H3nBYn35AkEAykFRudMqlBy3XmcGIpjxOD+7huyViPoUpy3ui/Bj3GbqsbEAt9cR
PyOJa1VFa2JqShta1Tdep8LJv1QvgvY7CwJBAML+al5gAXvwEGhB3RXg0fi2JFLG
opZMFbpDCUTkrtu3MeuVC7HbTVDpTSpmSO0uCed2D97NG+USZgsnbnuBHdECQQCw
S3FWPXdetQ0srzaMz61rLzphaDULuZhpBMNqnTYeNmMaUcPjewagd3Rf52rkKFun
juKE+Yd7SXGbYWEskT5zAkAD7tbNwe5ryD2CT71jrY/5uXMR2yg/A4Ry2ocZkQUp
iGflLrHnODvHO5LYLBlSKpjanBceYHJLuMFNZruf7uBM
-----END RSA PRIVATE KEY-----

(Oh, horrors! I've exposed a private key. Meh...)

I extract and display its public key:

>openssl rsa -in newclient_privatekey.pem -pubout
writing RSA key
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCn/OlFk7vLRQ6dBiNQkvjnhm4p
OYWo+GeAEmU4N1HPZj1dxv704hm80eYc7h12xc7oVcDLBdHByGAGBpQfpjgdPyoz
C/zSqcuU6iBrvzDTpyG1zhIG76KrcjdbX6PlKAPO9r/dCRmUijFhVoUlY6ywGknm
LBrtZkLkBhchgYnMswIDAQAB
-----END PUBLIC KEY-----

It so happens there's another public key output parameter (as is mentioned in an earlier comment). I extract and display the public key using that keyword instead:

>openssl rsa -in newclient_privatekey.pem -RSAPublicKey_out
writing RSA key
-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBAKf86UWTu8tFDp0GI1CS+OeGbik5haj4Z4ASZTg3Uc9mPV3G/vTiGbzR
5hzuHXbFzuhVwMsF0cHIYAYGlB+mOB0/KjML/NKpy5TqIGu/MNOnIbXOEgbvoqty
N1tfo+UoA872v90JGZSKMWFWhSVjrLAaSeYsGu1mQuQGFyGBicyzAgMBAAE=
-----END RSA PUBLIC KEY-----

Well, well. These two public key values aren't the same, though they're derived from the same private key. Or are they the same? I cut and paste the two public key strings into their own files, and then do a modulus check on each:

>openssl rsa -in newclient_publickey.pem -pubin -modulus
Modulus=
A7FCE94593BBCB450E9D06235092F8E7
866E293985A8F867801265383751CF66
3D5DC6FEF4E219BCD1E61CEE1D76C5CE
E855C0CB05D1C1C8600606941FA6381D
3F2A330BFCD2A9CB94EA206BBF30D3A7
21B5CE1206EFA2AB72375B5FA3E52803
CEF6BFDD0919948A316156852563ACB0
1A49E62C1AED6642E40617218189CCB3
writing RSA key
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCn/OlFk7vLRQ6dBiNQkvjnhm4p
OYWo+GeAEmU4N1HPZj1dxv704hm80eYc7h12xc7oVcDLBdHByGAGBpQfpjgdPyoz
C/zSqcuU6iBrvzDTpyG1zhIG76KrcjdbX6PlKAPO9r/dCRmUijFhVoUlY6ywGknm
LBrtZkLkBhchgYnMswIDAQAB
-----END PUBLIC KEY-----

The 'pubin' tells rsa that this really is supposed to be a public key, and don't complain that it's not a private key.

Now we take the RSA public key, display the modulus, and transmogrify it into a plain old 'public key' (again, we have to tell it the input is a public key):

>openssl rsa -in newclient_rsapublickey.pem -RSAPublicKey_in -modulus
Modulus=
A7FCE94593BBCB450E9D06235092F8E7
866E293985A8F867801265383751CF66
3D5DC6FEF4E219BCD1E61CEE1D76C5CE
E855C0CB05D1C1C8600606941FA6381D
3F2A330BFCD2A9CB94EA206BBF30D3A7
21B5CE1206EFA2AB72375B5FA3E52803
CEF6BFDD0919948A316156852563ACB0
1A49E62C1AED6642E40617218189CCB3
writing RSA key
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCn/OlFk7vLRQ6dBiNQkvjnhm4p
OYWo+GeAEmU4N1HPZj1dxv704hm80eYc7h12xc7oVcDLBdHByGAGBpQfpjgdPyoz
C/zSqcuU6iBrvzDTpyG1zhIG76KrcjdbX6PlKAPO9r/dCRmUijFhVoUlY6ywGknm
LBrtZkLkBhchgYnMswIDAQAB
-----END PUBLIC KEY-----

Same modulus, and same 'public key' value displayed. To make things more interesting (for me, anyway), when we tack on the RSAPublicKey_out keyword we get:

>openssl rsa -in newclient_rsapublickey.pem -RSAPublicKey_in -modulus -RSAPublicKey_out
Modulus=
A7FCE94593BBCB450E9D06235092F8E7
866E293985A8F867801265383751CF66
3D5DC6FEF4E219BCD1E61CEE1D76C5CE
E855C0CB05D1C1C8600606941FA6381D
3F2A330BFCD2A9CB94EA206BBF30D3A7
21B5CE1206EFA2AB72375B5FA3E52803
CEF6BFDD0919948A316156852563ACB0
1A49E62C1AED6642E40617218189CCB3
writing RSA key
-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBAKf86UWTu8tFDp0GI1CS+OeGbik5haj4Z4ASZTg3Uc9mPV3G/vTiGbzR
5hzuHXbFzuhVwMsF0cHIYAYGlB+mOB0/KjML/NKpy5TqIGu/MNOnIbXOEgbvoqty
N1tfo+UoA872v90JGZSKMWFWhSVjrLAaSeYsGu1mQuQGFyGBicyzAgMBAAE=
-----END RSA PUBLIC KEY-----

...and when we transmogrify the plain old 'public key' into an RSA public key:

>openssl rsa -in newclient_publickey.pem -pubin -RSAPublicKey_out
writing RSA key
-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBAKf86UWTu8tFDp0GI1CS+OeGbik5haj4Z4ASZTg3Uc9mPV3G/vTiGbzR
5hzuHXbFzuhVwMsF0cHIYAYGlB+mOB0/KjML/NKpy5TqIGu/MNOnIbXOEgbvoqty
N1tfo+UoA872v90JGZSKMWFWhSVjrLAaSeYsGu1mQuQGFyGBicyzAgMBAAE=
-----END RSA PUBLIC KEY-----

...marching on relentlessly, and although we just did this a few commands ago, to make the point we flip things around so the transmogrification is from RSA to plain old 'public key':

>openssl rsa -in newclient_rsapublickey.pem -RSAPublicKey_in -pubout
writing RSA key
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCn/OlFk7vLRQ6dBiNQkvjnhm4p
OYWo+GeAEmU4N1HPZj1dxv704hm80eYc7h12xc7oVcDLBdHByGAGBpQfpjgdPyoz
C/zSqcuU6iBrvzDTpyG1zhIG76KrcjdbX6PlKAPO9r/dCRmUijFhVoUlY6ywGknm
LBrtZkLkBhchgYnMswIDAQAB
-----END PUBLIC KEY-----

...which takes us right back where we started. What have we learned?

Summary: the keys internally are the same, they just look different. An earlier comment pointed out the RSA key format was defined in PKCS#1, and the plain old 'public key' format was defined in PKCS#8. However, editing one form doesn't turn it into the other. Hopefully I've now beaten this distinction to death.

In case there's still a spark of life left, though, let's flog this a bit more and reference the certificate that was originally generated with the RSA private key so long ago, examining its public key and modulus:

>openssl x509 -in newclient_cert.pem -pubkey -noout -modulus
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCn/OlFk7vLRQ6dBiNQkvjnhm4p
OYWo+GeAEmU4N1HPZj1dxv704hm80eYc7h12xc7oVcDLBdHByGAGBpQfpjgdPyoz
C/zSqcuU6iBrvzDTpyG1zhIG76KrcjdbX6PlKAPO9r/dCRmUijFhVoUlY6ywGknm
LBrtZkLkBhchgYnMswIDAQAB
-----END PUBLIC KEY-----
Modulus=
A7FCE94593BBCB450E9D06235092F8E7
866E293985A8F867801265383751CF66
3D5DC6FEF4E219BCD1E61CEE1D76C5CE
E855C0CB05D1C1C8600606941FA6381D
3F2A330BFCD2A9CB94EA206BBF30D3A7
21B5CE1206EFA2AB72375B5FA3E52803
CEF6BFDD0919948A316156852563ACB0
1A49E62C1AED6642E40617218189CCB3

...and they all lived happily ever after: the certificate has the same modulus value as the RSA public key, RSA private key, and plain old 'public key'. The certificate contains the same plain old 'public key' value that we saw earlier, although it was signed with a file marked as an RSA private key. It's safe to say there's a consensus.

There's no 'RSAPublicKey_out' equivalent keyword in the X509 quadrant of the OpenSSL galaxy, so we can't try that, although the modulus value is described as the "RSA key modulus" which I suppose is as close as we'll get.

How this would all look with a DSA-signed certificate, I don't know.

I realize this doesn't answer the original question, but perhaps it provides some useful background. If not, my apologies. At the very least, things not to do and assumptions not to make.

No doubt one has noted the slightly irritating repetition of "writing RSA key", when it's not doing any such thing. I assume what's meant is that the rsa module recognizes the plain old public key as a true RSA key, and that's why it keeps harping on "RSA key" (plus it is the rsa module, after all). If I recall properly, the generic EVP_PKEY structure has a union for all the key types, with each key type having its own special set of values (the helpfully named g, w, q, and other consonants).

In conclusion, I note there was a complaint regarding programming & development; now, every OpenSSL command obviously has corresponding code, and if one wishes to explore all the wonders that is OpenSSL programming today, the command line would seem a reasonable place to start. In this particular case (as I'm using a recent cygwin at the moment) one might start by reviewing \openssl-1.0.2f\apps\rsa.c and (given one has a high tolerance for macros) \openssl-1.0.2f\crypto\pem\pem_all.c

Lark
  • 161
  • 1
  • 2
14

Using phpseclib, a pure PHP RSA implementation...

<?php
include('Crypt/RSA.php');

$rsa = new Crypt_RSA();
$rsa->loadKey('-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA61BjmfXGEvWmegnBGSuS
+rU9soUg2FnODva32D1AqhwdziwHINFaD1MVlcrYG6XRKfkcxnaXGfFDWHLEvNBS
EVCgJjtHAGZIm5GL/KA86KDp/CwDFMSwluowcXwDwoyinmeOY9eKyh6aY72xJh7n
oLBBq1N0bWi1e2i+83txOCg4yV2oVXhBo8pYEJ8LT3el6Smxol3C1oFMVdwPgc0v
Tl25XucMcG/ALE/KNY6pqC2AQ6R2ERlVgPiUWOPatVkt7+Bs3h5Ramxh7XjBOXeu
lmCpGSynXNcpZ/06+vofGi/2MlpQZNhHAo8eayMp6FcvNucIpUndo1X8dKMv3Y26
ZQIDAQAB
-----END PUBLIC KEY-----');
$rsa->setPublicKey();

echo $rsa->getPublicKey(CRYPT_RSA_PUBLIC_FORMAT_PKCS1_RAW);

The base64-encoded stuff appears to match even though the header says BEGIN PUBLIC KEY and not BEGIN RSA PUBLIC KEY. So maybe just use str_replace to fix that and you should be good to go!

10

The only difference between your pub1 and pub2, besides the header/footer, is this additional string in pub2: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A. If you remove that, the Base 64 is identical to that in pub1.

The extra string corresponds to the algorithm identifier according to this Answer.

Community
  • 1
  • 1
gtrig
  • 11,442
  • 5
  • 27
  • 35