Created
November 19, 2015 12:29
-
-
Save brentmaxwell/46081df6fcbc0ca33fe1 to your computer and use it in GitHub Desktop.
Convert RSA keys from PEM to RSA Parameters
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.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)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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??