How to store/retrieve RSA public/private key
Asked Answered
V

6

62

I want to use RSA public key encryption. What is the best way to store or retrieve private and public keys? Is XML a good idea here?

How to get the keys?

RSAParameters privateKey = RSA.ExportParameters(true);
RSAParameters publicKey = RSA.ExportParameters(false);

Because RSAParameters have the following members: D, DP, DQ, Exponent, InverseQ, Modulus, P, Q

Which one is the key?

Vatican answered 28/7, 2009 at 11:35 Comment(0)
S
49

What I have done successfully is to store the keys as XML. There are two methods in RSACryptoServiceProvider: ToXmlString and FromXmlString. The ToXmlString will return an XML string containing either just the public key data or both the public and private key data depending on how you set its parameter. The FromXmlString method will populate the RSACryptoServiceProvider with the appropriate key data when provided an XML string containing either just the public key data or both the public and private key data.

Ssw answered 28/7, 2009 at 13:8 Comment(3)
Sorry for bringing this up, but why would you store the private key in an XML? Is using an RSA Key Container an overkill?Tav
@LOSTCODER The value of XML is that there are at least 7 other standards of how to store RSA key material. At least with XML you can see the values (modulus, exponent for public key, D, P, Q, DP, DQ, InverseQ) simply as base64 encoded byte data. From there you can convert it back into PKCS#1, PKCS#7, PKCS#8, PFX, certificate, OpenSSH, OpenSSL, etc. In short: XML is portable, while everything else is complicated proprietary (e.g. ASN.1 DER, which doesn't even have well defined standards)Stearic
While the treatise by Ian Boyd is amazing, it is too general and does not answer the question that was asked (which he admitted). HOWEVER, the answer here by Joe Kuemerle solves the problem. If you are storing the public or private key as XML, you can retrieve it this way. 1. csp = new RSACryptoServiceProvider(); 2. csp.FromXmlString(_publicKey); where _publicKey is a string retrieved from somewhere (database and file are the most common sources)Graphitize
S
230

I wanted to point out something as a response to a comment by ala asking if:

Public Key = modulus + exponent

