Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Convert RSA keys from PEM to RSA Parameters
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Security.Cryptography;
using System.Text;
internal class RSA {
private const string _begin = "-----BEGIN ";
private const string _end = "-----END ";
private const string _private = "PRIVATE KEY";
private const string _public = "PUBLIC KEY";
private const string _rsaPublic = "RSA PUBLIC KEY";
/// <summary>Imports PEM formatted key or certificate into crypto provider</summary>
/// <param name="data">Content of PEM-formatted object.</param>
/// <returns>Crypto provider, defined by given argument.</returns>
public static RSACryptoServiceProvider FromPem(string data) {
return FromPem(data, null);
}
/// <summary>Imports PEM formatted key or certificate into crypto provider</summary>
/// <param name="data">Content of PEM-formatted object.</param>
/// <param name="passKey">Passkey for PEM-formatted object.</param>
/// <returns>Crypto provider, defined by given argument.</returns>
public static RSACryptoServiceProvider FromPem(string data, string passKey) {
using (var reader = new StringReader(data)) {
var line = reader.ReadLine();
if (line.NotNull() && line.StartsWith(_begin)) {
line = line.Substring(_begin.Length);
var idx = line.IndexOf('-');
if (idx > 0) {
var type = line.Before(idx);
return _loadPem(reader, type, passKey);
}
}
throw new ArgumentException("This is not valid PEM format", "data", new FormatException("PEM start identifier is invalid or not found."));
}
}
private static RSACryptoServiceProvider _loadPem(StringReader reader, string type, string passkey) {
var end = _end + type;
var headers = new _pemHeaders();
var line = string.Empty;
var body = new StringBuilder();
while ((line = reader.ReadLine()) != null && line.IndexOf(end) == -1) {
if (line == null) {
throw new FormatException("PEM end identifier is invalid or not found.");
}
var d = line.IndexOf(':');
if (d >= 0) {
// header
var n = line.Substring(0, d).Trim();
if (n.StartsWith("X-")) n = n.Substring(2);
var v = line.After(d).Trim();
if (!headers.ContainsKey(n)) {
headers.Add(n, v);
} else {
throw new FormatException("Duplicate header {0} in PEM data.".Substitute(n));
}
} else {
// body
body.Append(line);
}
}
if (body.Length % 4 != 0) {
throw new FormatException("PEM data is invalid or truncated.");
}
return _createPem(type, headers, Convert.FromBase64String(body.ToString()), passkey);
}
private static RSACryptoServiceProvider _createPem(string type, _pemHeaders headers, byte[] body, string passkey) {
if (type.EndsWith(_private)) {
return _fromPrivateKey(type, headers, body, passkey);
} else {
throw new NotSupportedException("Import of {0} is not supported. Only RSA private key import is supported.".Substitute(type));
}
}
private static RSACryptoServiceProvider _fromPrivateKey(string type, _pemHeaders headers, byte[] body, string passkey) {
type = type.Before(type.Length - _private.Length).Trim();
var pType = headers.TryGet("Proc-Type");
if (pType == "4,ENCRYPTED") {
if (passkey.IsEmpty()) {
throw new ArgumentException("Passkey is mandatory for encrypted PEM object");
}
var dek = headers.TryGet("DEK-Info");
var tkz = dek.Split(',');
if (tkz.Length > 1) {
var alg = new _alg(tkz[0]);
var saltLen = tkz[1].Length;
var salt = new byte[saltLen / 2];
for (var i = 0; i < saltLen / 2; i++) {
var pair = tkz[1].Substring(2 * i, 2);
salt[i] = Byte.Parse(pair, NumberStyles.AllowHexSpecifier);
}
body = _decodePem(body, passkey, alg, salt);
if (body != null) {
return _decodeRsaPrivateKey(body);
}
} else {
throw new FormatException("DEK information is invalid or truncated.");
}
}
return null;
}
private static RSACryptoServiceProvider _decodeRsaPrivateKey(byte[] body) {
using (var ms = new MemoryStream(body)) {
using (var reader = new BinaryReader(ms)) {
try {
var tb = reader.ReadUInt16(); // LE: x30 x81
if (tb == 0x8130) {
reader.ReadByte(); // fw 1
} else if (tb == 0x8230) {
reader.ReadInt16(); // fw 2
} else {
return null;
}
tb = reader.ReadUInt16(); // version
if (tb != 0x0102) {
return null;
}
if (reader.ReadByte() != 0x00) {
return null;
}
var MODULUS = _readInt(reader);
var E = _readInt(reader);
var D = _readInt(reader);
var P = _readInt(reader);
var Q = _readInt(reader);
var DP = _readInt(reader);
var DQ = _readInt(reader);
var IQ = _readInt(reader);
var result = new RSACryptoServiceProvider();
var param = new RSAParameters {
Modulus = MODULUS,
Exponent = E,
D = D,
P = P,
Q = Q,
DP = DP,
DQ = DQ,
InverseQ = IQ
};
result.ImportParameters(param);
return result;
} catch (Exception ex) {
Log.Exception(ex);
} finally {
reader.Close();
}
}
}
return null;
}
private static Func<BinaryReader, byte[]> _readInt = r => {
var s = _getIntSize(r);
return r.ReadBytes(s);
};
private static Func<BinaryReader, int> _getIntSize = r => {
byte lb = 0x00;
byte hb = 0x00;
int c = 0;
var b = r.ReadByte();
if (b != 0x02) { //int
return 0;
}
b = r.ReadByte();
if (b == 0x81) {
c = r.ReadByte(); //size
} else
if (b == 0x82) {
hb = r.ReadByte(); //size
lb = r.ReadByte();
byte[] m = { lb, hb, 0x00, 0x00 };
c = BitConverter.ToInt32(m, 0);
} else {
c = b; //got size
}
while (r.ReadByte() == 0x00) { //remove high zero
c -= 1;
}
r.BaseStream.Seek(-1, SeekOrigin.Current); // last byte is not zero, go back;
return c;
};
private static byte[] _decodePem(byte[] body, string passkey, _alg alg, byte[] salt) {
if (alg.AlgBase != _alg.BaseAlg.DES_EDE3 && alg.AlgMode != _alg.Mode.CBC) {
throw new NotSupportedException("Only 3DES-CBC keys are supported.");
}
var des = _get3DesKey(salt, passkey);
if (des == null) {
throw new ApplicationException("Unable to calculate 3DES key for decryption.");
}
var rsa = _decryptRsaKey(body, des, salt);
if (rsa == null) {
throw new ApplicationException("Unable to decrypt RSA private key.");
}
return rsa;
}
private static byte[] _decryptRsaKey(byte[] body, byte[] desKey, byte[] iv) {
byte[] result = null;
using (var stream = new MemoryStream()) {
var alg = TripleDES.Create();
alg.Key = desKey;
alg.IV = iv;
try {
using (var cs = new CryptoStream(stream, alg.CreateDecryptor(), CryptoStreamMode.Write)) {
cs.Write(body, 0, body.Length);
cs.Close();
}
result = stream.ToArray();
} catch (CryptographicException ce) {
// throw up
throw ce;
} catch (Exception ex) {
Log.Exception(ex, "Failed to write crypto stream.");
};
}
return result;
}
private static byte[] _get3DesKey(byte[] salt, string passkey) {
var HASHLENGTH = 16;
var m = 2; // 2 iterations for at least 24 bytes
var c = 1; // 1 hash for Open SSL
var k = new byte[HASHLENGTH * m];
var pk = Encoding.ASCII.GetBytes(passkey);
var data = new byte[salt.Length + pk.Length];
Array.Copy(pk, data, pk.Length);
Array.Copy(salt, 0, data, pk.Length, salt.Length);
var md5 = new MD5CryptoServiceProvider();
byte[] result = null;
var hash = new byte[HASHLENGTH + data.Length];
for (int i = 0; i < m; i++) {
if (i == 0) {
result = data;
} else {
Array.Copy(result, hash, result.Length);
Array.Copy(data, 0, hash, result.Length, data.Length);
result = hash;
}
for (int j = 0; j < c; j++) {
result = md5.ComputeHash(result);
}
Array.Copy(result, 0, k, i * HASHLENGTH, result.Length);
}
var dk = new byte[24]; //final key
Array.Copy(k, dk, dk.Length);
return dk;
}
private class _pemHeaders : Dictionary<string, string> { }
private sealed class _alg {
public _alg(string alg) {
try {
_mode = Mode.ECB;
switch (alg.Trim()) {
//TK: DES-EDE based algorithms come only with ECB mode.
case "DES-EDE": _baseAlg = BaseAlg.DES_EDE; return;
case "DES-EDE3": _baseAlg = BaseAlg.DES_EDE3; return;
default:
var p = alg.LastIndexOf('-');
if (p >= 0) {
_baseAlg = (BaseAlg)_parseVal(typeof(BaseAlg), alg.Before(p));
_mode = (Mode)_parseVal(typeof(Mode), alg.After(p));
return;
}
break;
}
} catch (ArgumentException ae) { Log.Exception(ae); }
throw new ArgumentException("Unknown DEK algorithm '{0}'", alg);
}
public BaseAlg AlgBase { get { return _baseAlg; } }
private BaseAlg _baseAlg;
public Mode AlgMode { get { return _mode; } }
private Mode _mode;
private Func<Type, string, Enum> _parseVal = (t, s) => {
s = s.Replace('-', '_');
return (Enum)Enum.Parse(t, s);
};
public enum BaseAlg { AES_128, AES_192, AES_256, BF, DES, DES_EDE, DES_EDE3, RC2, RC2_40, RC2_64 };
public enum Mode { CBC, CFB, ECB, OFB };
}
}
internal static class helper {
public static bool NotNull<T>(this T value) where T : class {
return !Object.Equals(value, null);
}
public static bool IsEmpty(this string value) {
return String.IsNullOrEmpty(value);
}
public static string Before(this string value, int end) {
return (end == 0 ? String.Empty : value.Between(0, end - 1));
}
public static string After(this string value, int start) {
return value.Between(start + 1, Int32.MaxValue);
}
public static string Between(this string value, int start, int end) {
int len = (String.IsNullOrEmpty(value) ? 0 : value.Length);
if (start < 0) start += len;
if (end < 0) end += len;
if (len == 0 || start > len - 1 || end < start) {
return String.Empty;
} else {
if (start < 0) start = 0;
if (end >= len) end = len - 1;
return value.Substring(start, end - start + 1);
}
}
public static string Substitute(this string format, params object[] args) {
string value = String.Empty;
if (String.IsNullOrEmpty(format)) return value;
if (args.Length == 0) return format;
try {
return String.Format(format, args);
} catch (FormatException) {
return format;
} catch {
return "***";
}
}
public static V TryGet<K, V>(this Dictionary<K, V> dictionary, K key) {
return dictionary.TryGet(key, default(V));
}
public static V TryGet<K, V>(this Dictionary<K, V> dictionary, K key, V defaultValue) {
if (dictionary != null && dictionary.ContainsKey(key)) {
return dictionary[key];
} else {
return defaultValue;
}
}
}
internal static class Log {
public static void Exception(Exception exception) {
Trace.WriteLine(exception);
}
public static void Exception(Exception exception, string format, params object[] args) {
Trace.WriteLine("{0}{1}".Substitute(exception), format.Substitute(args));
}
}
@brentmaxwell

This comment has been minimized.

Copy link
Owner Author

commented Nov 19, 2015

I found this somewhere else and had to grab it from archive.org. I make no guarantees to it's design or functionality

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.