Skip to content

Instantly share code, notes, and snippets.

@brentmaxwell
Created November 19, 2015 12:29
Show Gist options
  • Save brentmaxwell/46081df6fcbc0ca33fe1 to your computer and use it in GitHub Desktop.
Save brentmaxwell/46081df6fcbc0ca33fe1 to your computer and use it in GitHub Desktop.
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
Copy link
Author

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

@AfonsoMenata
Copy link

Boa noite pessoal. Eu copiei o código e não apresentor nenumerro Mas agora, como eu uso as funções para carregar o ficheiro PEM??

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment