Skip to content

Instantly share code, notes, and snippets.

@acid-chicken
Created October 19, 2018 08:31
Show Gist options
  • Save acid-chicken/e4b01c1b21a15f8e20e8499e017ad211 to your computer and use it in GitHub Desktop.
Save acid-chicken/e4b01c1b21a15f8e20e8499e017ad211 to your computer and use it in GitHub Desktop.
using System;
using System.Security.Cryptography;
public static class PEM
{
const string
_header = "-----BEGIN RSA PRIVATE KEY-----",
_footer = "-----END RSA PRIVATE KEY-----";
public static RSAParameters ParseToRSAParameters(ReadOnlySpan<char> s)
{
var ss = s.IndexOf(_header, StringComparison.Ordinal) + _header.Length;
var sl = s.IndexOf(_footer, StringComparison.Ordinal) - ss;
Span<byte> key = stackalloc byte[sl << 1];
key = Convert.TryFromBase64Chars(s.Slice(ss, sl), key, out var written) ?
key.Slice(0, written) :
throw new FormatException($"{nameof(s)} is not a valid BASE64 string.");
var cursor = 0;
byte[] ReadParameter(ReadOnlySpan<byte> source)
{
var id = source[cursor++];
var tag = id & 0b_0001_1111;
var lfr = source[cursor++];
var lfs = lfr & 0b_0111_1111;
var lem = lfr == lfs;
Span<byte> lsb = stackalloc byte[4];
if (lem)
lsb[0] = lfr;
else
for (var i = --lfs; i >= 0; i--)
lsb[i] = source[cursor++];
var length = BitConverter.ToInt32(lsb);
switch (tag)
{
case 0b_0000_0010:
case 0b_0000_0100:
break;
case 0b_0000_0011:
var first = source[cursor++];
if (first != 0b_0000_0000)
throw new NotSupportedException("The feature of specifing unused bits is not supported.");
length--;
break;
case 0b_0000_0101:
case 0b_0001_0000:
return Array.Empty<byte>();
default:
throw new NotSupportedException($"Tag #{tag} is not supported.");
}
var content = source.Slice(cursor, length);
cursor += length;
return content.ToArray();
}
ReadParameter(key);
var version =
ReadParameter(key);
if (version.Length != 1 || version[0] != 0)
throw new NotSupportedException($"Version {version} is not supported.");
return new RSAParameters
{
Modulus = ReadParameter(key),
Exponent = ReadParameter(key),
D = ReadParameter(key),
P = ReadParameter(key),
Q = ReadParameter(key),
DP = ReadParameter(key),
DQ = ReadParameter(key),
InverseQ = ReadParameter(key)
};
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
public class PEMTest
{
[Fact]
public void ParseToRSAParametersTest()
{
var separators = new [] { ':', '\n' };
IEnumerable<string> L(byte[] source) =>
source.Select(x => x.ToString("x2"));
IEnumerable<string> R(string source) =>
source.Split(separators, StringSplitOptions.RemoveEmptyEntries);
var key = @"
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAvy2lvWbzv/MGBkIaCQ6bwpzzyP92xYT7bzjodk9OhfCspKiM
hF4HHOwMQvyIQ94h+Si03mHtePnsKDbtxwegxWkZ9sed+OEXiwNb0SywXoiGsys5
Nsqv/VudjumOVjpVd65NcrHLq30XIuHy33jEZn7qn+Zo6XAFx1c7Pxr6OHK1D+38
Qb7tnvIEXYeeeGACnyyiLeGdGXIoXAKgWMDdSJufHRjuBktqEtW5iWd3tChHJl0D
ejh4UEjKIk/SVC6mOaYmJE4OJaBKbyBwsuiyHs5CcZfOM7xh1e5i/amMamFqpzbr
XR15FnyDy7WAz/6F9XdClOzj+Irn9htk3qi8CwIDAQABAoIBAFQUaFs3ZyZZZKHl
+ntXQGvECXex2vOdu9M7rQkzce54XgWA12Pz0p8GtZHUbL2keT6Sh5FycjWNfS5m
kgbBtRR9V9zwB+sIXAlYbc4+IEdDNjKgZOZTGDmOTGopD9+egi5Dq24xAcknF8DQ
rLdZ7s7BLMEsXaGlEfWMyNLFM50VGipvuTIwCIkmNxagW4RMAThMfTYuDXuambdS
ChZoFubUnn3aUSna0E2QusCDr+Q2REuNF+DHnLkBCjdjhyUycYa430f7Vad54lVN
+rOUXtnFq43STbwOdfIZAD2KQL8eK22ej3kDtZwev8O00rECLIcvn1rJwaEuww6E
eMDvZDECgYEA9Tf8p/fNU7JOPiLHaX0hEy+7JGJfQ6lEdLFDHZxkU0h5q75pMztZ
vLZ5RjBf1gMCrAFtuDoMMwXBoBRono8nhD+O8Qi/ADNcn+ZHxlXhI2RNuwGesqZW
+khMfIdX6RFMtwDrpTGQOFDKRqmFuzafwfqAIgV+EM/pVMS+w46VU1MCgYEAx5Vr
RUwSxvih46gCvT15BsqwR/lmKVruuh/vydt0f9luFSJfMsH0tFjutpJpQ/1sH3wS
Ej1TjyxeFYHEt+XOKtboBLiOjSu+Few0R/mLPDcivAEe4sbVPDSDUTUqorSHUQTk
5tYyMz8tonY/Ye3YnDIREOy3qSX7HDf7o3VlVWkCgYBwExudpUspwqeyDHE5jGAO
hdUxhuhlYzqPXuj+4piT298IGKm6KZkVAA0jgD588LlK5ghAl/81Xp8lS86ZEXKN
JgNttIKfU9o0lqodQuj4JQLFwrLGkfHUyDB1BeKu+iImzfvlb2ar5njcnOQrMYcI
wDXJ1trMUkohXR6XAFbNUwKBgBpvz2LBfec/Pepy8dHxV5uvs4QFJCQsOF0NJ+0c
FaVtvqgsAmIt0OUmtpAWer0Xz3+oJpil6PCZFulQZCdb2GBSUS925uMKPUaYICC8
jFXwk7hFibrOTaaI6jASk9Azi40O0edFziZ9ouTXNvQY1k1yUFJmmLleH5IQVFPF
lCOpAoGAXGvdcTvDzB1n9fq6bN6vfzUTezSZNClFhd//18IG32qSwnf2Z6CTdjLG
eqRxURb5LSR7Q0T4PYBLR0zEEadysFvEoJqH4nQJohmpQIeEadf7zwl5TIDhPrbo
qgrHJTtB832Hl0mhIAQNVIed2+QTH/SJReyRTW7/J5Lht9tjQlo=
-----END RSA PRIVATE KEY-----".AsSpan(1);
var rsa = PEM.ParseToRSAParameters(key);
Assert.Equal(L(rsa.Modulus), R(@"
00:bf:2d:a5:bd:66:f3:bf:f3:06:06:42:1a:09:0e:
9b:c2:9c:f3:c8:ff:76:c5:84:fb:6f:38:e8:76:4f:
4e:85:f0:ac:a4:a8:8c:84:5e:07:1c:ec:0c:42:fc:
88:43:de:21:f9:28:b4:de:61:ed:78:f9:ec:28:36:
ed:c7:07:a0:c5:69:19:f6:c7:9d:f8:e1:17:8b:03:
5b:d1:2c:b0:5e:88:86:b3:2b:39:36:ca:af:fd:5b:
9d:8e:e9:8e:56:3a:55:77:ae:4d:72:b1:cb:ab:7d:
17:22:e1:f2:df:78:c4:66:7e:ea:9f:e6:68:e9:70:
05:c7:57:3b:3f:1a:fa:38:72:b5:0f:ed:fc:41:be:
ed:9e:f2:04:5d:87:9e:78:60:02:9f:2c:a2:2d:e1:
9d:19:72:28:5c:02:a0:58:c0:dd:48:9b:9f:1d:18:
ee:06:4b:6a:12:d5:b9:89:67:77:b4:28:47:26:5d:
03:7a:38:78:50:48:ca:22:4f:d2:54:2e:a6:39:a6:
26:24:4e:0e:25:a0:4a:6f:20:70:b2:e8:b2:1e:ce:
42:71:97:ce:33:bc:61:d5:ee:62:fd:a9:8c:6a:61:
6a:a7:36:eb:5d:1d:79:16:7c:83:cb:b5:80:cf:fe:
85:f5:77:42:94:ec:e3:f8:8a:e7:f6:1b:64:de:a8:
bc:0b"));
Assert.Equal(L(rsa.Exponent), R(@"
01:00:01"));
Assert.Equal(L(rsa.D), R(@"
54:14:68:5b:37:67:26:59:64:a1:e5:fa:7b:57:40:
6b:c4:09:77:b1:da:f3:9d:bb:d3:3b:ad:09:33:71:
ee:78:5e:05:80:d7:63:f3:d2:9f:06:b5:91:d4:6c:
bd:a4:79:3e:92:87:91:72:72:35:8d:7d:2e:66:92:
06:c1:b5:14:7d:57:dc:f0:07:eb:08:5c:09:58:6d:
ce:3e:20:47:43:36:32:a0:64:e6:53:18:39:8e:4c:
6a:29:0f:df:9e:82:2e:43:ab:6e:31:01:c9:27:17:
c0:d0:ac:b7:59:ee:ce:c1:2c:c1:2c:5d:a1:a5:11:
f5:8c:c8:d2:c5:33:9d:15:1a:2a:6f:b9:32:30:08:
89:26:37:16:a0:5b:84:4c:01:38:4c:7d:36:2e:0d:
7b:9a:99:b7:52:0a:16:68:16:e6:d4:9e:7d:da:51:
29:da:d0:4d:90:ba:c0:83:af:e4:36:44:4b:8d:17:
e0:c7:9c:b9:01:0a:37:63:87:25:32:71:86:b8:df:
47:fb:55:a7:79:e2:55:4d:fa:b3:94:5e:d9:c5:ab:
8d:d2:4d:bc:0e:75:f2:19:00:3d:8a:40:bf:1e:2b:
6d:9e:8f:79:03:b5:9c:1e:bf:c3:b4:d2:b1:02:2c:
87:2f:9f:5a:c9:c1:a1:2e:c3:0e:84:78:c0:ef:64:
31"));
Assert.Equal(L(rsa.P), R(@"
00:f5:37:fc:a7:f7:cd:53:b2:4e:3e:22:c7:69:7d:
21:13:2f:bb:24:62:5f:43:a9:44:74:b1:43:1d:9c:
64:53:48:79:ab:be:69:33:3b:59:bc:b6:79:46:30:
5f:d6:03:02:ac:01:6d:b8:3a:0c:33:05:c1:a0:14:
68:9e:8f:27:84:3f:8e:f1:08:bf:00:33:5c:9f:e6:
47:c6:55:e1:23:64:4d:bb:01:9e:b2:a6:56:fa:48:
4c:7c:87:57:e9:11:4c:b7:00:eb:a5:31:90:38:50:
ca:46:a9:85:bb:36:9f:c1:fa:80:22:05:7e:10:cf:
e9:54:c4:be:c3:8e:95:53:53"));
Assert.Equal(L(rsa.Q), R(@"
00:c7:95:6b:45:4c:12:c6:f8:a1:e3:a8:02:bd:3d:
79:06:ca:b0:47:f9:66:29:5a:ee:ba:1f:ef:c9:db:
74:7f:d9:6e:15:22:5f:32:c1:f4:b4:58:ee:b6:92:
69:43:fd:6c:1f:7c:12:12:3d:53:8f:2c:5e:15:81:
c4:b7:e5:ce:2a:d6:e8:04:b8:8e:8d:2b:be:15:ec:
34:47:f9:8b:3c:37:22:bc:01:1e:e2:c6:d5:3c:34:
83:51:35:2a:a2:b4:87:51:04:e4:e6:d6:32:33:3f:
2d:a2:76:3f:61:ed:d8:9c:32:11:10:ec:b7:a9:25:
fb:1c:37:fb:a3:75:65:55:69"));
Assert.Equal(L(rsa.DP), R(@"
70:13:1b:9d:a5:4b:29:c2:a7:b2:0c:71:39:8c:60:
0e:85:d5:31:86:e8:65:63:3a:8f:5e:e8:fe:e2:98:
93:db:df:08:18:a9:ba:29:99:15:00:0d:23:80:3e:
7c:f0:b9:4a:e6:08:40:97:ff:35:5e:9f:25:4b:ce:
99:11:72:8d:26:03:6d:b4:82:9f:53:da:34:96:aa:
1d:42:e8:f8:25:02:c5:c2:b2:c6:91:f1:d4:c8:30:
75:05:e2:ae:fa:22:26:cd:fb:e5:6f:66:ab:e6:78:
dc:9c:e4:2b:31:87:08:c0:35:c9:d6:da:cc:52:4a:
21:5d:1e:97:00:56:cd:53"));
Assert.Equal(L(rsa.DQ), R(@"
1a:6f:cf:62:c1:7d:e7:3f:3d:ea:72:f1:d1:f1:57:
9b:af:b3:84:05:24:24:2c:38:5d:0d:27:ed:1c:15:
a5:6d:be:a8:2c:02:62:2d:d0:e5:26:b6:90:16:7a:
bd:17:cf:7f:a8:26:98:a5:e8:f0:99:16:e9:50:64:
27:5b:d8:60:52:51:2f:76:e6:e3:0a:3d:46:98:20:
20:bc:8c:55:f0:93:b8:45:89:ba:ce:4d:a6:88:ea:
30:12:93:d0:33:8b:8d:0e:d1:e7:45:ce:26:7d:a2:
e4:d7:36:f4:18:d6:4d:72:50:52:66:98:b9:5e:1f:
92:10:54:53:c5:94:23:a9"));
Assert.Equal(L(rsa.InverseQ), R(@"
5c:6b:dd:71:3b:c3:cc:1d:67:f5:fa:ba:6c:de:af:
7f:35:13:7b:34:99:34:29:45:85:df:ff:d7:c2:06:
df:6a:92:c2:77:f6:67:a0:93:76:32:c6:7a:a4:71:
51:16:f9:2d:24:7b:43:44:f8:3d:80:4b:47:4c:c4:
11:a7:72:b0:5b:c4:a0:9a:87:e2:74:09:a2:19:a9:
40:87:84:69:d7:fb:cf:09:79:4c:80:e1:3e:b6:e8:
aa:0a:c7:25:3b:41:f3:7d:87:97:49:a1:20:04:0d:
54:87:9d:db:e4:13:1f:f4:89:45:ec:91:4d:6e:ff:
27:92:e1:b7:db:63:42:5a"));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment