Last active
August 3, 2017 14:43
-
-
Save e23z/ebaec2311d5c244b6d04793caa79dc45 to your computer and use it in GitHub Desktop.
[RSA Keys Import & Export] How to import and export C# generated RSA keys to and from the PEM format. #cryptography #security #rsa #csharp #function
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.IO; | |
using System.Security.Cryptography; | |
using System.Text; | |
namespace myapp { | |
public static class PEMExporter { | |
public static string Export(RSA rsa, bool exportPrivateKey) { | |
var parameters = rsa.ExportParameters(includePrivateParameters : exportPrivateKey); | |
var key = new StringBuilder(); | |
using(var stream = new MemoryStream()) { | |
var writer = new BinaryWriter(stream); | |
writer.Write((byte) 0x30); // SEQUENCE | |
using(var innerStream = new MemoryStream()) { | |
var innerWriter = new BinaryWriter(innerStream); | |
EncodeIntegerBigEndian(innerWriter, new byte[] { 0x00 }); // Version | |
EncodeIntegerBigEndian(innerWriter, parameters.Modulus); | |
EncodeIntegerBigEndian(innerWriter, parameters.Exponent); | |
EncodeIntegerBigEndian(innerWriter, exportPrivateKey ? parameters.D : parameters.Exponent); | |
EncodeIntegerBigEndian(innerWriter, exportPrivateKey ? parameters.P : parameters.Exponent); | |
EncodeIntegerBigEndian(innerWriter, exportPrivateKey ? parameters.Q : parameters.Exponent); | |
EncodeIntegerBigEndian(innerWriter, exportPrivateKey ? parameters.DP : parameters.Exponent); | |
EncodeIntegerBigEndian(innerWriter, exportPrivateKey ? parameters.DQ : parameters.Exponent); | |
EncodeIntegerBigEndian(innerWriter, exportPrivateKey ? parameters.InverseQ : parameters.Exponent); | |
var length = (int) innerStream.Length; | |
EncodeLength(writer, length); | |
ArraySegment<byte> innerBuffer = new ArraySegment<byte>(); | |
innerStream.TryGetBuffer(out innerBuffer); | |
writer.Write(innerBuffer.Array, 0, length); | |
} | |
ArraySegment<byte> buffer = new ArraySegment<byte>(); | |
stream.TryGetBuffer(out buffer); | |
var base64 = Convert.ToBase64String(buffer.Array, 0, (int) stream.Length); | |
key.AppendLine(exportPrivateKey ? "-----BEGIN RSA PRIVATE KEY-----" : "-----BEGIN PUBLIC KEY-----"); | |
for (var i = 0; i < base64.Length; i += 64) { | |
key.AppendLine(base64.Substring(i, Math.Min(64, base64.Length - i))); | |
} | |
key.AppendLine(exportPrivateKey ? "-----END RSA PRIVATE KEY-----" : "-----END PUBLIC KEY-----"); | |
} | |
return key.ToString(); | |
} | |
static void EncodeLength(BinaryWriter stream, int length) { | |
if (length < 0) throw new ArgumentOutOfRangeException("length", "Length must be non-negative"); | |
if (length < 0x80) { // Short form | |
stream.Write((byte) length); | |
} else { // Long form | |
var temp = length; | |
var bytesRequired = 0; | |
while (temp > 0) { | |
temp >>= 8; | |
bytesRequired++; | |
} | |
stream.Write((byte)(bytesRequired | 0x80)); | |
for (var i = bytesRequired - 1; i >= 0; i--) { | |
stream.Write((byte)(length >>(8 * i) & 0xff)); | |
} | |
} | |
} | |
static void EncodeIntegerBigEndian(BinaryWriter stream, byte[] value, bool forceUnsigned = true) { | |
stream.Write((byte) 0x02); // INTEGER | |
var prefixZeros = 0; | |
for (var i = 0; i < value.Length; i++) { | |
if (value[i] != 0) break; | |
prefixZeros++; | |
} | |
if (value.Length - prefixZeros == 0) { | |
EncodeLength(stream, 1); | |
stream.Write((byte) 0); | |
} else { | |
if (forceUnsigned && value[prefixZeros] > 0x7f) { | |
// Add a prefix zero to force unsigned if the MSB is 1 | |
EncodeLength(stream, value.Length - prefixZeros + 1); | |
stream.Write((byte) 0); | |
} else { | |
EncodeLength(stream, value.Length - prefixZeros); | |
} | |
for (var i = prefixZeros; i < value.Length; i++) { | |
stream.Write(value[i]); | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.IO; | |
using System.Security; | |
using System.Security.Cryptography; | |
using System.Text; | |
namespace myapp { | |
public static class PEMLoader { | |
const string PrivateHeader = "-----BEGIN RSA PRIVATE KEY-----"; | |
const string PrivateFooter = "-----END RSA PRIVATE KEY-----"; | |
const string PublicHeader = "-----BEGIN PUBLIC KEY-----"; | |
const string PublicFooter = "-----END PUBLIC KEY-----"; | |
public static RSA GetRSAFromPEM(string pemStr) { | |
pemStr = pemStr.Trim(); | |
var isPrivateKey = pemStr.StartsWith(PrivateHeader) && pemStr.EndsWith(PrivateFooter); | |
var keyBytes = isPrivateKey ? DecodePrivateKey(pemStr) : DecodePublicKey(pemStr); | |
if (keyBytes == null) | |
return null; | |
return ConvertToRSA(keyBytes); | |
} | |
static byte[] DecodePublicKey(string publicKey) { | |
publicKey = publicKey.Trim(); | |
if (!publicKey.StartsWith(PublicHeader) || !publicKey.EndsWith(PublicFooter)) | |
return null; | |
publicKey = publicKey.Replace(PublicHeader, "").Replace(PublicFooter, "").Trim(); | |
byte[] keyBytes = null; | |
try { keyBytes = publicKey.FromBase64(); } catch { } | |
return keyBytes; | |
} | |
static byte[] DecodePrivateKey(string privateKey) { | |
privateKey = privateKey.Trim(); | |
if (!privateKey.StartsWith(PrivateHeader) || !privateKey.EndsWith(PrivateFooter)) | |
return null; | |
privateKey = privateKey.Replace(PrivateHeader, "").Replace(PrivateFooter, "").Trim(); | |
byte[] keyBytes = null; | |
try { keyBytes = privateKey.FromBase64(); } catch { } | |
return keyBytes; | |
} | |
static RSA ConvertToRSA(byte[] key) { | |
RSA rsa = null; | |
using(var ms = new MemoryStream(key)) | |
using(var br = new BinaryReader(ms)) { | |
byte[] MODULUS, E, D, P, Q, DP, DQ, IQ; | |
byte bt = 0; | |
ushort twobytes = 0; | |
int elems = 0; | |
try { | |
twobytes = br.ReadUInt16(); | |
if (twobytes == 0x8130) br.ReadByte(); | |
else if (twobytes == 0x8230) br.ReadInt16(); | |
else throw new InvalidDataException(); | |
twobytes = br.ReadUInt16(); | |
if (twobytes != 0x0102) throw new InvalidDataException(); | |
bt = br.ReadByte(); | |
if (bt != 0x00) throw new InvalidDataException(); | |
elems = GetIntegerSize(br); | |
MODULUS = br.ReadBytes(elems); | |
elems = GetIntegerSize(br); | |
E = br.ReadBytes(elems); | |
elems = GetIntegerSize(br); | |
D = br.ReadBytes(elems); | |
elems = GetIntegerSize(br); | |
P = br.ReadBytes(elems); | |
elems = GetIntegerSize(br); | |
Q = br.ReadBytes(elems); | |
elems = GetIntegerSize(br); | |
DP = br.ReadBytes(elems); | |
elems = GetIntegerSize(br); | |
DQ = br.ReadBytes(elems); | |
elems = GetIntegerSize(br); | |
IQ = br.ReadBytes(elems); | |
rsa = RSA.Create(); | |
rsa.ImportParameters(new RSAParameters { | |
Modulus = MODULUS, | |
Exponent = E, | |
D = D, | |
P = P, | |
Q = Q, | |
DP = DP, | |
DQ = DQ, | |
InverseQ = IQ | |
}); | |
} catch { } | |
} | |
return rsa; | |
} | |
static int GetIntegerSize(BinaryReader br) { | |
byte bt = 0; | |
byte lowbyte = 0x00; | |
byte highbyte = 0x00; | |
int count = 0; | |
bt = br.ReadByte(); | |
if (bt != 0x02) return 0; | |
bt = br.ReadByte(); | |
if (bt == 0x81) count = br.ReadByte(); | |
else if (bt == 0x82) { | |
highbyte = br.ReadByte(); | |
lowbyte = br.ReadByte(); | |
byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; | |
count = BitConverter.ToInt32(modint, 0); | |
} | |
else count = bt; | |
while (br.ReadByte() == 0x00) { count -= 1; } | |
br.BaseStream.Seek(-1, SeekOrigin.Current); | |
return count; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment