現在、デコードされたPEMファイルのみからRSACryptoServiceProvider
オブジェクトを作成しようとしています。数日間の検索の後、私はなんとかして実用的な解決策を練り上げましたが、それは生産準備ができているものではありません。
簡単に言えば、PEMファイルの公開鍵を構成するバイトからRSACryptoServiceProvider
オブジェクトを作成するには、キーサイズを指定するオブジェクト(現在は特にSHA256を使用する2048)を作成してから、 RSAParameters
およびExponent
が設定されたModulus
オブジェクト。私はそのようにしています。
byte[] publicKeyBytes = Convert.FromBase64String(deserializedPublicKey.Replace("-----BEGIN PUBLIC KEY-----", "")
.Replace("-----END PUBLIC KEY-----", ""));
// extract the modulus and exponent based on the key data
byte[] exponentData = new byte[3];
byte[] modulusData = new byte[256];
Array.Copy(publicKeyBytes, publicKeyBytes.Length - exponentData.Length, exponentData, 0, exponentData.Length);
Array.Copy(publicKeyBytes, 9, modulusData, 0, modulusData.Length);
// import the public key data (base RSA - works)
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(dwKeySize: 2048);
RSAParameters rsaParam = rsa.ExportParameters(false);
rsaParam.Modulus = modulusData;
rsaParam.Exponent = exponentData;
rsa.ImportParameters(rsaParam);
これは機能しますが、deserializedPublicKey
が正確に270バイトであり、必要な係数が位置9にあり、長さが常に256バイトであると想定することは現実的ではありません。
これを変更して、公開鍵バイトのセットが指定されたモジュラスと指数バイトを正しく選択するにはどうすればよいですか?私はASN.1標準を理解しようとしましたが、私はそこから必要なものを見つけるのにほとんど運がありませんでした-標準は多少ビザンチンです。
どんな助けでもありがたいです。
既存のパラメーターをエクスポートしてからそれらの上に再インポートする必要はありません。これにより、マシンはRSAキーを生成して強制的に破棄します。したがって、コンストラクタにキーサイズを指定することは重要ではありません(キーを使用しない場合、キーは生成されません...通常)。
公開鍵ファイルは、DERエンコードされたblobです。
_-----BEGIN PUBLIC KEY-----
MIGgMA0GCSqGSIb3DQEBAQUAA4GOADCBigKBggC8rLGlNJ17NaWArDs5mOsV6/kA
7LMpvx91cXoAshmcihjXkbWSt+xSvVry2w07Y18FlXU9/3unyYctv34yJt70SgfK
Vo0QF5ksK0G/5ew1cIJM8fSxWRn+1RP9pWIEryA0otCP8EwsyknRaPoD+i+jL8zT
SEwV8KLlRnx2/HYLVQkCAwEAAQ==
-----END PUBLIC KEY-----
_
PEMのアーマー内のコンテンツを取得する場合、それはBase64でエンコードされたバイト配列です。
_30 81 A0 30 0D 06 09 2A 86 48 86 F7 0D 01 01 01
05 00 03 81 8E 00 30 81 8A 02 81 82 00 BC AC B1
A5 34 9D 7B 35 A5 80 AC 3B 39 98 EB 15 EB F9 00
EC B3 29 BF 1F 75 71 7A 00 B2 19 9C 8A 18 D7 91
B5 92 B7 EC 52 BD 5A F2 DB 0D 3B 63 5F 05 95 75
3D FF 7B A7 C9 87 2D BF 7E 32 26 DE F4 4A 07 CA
56 8D 10 17 99 2C 2B 41 BF E5 EC 35 70 82 4C F1
F4 B1 59 19 FE D5 13 FD A5 62 04 AF 20 34 A2 D0
8F F0 4C 2C CA 49 D1 68 FA 03 FA 2F A3 2F CC D3
48 4C 15 F0 A2 E5 46 7C 76 FC 76 0B 55 09 02 03
01 00 01
_
ITU-T X.69 は、Basic Encoding Rules(BER)、Canonical Encoding Rules(CER、私が明示的に使用したことはない)、Distinguished Encoding Rules(DER)でエンコードされたものを読み取る方法を定義します。ほとんどの場合、CERはBERを制限し、DERはCERを制限するため、DERが最も読みやすくなります。 ( ITU-T X.68 は、DERがバイナリエンコーディングである文法である抽象構文記法1(ASN.1)を説明します)
これで少し解析できます:
_30
_
これは、CONSTRUCTEDビットが設定されたシーケンス(0x10)(0x20)を識別します。これは、他のDER /タグ付き値が含まれていることを意味します。 (SEQUENCEは常にDERで構築されます)
_81 A0
_
この次の部分は長さです。上位ビットセット(> 0x7F)があるため、最初のバイトは「長さの長さ」の値です。これは、実際の長さが次の1バイトでエンコードされることを示します(_lengthLength & 0x7F
_)。したがって、このシーケンスの内容は合計160バイトです。 (この場合、「残りのデータ」ですが、SEQUENCEは他の何かの中に含まれている可能性があります)。内容を読みましょう:
_30 0D
_
長さの値が_0x30
_のCONSTRUCTED SEQUENCE(_0x0D
_)が再び表示されるので、13バイトのペイロードがあります。
_06 09 2A 86 48 86 F7 0D 01 01 01 05 00
_
_06
_はOBJECT IDENTIFIERで、_0x09
_バイトのペイロードがあります。 OIDは少し直感的でないエンコーディングを持っていますが、これは_1.2.840.113549.1.1.1
_であるテキスト表現_id-rsaEncryption
_と同等です( http:// www .oid-info.com/get/1.2.840.113549.1.1.1 )。
これでも、2バイト(_05 00
_)が残りますが、これはNULLです(ペイロードは0バイトです。まあ、それはNULLだからです)。
これまでのところ
_SEQUENCE
SEQUENCE
OID 1.2.840.113549.1.1.1
NULL
143 more bytes.
_
継続:
_03 81 8E 00
_
_03
_はBIT STRINGを意味します。 BIT STRINGは、[タグ] [長さ] [未使用ビット数]としてエンコードされます。未使用のビットは本質的に常にゼロです。つまり、これは_0x8E
_バイト長のビットのシーケンスであり、それらすべてが使用されます。
CONSTRUCTEDが設定されていなかったため、技術的にはここで停止する必要があります。しかし、たまたまこの構造の形式を知っているので、CONSTRUCTEDビットが設定されているかのように値を扱います。
_30 81 8A
_
これが私たちの友人のCONSTRUCTED SEQUENCEである_0x8A
_ペイロードバイトです。これは、「残っているすべてのもの」に便利に対応しています。
_02 81 82
_
_02
_はINTEGERを識別し、これには_0x82
_ペイロードバイトがあります。
_00 BC AC B1 A5 34 9D 7B 35 A5 80 AC 3B 39 98 EB
15 EB F9 00 EC B3 29 BF 1F 75 71 7A 00 B2 19 9C
8A 18 D7 91 B5 92 B7 EC 52 BD 5A F2 DB 0D 3B 63
5F 05 95 75 3D FF 7B A7 C9 87 2D BF 7E 32 26 DE
F4 4A 07 CA 56 8D 10 17 99 2C 2B 41 BF E5 EC 35
70 82 4C F1 F4 B1 59 19 FE D5 13 FD A5 62 04 AF
20 34 A2 D0 8F F0 4C 2C CA 49 D1 68 FA 03 FA 2F
A3 2F CC D3 48 4C 15 F0 A2 E5 46 7C 76 FC 76 0B
55 09
_
先頭の0x00は、次のバイトに上位ビットが設定されている場合を除いて、DERの違反になります。これは、符号ビットが設定されないようにするために0x00があり、これを正の数にしたことを意味します。
_02 03 01 00 01
_
別のINTEGER、3バイト、値_01 00 01
_。これで完了です。
_SEQUENCE
SEQUENCE
OID 1.2.840.113549.1.1.1
NULL
BIT STRING
SEQUENCE
INTEGER 00 BC AC ... 0B 55 09
INTEGER 01 00 01
_
収穫 https://tools.ietf.org/html/rfc528 これはSubjectPublicKeyInfo
構造によく似ていることがわかります。
_SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier,
subjectPublicKey BIT STRING }
AlgorithmIdentifier ::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL }
-- contains a value of the type
-- registered for use with the
-- algorithm object identifier value
_
もちろん、RSA公開鍵の形式が何であるかはわかりません。しかし、oid-infoサイトはチェックアウトするように私たちに言いました RFC 231 、私たちが見るところ
_An RSA public key shall have ASN.1 type RSAPublicKey:
RSAPublicKey ::= SEQUENCE {
modulus INTEGER, -- n
publicExponent INTEGER -- e }
_
つまり、読み取る最初のINTEGERはModulus値であり、2番目は(public)Exponentです。
DERエンコーディングはビッグエンディアンであり、これはRSAParametersエンコーディングでもありますが、RSAParametersの場合、先頭の_0x00
_値をModulusから削除する必要があります。
それを行うためのコードを提供するほど簡単ではありませんが、この情報が与えられれば、RSAキーのパーサーを書くのはかなり簡単です。 internal static RSAParameters ReadRsaPublicKey(...)
と書くことをお勧めします。
_RSAParameters rsaParameters = ReadRsaPublicKey(...);
using (RSA rsa = RSA.Create())
{
rsa.ImportParameters(rsaParameters);
// things you want to do with the key go here
}
_
長い時間をかけて、検索と bartonjs の優れた応答を行うと、公開鍵の構造に慣れていない人には少し直感的ではありませんが、これを行うコードは実際には単純です。
公開鍵PEMは、new RSACryptoServiceProvider(pemBytes)
のようなものではなく、RSAだけでなく、さまざまな鍵タイプを記述することができます。構造/構文、ASN.1に基づいてPEMを解析する必要があります。それがRSAキーである場合(他の範囲の場合もあります)。知っています;
const string rsaOid = "1.2.840.113549.1.1.1"; // found under System.Security.Cryptography.CngLightup.RsaOid but it's marked as private
Oid oid = new Oid(rsaOid);
AsnEncodedData keyValue = new AsnEncodedData(publicKeyBytes); // see question
AsnEncodedData keyParam = new AsnEncodedData(new byte[] { 05, 00 }); // ASN.1 code for NULL
PublicKey pubKeyRdr = new PublicKey(oid, keyParam, keyValue);
var rsaCryptoServiceProvider = (RSACryptoServiceProvider)pubKeyRdr.Key;
注:上記のコードはnot生産準備完了です!オブジェクトの作成(公開鍵はRSAではない場合があります)、RSACryptoServiceProvider
へのキャストなどに適切なガードを配置する必要があります。ここのコードサンプルは、合理的にクリーンに実行できることを示すために短くなっています。
どうやってこれを手に入れましたか? ILSpyの暗号化名前空間を調べてみると、AsnEncodedData
が bartonjs の説明でベルを鳴らしていることに気付きました。さらに調査を行ったところ、 this の投稿に遭遇しました(見覚えがありますか?)。これはキーのサイズを特定することを試みていましたが、途中で必要なRSACryptoServiceProvider
が作成されます。
bartonjs の回答をAcceptedのままにします。上記のコードはその研究の結果であり、私がここに残しているのは、同じことをしようとしている他の人が、私のOPのように配列をコピーするハックなしに、きれいに実行できるようにするためです。
また、デコードとテストの目的で、ASN.1デコーダー here を使用して、公開鍵が解析可能かどうかを確認できます。
[〜#〜]更新[〜#〜]
これを作成するのは.NETロードマップにあります easier with ASN.1 parsing for Core> 2.1.0。
更新2
現在、Core .NET 2.1.1にはプライベート実装があります。 MSは、すべてが満足するまでドッグフーディングしており、(できれば)後続のバージョンでパブリックAPIを見る予定です。
PEMファイルは、base64でエンコードされたDERファイルの単なるシリーズであり、.netはDERファイルを直接インポートできるため、次のようなことを実行できます(使用するのは、公開鍵のみを使用していると想定しているためです)。
byte[] certBytes = Convert.FromBase64String(deserializedPublicKey
.Replace("-----BEGIN PUBLIC KEY-----", "")
.Replace("-----END PUBLIC KEY-----", ""));
X509Certificate2 cert = new X509Certificate2(certBytes);
RSACryptoServiceProvider publicKeyProvider =
(RSACryptoServiceProvider)cert.PublicKey.Key;