That is exactly correct. There are a few ways of storing this exponent + modulus. The first attempt at a standard was in RFC 3447 (Public-Key Cryptography Standards (PKCS) #1: RSA Cryptography Specifications Version 2.1), which defines a structure for a public key of called RSAPublicKey:

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

The same RFC goes on to declare that you should use the DER flavor of ASN.1 encoding to store the public key. i have a sample public key:

  • publicExponent: 65537 (it is convention that all RSA public keys use 65537 as their exponent)
  • modulus: 0xDC 67 FA F4 9E F2 72 1D 45 2C B4 80 79 06 A0 94 27 50 8209 DD 67 CE 57 B8 6C 4A 4F 40 9F D2 D1 69 FB 995D 85 0C 07 A1 F9 47 1B 56 16 6E F6 7F B9 CF 2A 58 36 37 99 29 AA 4F A8 12 E8 4F C7 82 2B 9D 72 2A 9C DE 6F C2 EE 12 6D CF F0 F2 B8 C4 DD 7C 5C 1A C8 17 51 A9 AC DF 08 22 04 9D 2B D7 F9 4B 09 DE 9A EB 5C 51 1A D8 F8 F9 56 9E F8 FB 37 9B 3F D3 74 65 24 0D FF 34 75 57 A4 F5 BF 55

The DER ASN.1 encoding of this public key is:

30 81 89          ;SEQUENCE (0x89 bytes = 137 bytes)
|  02 81 81       ;INTEGER (0x81 bytes = 129 bytes)
|  |  00          ;leading zero of INTEGER
|  |  DC 67 FA
|  |  F4 9E F2 72 1D 45 2C B4  80 79 06 A0 94 27 50 82
|  |  09 DD 67 CE 57 B8 6C 4A  4F 40 9F D2 D1 69 FB 99
|  |  5D 85 0C 07 A1 F9 47 1B  56 16 6E F6 7F B9 CF 2A
|  |  58 36 37 99 29 AA 4F A8  12 E8 4F C7 82 2B 9D 72
|  |  2A 9C DE 6F C2 EE 12 6D  CF F0 F2 B8 C4 DD 7C 5C
|  |  1A C8 17 51 A9 AC DF 08  22 04 9D 2B D7 F9 4B 09
|  |  DE 9A EB 5C 51 1A D8 F8  F9 56 9E F8 FB 37 9B 3F
|  |  D3 74 65 24 0D FF 34 75  57 A4 F5 BF 55
|  02 03          ;INTEGER (0x03 = 3 bytes)
|  |  01 00 01    ;hex for 65537. see it?

If you take that entire above DER ASN.1 encoded modulus+exponent:

30 81 89 02 81 81 00 DC 67 FA F4 9E F2 72 1D 45 2C B4 80 79 06 A0 94 27 50 82 09 DD 67 CE 57 B8 6C 4A 4F 40 9F D2 D1 69 FB 99 5D 85 0C 07 A1 F9 47 1B 56 16 6E F6 7F B9 CF 2A 58 36 37 99 29 AA 4F A8 12 E8 4F C7 82 2B 9D 72 2A 9C DE 6F C2 EE 12 6D CF F0 F2 B8 C4 DD 7C 5C 1A C8 17 51 A9 AC DF 08 22 04 9D 2B D7 F9 4B 09 DE 9A EB 5C 51 1A D8 F8 F9 56 9E F8 FB 37 9B 3F D3 74 65 24 0D FF 34 75 57 A4 F5 BF 55 02 03 01 00 01

and you PEM encode it (i.e. base64):

MIGJAoGBANxn+vSe8nIdRSy0gHkGoJQnUIIJ3WfOV7hsSk9An9LRafuZXY
UMB6H5RxtWFm72f7nPKlg2N5kpqk+oEuhPx4IrnXIqnN5vwu4Sbc/w8rjE
3XxcGsgXUams3wgiBJ0r1/lLCd6a61xRGtj4+Vae+Ps3mz/TdGUkDf80dV
ek9b9VAgMBAAE=

It's a convention to wrap that base64 encoded data in:

-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBANxn+vSe8nIdRSy0gHkGoJQnUIIJ3WfOV7hsSk9An9LRafuZXY
UMB6H5RxtWFm72f7nPKlg2N5kpqk+oEuhPx4IrnXIqnN5vwu4Sbc/w8rjE
3XxcGsgXUams3wgiBJ0r1/lLCd6a61xRGtj4+Vae+Ps3mz/TdGUkDf80dV
ek9b9VAgMBAAE=
-----END RSA PUBLIC KEY-----

And that's how you get an have a PEM DER ASN.1 PKCS#1 RSA Public key.


The next standard was RFC 4716 (The Secure Shell (SSH) Public Key File Format). They included an algorithm identifier (ssh-rsa), before the exponent and modulus:

string    "ssh-rsa"
mpint     e
mpint     n

They didn't want to use DER ASN.1 encoding (as it is horrendously complex), and instead opted for 4-byte length prefixing:

00000007                 ;7 byte algorithm identifier
73 73 68 2d 72 73 61     ;"ssh-rsa"
00000003                 ;3 byte exponent
01 00 01                 ;hex for 65,537 
00000080                 ;128 byte modulus
DC 67 FA F4 9E F2 72 1D  45 2C B4 80 79 06 A0 94 
27 50 82 09 DD 67 CE 57  B8 6C 4A 4F 40 9F D2 D1 
69 FB 99 5D 85 0C 07 A1  F9 47 1B 56 16 6E F6 7F 
B9 CF 2A 58 36 37 99 29  AA 4F A8 12 E8 4F C7 82 
2B 9D 72 2A 9C DE 6F C2  EE 12 6D CF F0 F2 B8 C4 
DD 7C 5C 1A C8 17 51 A9  AC DF 08 22 04 9D 2B D7 
F9 4B 09 DE 9A EB 5C 51  1A D8 F8 F9 56 9E F8 FB 
37 9B 3F D3 74 65 24 0D  FF 34 75 57 A4 F5 BF 55

Take the entire above byte sequence and base-64 encode it:

AAAAB3NzaC1yc2EAAAADAQABAAAAgNxn+vSe8nIdRSy0gHkGoJQnUIIJ3WfOV7hs
Sk9An9LRafuZXYUMB6H5RxtWFm72f7nPKlg2N5kpqk+oEuhPx4IrnXIqnN5vwu4S
bc/w8rjE3XxcGsgXUams3wgiBJ0r1/lLCd6a61xRGtj4+Vae+Ps3mz/TdGUkDf80
dVek9b9V

And wrap it in the OpenSSH header and trailer:

---- BEGIN SSH2 PUBLIC KEY ----
AAAAB3NzaC1yc2EAAAADAQABAAAAgNxn+vSe8nIdRSy0gHkGoJQnUIIJ3WfOV7hs
Sk9An9LRafuZXYUMB6H5RxtWFm72f7nPKlg2N5kpqk+oEuhPx4IrnXIqnN5vwu4S
bc/w8rjE3XxcGsgXUams3wgiBJ0r1/lLCd6a61xRGtj4+Vae+Ps3mz/TdGUkDf80
dVek9b9V
---- END SSH2 PUBLIC KEY ----

Note: That OpenSSH uses four dashes with a space (---- ) rather than five dashes and no space (-----).


The next standard was RFC 2459 (Internet X.509 Public Key Infrastructure Certificate and CRL Profile). They took the PKCS#1 public key format:

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

and extended it to include an algorithm identifier prefix (in case you want to use a public key encryption algorithm other than RSA):

SubjectPublicKeyInfo  ::=  SEQUENCE  {
    algorithm            AlgorithmIdentifier,
    subjectPublicKey     RSAPublicKey }

The "Algorithm Identifier" for RSA is 1.2.840.113549.1.1.1, which comes from:

  • 1 - ISO assigned OIDs
    • 1.2 - ISO member body
      • 1.2.840 - USA
        • 1.2.840.113549 - RSADSI
          • 1.2.840.113549.1 - PKCS
            • 1.2.840.113549.1.1 - PKCS-1

The X.509 is an awful standard, that defines a horribly complicated way of encoding an OID into hex, but in the end the DER ASN.1 encoding of an X.509 SubjectPublicKeyInfo RSA Public key is:

30 81 9F            ;SEQUENCE (0x9f bytes = 159 bytes)
|  30 0D            ;SEQUENCE (0x0d bytes = 13 bytes)
|  |  06 09         ;OBJECT_IDENTIFIER (0x09 = 9 bytes)
|  |  2A 86 48 86   ;Hex encoding of 1.2.840.113549.1.1
|  |  F7 0D 01 01 01
|  |  05 00         ;NULL (0 bytes)
|  03 81 8D 00      ;BIT STRING (0x8d bytes = 141 bytes)
|  |  30 81 89          ;SEQUENCE (0x89 bytes = 137 bytes)
|  |  |  02 81 81       ;INTEGER (0x81 bytes = 129 bytes)
|  |  |  00          ;leading zero of INTEGER
|  |  |  DC 67 FA
|  |  |  F4 9E F2 72 1D 45 2C B4  80 79 06 A0 94 27 50 82
|  |  |  09 DD 67 CE 57 B8 6C 4A  4F 40 9F D2 D1 69 FB 99
|  |  |  5D 85 0C 07 A1 F9 47 1B  56 16 6E F6 7F B9 CF 2A
|  |  |  58 36 37 99 29 AA 4F A8  12 E8 4F C7 82 2B 9D 72
|  |  |  2A 9C DE 6F C2 EE 12 6D  CF F0 F2 B8 C4 DD 7C 5C
|  |  |  1A C8 17 51 A9 AC DF 08  22 04 9D 2B D7 F9 4B 09
|  |  |  DE 9A EB 5C 51 1A D8 F8  F9 56 9E F8 FB 37 9B 3F
|  |  |  D3 74 65 24 0D FF 34 75  57 A4 F5 BF 55
|  |  02 03          ;INTEGER (0x03 = 3 bytes)
|  |  |  01 00 01    ;hex for 65537. see it?

You can see in the decoded ASN.1 how they just prefixed the old RSAPublicKey with an OBJECT_IDENTIFIER.

Taking the above bytes and PEM (i.e. base-64) encoding them:

MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcZ/r0nvJyHUUstIB5BqCUJ1CC
Cd1nzle4bEpPQJ/S0Wn7mV2FDAeh+UcbVhZu9n+5zypYNjeZKapPqBLoT8eCK51y
Kpzeb8LuEm3P8PK4xN18XBrIF1GprN8IIgSdK9f5SwnemutcURrY+PlWnvj7N5s/
03RlJA3/NHVXpPW/VQIDAQAB

The standard is then to wrap this with a header similar to RSA PKCS#1, but without the "RSA" (since it could be something other than RSA):

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcZ/r0nvJyHUUstIB5BqCUJ1CC
Cd1nzle4bEpPQJ/S0Wn7mV2FDAeh+UcbVhZu9n+5zypYNjeZKapPqBLoT8eCK51y
Kpzeb8LuEm3P8PK4xN18XBrIF1GprN8IIgSdK9f5SwnemutcURrY+PlWnvj7N5s/
03RlJA3/NHVXpPW/VQIDAQAB
-----END PUBLIC KEY-----

And that's how you invent an X.509 SubjectPublicKeyInfo/OpenSSL PEM public key format.


That doesn't stop the list of standard formats for an RSA public key. Next is the proprietary public key format used by OpenSSH:

ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgNxn+vSe8nIdRSy0gHkGoJQnUIIJ3WfOV7hs Sk9An9LRafuZXYUMB6H5RxtWFm72f7nPKlg2N5kpqk+oEuhPx4IrnXIqnN5vwu4Sbc/w8rjE3XxcGsgXUams3wgiBJ0r1/lLCd6a61xRGtj4+Vae+Ps3mz/TdGUkDf80dVek9b9V

Which is actually the SSH public key format above, but prefixed with ssh-rsa, rather than wrapped in ---- BEGIN SSH2 PUBLIC KEY ----/---- END SSH2 PUBLIC KEY ----.


This is where the ease of the XML RSAKeyValue public key comes in:

  • Exponent: 0x 010001 base64 encoded is AQAB
  • Modulus: 0x 00 dc 67 fa f4 9e f2 72 1d 45 2c b4 80 79 06 a0 94 27 50 82 09 dd 67 ce 57 b8 6c 4a 4f 40 9f d2 d1 69 fb 99 5d 85 0c 07 a1 f9 47 1b 56 16 6e f6 7f b9 cf 2a 58 36 37 99 29 aa 4f a8 12 e8 4f c7 82 2b 9d 72 2a 9c de 6f c2 ee 12 6d cf f0 f2 b8 c4 dd 7c 5c 1a c8 17 51 a9 ac df 08 22 04 9d 2b d7 f9 4b 09 de 9a eb 5c 51 1a d8 f8 f9 56 9e f8 fb 37 9b 3f d3 74 65 24 0d ff 34 75 57 a4 f5 bf 55 base64 encoded is ANxn+vSe8nIdRSy0gHkGoJQnUIIJ3WfOV7hsSk9An9LRafuZXYUMB6H5RxtWFm72f7nPKlg2N5kpqk+oEuhPx4IrnXIqnN5vwu4Sbc/w8rjE3XxcGsgXUams3wgiBJ0r1/lLCd6a61xRGtj4+Vae+Ps3mz/TdGUkDf80dVek9b9V.

This means the XML is:

<RSAKeyValue>
   <Modulus>ANxn+vSe8nIdRSy0gHkGoJQnUIIJ3WfOV7hsSk9An9LRafuZXYUMB6H5RxtWFm72f7nPKlg2N5kpqk+oEuhPx4IrnXIqnN5vwu4Sbc/w8rjE3XxcGsgXUams3wgiBJ0r1/lLCd6a61xRGtj4+Vae+Ps3mz/TdGUkDf80dVek9b9V</Modulus>
   <Exponent>AQAB</Exponent>
</RSAKeyValue>

Much simpler. A downside is that it doesn't wrap, copy, paste, as nicely as (i.e. Xml is not as user friendly as):

-----BEGIN RSA PUBLIC KEY-----
MIGJAoGBANxn+vSe8nIdRSy0gHkGoJQnUIIJ3WfOV7hsSk9An9LRafuZXY
UMB6H5RxtWFm72f7nPKlg2N5kpqk+oEuhPx4IrnXIqnN5vwu4Sbc/w8rjE
3XxcGsgXUams3wgiBJ0r1/lLCd6a61xRGtj4+Vae+Ps3mz/TdGUkDf80dV
ek9b9VAgMBAAE=
-----END RSA PUBLIC KEY-----

But it makes a great neutral storage format.

See also

  • Translator, Binary: Great for decoding and encoding base64 data
  • ASN.1 JavaScript decoder: Great for decoding ASN.1 encoded hex data (that you get from Translator, Binary
  • Microsoft ASN.1 Documentation: Describes the Distinguished Encoding Rules (DER) used for ASN.1 structures (you won't find a better set of documentation anywhere else; i would argue Microsoft's is not only real documentation)
Stearic answered 27/10, 2012 at 21:45 Comment(10)
Great answer! Do not answer precisely to the related question, but is an extensive and reliable source of information on RSA keys. Impressive work :oKnipe
Could I ask a question? Why is the modulus made up of so many characters? Is it some big number encoded in a particular way? Thanks!Schleswig
@kevinze Yes, it is a big number encoded in a particular way. It is shown in hexadecimal format, rather than decimal format. In the same way that 78 (decimal) = 4E (hexadecimal), and `65,535 (decimal) = FFFF (hexadecimal), the really long value you see (shown in hexadecimal, with a space every 2 characters) is a really large number in decimal.Stearic
@kevinze In fact the public key in decimal is 154,774,478,177,095,248,394,968,828,543,369,801,032,226,937,226,535,865,231,262,824,893,513,573,019,304,152,154,974,259,955,740,337,204,606,655,133,945,162,319,470,662,684,517,274,530,901,497,375,379,716,962,851,415,879,364,453,962,123,395,223,899,051,919,634,994,929,603,613,704,222,239,797,911,292,193,776,910,691,509,004,328,773,391,280,872,757,318,122,152,217,457,361,921,195,935,350,223,751,896,771,182,421. Which if i had to put a name to it would be "one hundred and fifty four million googol googol googol"Stearic
Great answer - thanks so much. Please note that it appears that a leading zero is added when writing out the modulus (and presumably the exponent) if it is eight-bit high. The most useful hint about this I can find is a comment in github.com/openssh/openssh-portable which says "/* If MSB is set, prepend a \0 */" when writing out bignums in the ssh-rsa format. No idea where this actually comes from.Musketry
@TimPotter You would have to have a leading zero on an integer. If the high bit is set, that would mean the number is negative.Stearic
Good explanation of the relationship between formats, but one small point: PEM uses 5 dashes not equals. SSH2 does vary that to 4 and one space, but doesn't change the character.Flagstad
The stupidity of using the XML variant of the public key encoding is that nobody else uses it. As it is again one of these Microsoft only solutions, it has very limited value.Diggs
Absolutely disagree. Using SubjectPublicKeyInfo would be most compatible, well defined and obvious solution. Besides that, the modulus and public exponent are integers. There is no reason to use binary encoding here (endianess issues etc.).Diggs
@IanBoyd I decoded public key using Base64. I got 00000007 ;7 byte algorithm identifier 73 73 68 2d 72 73 61 ;"ssh-rsa" 00000003 ;3 byte exponent 01 00 01 ;hex for 65,537 00000081 ;129 byte modulus (the public key bit is 1028 bit) but i saw the modulus was 129Estebanesteem
S
49

What I have done successfully is to store the keys as XML. There are two methods in RSACryptoServiceProvider: ToXmlString and FromXmlString. The ToXmlString will return an XML string containing either just the public key data or both the public and private key data depending on how you set its parameter. The FromXmlString method will populate the RSACryptoServiceProvider with the appropriate key data when provided an XML string containing either just the public key data or both the public and private key data.

Ssw answered 28/7, 2009 at 13:8 Comment(3)
Sorry for bringing this up, but why would you store the private key in an XML? Is using an RSA Key Container an overkill?Tav
@LOSTCODER The value of XML is that there are at least 7 other standards of how to store RSA key material. At least with XML you can see the values (modulus, exponent for public key, D, P, Q, DP, DQ, InverseQ) simply as base64 encoded byte data. From there you can convert it back into PKCS#1, PKCS#7, PKCS#8, PFX, certificate, OpenSSH, OpenSSL, etc. In short: XML is portable, while everything else is complicated proprietary (e.g. ASN.1 DER, which doesn't even have well defined standards)Stearic
While the treatise by Ian Boyd is amazing, it is too general and does not answer the question that was asked (which he admitted). HOWEVER, the answer here by Joe Kuemerle solves the problem. If you are storing the public or private key as XML, you can retrieve it this way. 1. csp = new RSACryptoServiceProvider(); 2. csp.FromXmlString(_publicKey); where _publicKey is a string retrieved from somewhere (database and file are the most common sources)Graphitize
F
2

Use a existing standard format, like PEM. Your crypto library should provide functions to load and save keys from files in PEM format.

Exponent and Modulus are the Public key. D and Modulus are the Private key. The other values allow faster computation for the holder of the Private key.

Fictionist answered 28/7, 2009 at 11:43 Comment(2)
so public-key = exponent + modulus (concatenated together?)Vatican
Exponent and Modulus as two independent values, stored in whatever way makes sense. The PEM format uses ASN1 to store these values (defined in the PKCS#1 standard as the "SubjectPublicKeyInfo" format), then base64 encodes the result.Fictionist
C
0

The public key is identified by Modulus and Exponent. The private key is identified by the other members.

Columelliform answered 28/7, 2009 at 11:42 Comment(0)
M
0

I suppose answer from @Ian Boyd would not precisely, the format should be SSH2, instead of OpenSSH, as the RFC4716 defined for SSH2, OpenSSH format have is proprietary:

Note: That OpenSSH uses four dashes with a space (---- ) rather than five dashes and no space (-----).

Meridith answered 27/12, 2021 at 9:20 Comment(0)
P
-1

Is XML a good idea here?

Normally Private keys are stored in HSM's/smart card. This provides a good security.

Prelusive answered 30/7, 2009 at 13:2 Comment(2)
you're probably right, but I need to implement private/public key using software, what do you think is the best way is?Vatican
This is one of the requirements for my future project. In my current one, we used HSM. My initial readings shows that MS has secure stores to store keys. You can access keys from these secure stores. Other alternatives to MS are [Link][1] [1]: trac.opendnssec.org/wiki/HSM/SoftTokensPrelusive

© 2022 - 2024 — McMap. All rights reserved.