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?
<
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.
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:
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-1The 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:
0x 010001
base64 encoded is AQAB
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.
Translator, Binary
The public key is identified by Modulus and Exponent. The private key is identified by the other members.
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.
Is XML a good idea here?
Normally Private keys are stored in HSM's/smart card. This provides a good security.