LoginSignup
18
9

More than 5 years have passed since last update.

OpenSSLで作成したRSA暗号鍵をC#で読み込む方法

Last updated at Posted at 2016-07-06

OpenSSLで作成したRSA暗号鍵(.pem)を、C#で読み込むには工夫が必要です。
そのままの内容で読み込むとエラーとなります。
原因はわかったのですがスマートな解決策が見つかっていません。
内容を見て、もし助言いただければ、もれなく私の上司がアイスをプレゼントします。

以下、調査結果です。

1. まず秘密鍵を作成

openssl genrsa > private-key.pem
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC7Np2qTFhnZD0meg7OqzPMLzQjZLhCjLpmVoeTPAaeTpvQij0r
EG9FmS+Gbj4oGBiV8BaOraWlRTmPh2DnJJv/WmsAmd+WRsbCowGfpmukG7X0bEp7
XIRvbwzTHV34X3JyWdq9eGyr6FjidyiMPnA+sBRuvJhvsf4kmadrHIGXuQIDAQAB
AoGACJHfdcQ458K26eP8eYlsvZQLlvMtXB6FCdo8Kok85FJE3670Tdxau5zfiA/T
2JIBzhtHbRCV3JLmp6NS1EHgwiOVgohrzLj2QME+pN05Kxs+d8snfyl+izmuluV/
4qfOEcq1q3BiKI5jjrF78vVJzkRJNN/xLDxfk+duXYxnHeECQQDoQCcVGRKThyDp
cD/flayKf0ejtfKFZieTkOcKmU5zNdACDu7QgvcgwA2emu/cH/yFIExzPcg6vAXc
cXAg6CLTAkEAzlt6zfaLfGGeWrp+2W+J9svrc060ckeFRzDHiwWA/T3c/F1fjCDs
B2qouZuRWxSQJ5XTro4uKRvd+GjBZxALwwJAC5+qVRP8KgYJT6WejMIg8I2OOFNS
3/pM4MXPymgB1SJMVs4IhccIN0tlYxq+BnZTXRZQvTPKYaYnZPEIe50ioQJBALe0
mrvWu1BsexPcKOcbM+Ago6gQLfX6k7zPwSSTLsTDmQKVgWT3PghQPIqX4qwJv1du
O3ePHar9IvttDpfYog8CQAMy9RiFqJ3K/St4iVZizsoeG/GAa+o/kKiTjK4+wnox
UdkJEZh/7lkKbw9h2CVURIjUitmKDrfjkeJ7GsdKvAM=
-----END RSA PRIVATE KEY-----

中を覗くと以下のようになっています。

Private-Key: (1024 bit)
modulus:
    00:bb:36:9d:aa:4c:58:67:64:3d:26:7a:0e:ce:ab:
    33:cc:2f:34:23:64:b8:42:8c:ba:66:56:87:93:3c:
    06:9e:4e:9b:d0:8a:3d:2b:10:6f:45:99:2f:86:6e:
    3e:28:18:18:95:f0:16:8e:ad:a5:a5:45:39:8f:87:
    60:e7:24:9b:ff:5a:6b:00:99:df:96:46:c6:c2:a3:
    01:9f:a6:6b:a4:1b:b5:f4:6c:4a:7b:5c:84:6f:6f:
    0c:d3:1d:5d:f8:5f:72:72:59:da:bd:78:6c:ab:e8:
    58:e2:77:28:8c:3e:70:3e:b0:14:6e:bc:98:6f:b1:
    fe:24:99:a7:6b:1c:81:97:b9
publicExponent: 65537 (0x10001)
privateExponent:
    08:91:df:75:c4:38:e7:c2:b6:e9:e3:fc:79:89:6c:
    bd:94:0b:96:f3:2d:5c:1e:85:09:da:3c:2a:89:3c:
    e4:52:44:df:ae:f4:4d:dc:5a:bb:9c:df:88:0f:d3:
    d8:92:01:ce:1b:47:6d:10:95:dc:92:e6:a7:a3:52:
    d4:41:e0:c2:23:95:82:88:6b:cc:b8:f6:40:c1:3e:
    a4:dd:39:2b:1b:3e:77:cb:27:7f:29:7e:8b:39:ae:
    96:e5:7f:e2:a7:ce:11:ca:b5:ab:70:62:28:8e:63:
    8e:b1:7b:f2:f5:49:ce:44:49:34:df:f1:2c:3c:5f:
    93:e7:6e:5d:8c:67:1d:e1
prime1:
    00:e8:40:27:15:19:12:93:87:20:e9:70:3f:df:95:
    ac:8a:7f:47:a3:b5:f2:85:66:27:93:90:e7:0a:99:
    4e:73:35:d0:02:0e:ee:d0:82:f7:20:c0:0d:9e:9a:
    ef:dc:1f:fc:85:20:4c:73:3d:c8:3a:bc:05:dc:71:
    70:20:e8:22:d3
prime2:
    00:ce:5b:7a:cd:f6:8b:7c:61:9e:5a:ba:7e:d9:6f:
    89:f6:cb:eb:73:4e:b4:72:47:85:47:30:c7:8b:05:
    80:fd:3d:dc:fc:5d:5f:8c:20:ec:07:6a:a8:b9:9b:
    91:5b:14:90:27:95:d3:ae:8e:2e:29:1b:dd:f8:68:
    c1:67:10:0b:c3
exponent1:
    0b:9f:aa:55:13:fc:2a:06:09:4f:a5:9e:8c:c2:20:
    f0:8d:8e:38:53:52:df:fa:4c:e0:c5:cf:ca:68:01:
    d5:22:4c:56:ce:08:85:c7:08:37:4b:65:63:1a:be:
    06:76:53:5d:16:50:bd:33:ca:61:a6:27:64:f1:08:
    7b:9d:22:a1
exponent2:
    00:b7:b4:9a:bb:d6:bb:50:6c:7b:13:dc:28:e7:1b:
    33:e0:20:a3:a8:10:2d:f5:fa:93:bc:cf:c1:24:93:
    2e:c4:c3:99:02:95:81:64:f7:3e:08:50:3c:8a:97:
    e2:ac:09:bf:57:6e:3b:77:8f:1d:aa:fd:22:fb:6d:
    0e:97:d8:a2:0f
coefficient:
    03:32:f5:18:85:a8:9d:ca:fd:2b:78:89:56:62:ce:
    ca:1e:1b:f1:80:6b:ea:3f:90:a8:93:8c:ae:3e:c2:
    7a:31:51:d9:09:11:98:7f:ee:59:0a:6f:0f:61:d8:
    25:54:44:88:d4:8a:d9:8a:0e:b7:e3:91:e2:7b:1a:
    c7:4a:bc:03

2. つづけて公開鍵を作成

openssl rsa -pubout < private-key.pem > public-key.pem
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7Np2qTFhnZD0meg7OqzPMLzQj
ZLhCjLpmVoeTPAaeTpvQij0rEG9FmS+Gbj4oGBiV8BaOraWlRTmPh2DnJJv/WmsA
md+WRsbCowGfpmukG7X0bEp7XIRvbwzTHV34X3JyWdq9eGyr6FjidyiMPnA+sBRu
vJhvsf4kmadrHIGXuQIDAQAB
-----END PUBLIC KEY-----

中身は以下の通り

Public-Key: (1024 bit)
Modulus:
    00:bb:36:9d:aa:4c:58:67:64:3d:26:7a:0e:ce:ab:
    33:cc:2f:34:23:64:b8:42:8c:ba:66:56:87:93:3c:
    06:9e:4e:9b:d0:8a:3d:2b:10:6f:45:99:2f:86:6e:
    3e:28:18:18:95:f0:16:8e:ad:a5:a5:45:39:8f:87:
    60:e7:24:9b:ff:5a:6b:00:99:df:96:46:c6:c2:a3:
    01:9f:a6:6b:a4:1b:b5:f4:6c:4a:7b:5c:84:6f:6f:
    0c:d3:1d:5d:f8:5f:72:72:59:da:bd:78:6c:ab:e8:
    58:e2:77:28:8c:3e:70:3e:b0:14:6e:bc:98:6f:b1:
    fe:24:99:a7:6b:1c:81:97:b9
Exponent: 65537 (0x10001)

3. 暗号化を行うコードを用意

※鍵の読み込みにはBouncyCastleを使っています。

error.cs
void Main()
{
    //暗号化
    var publicParameters = CreatePublicKeyParameters(PublicKey);
    var cipherText = Encrypt(publicParameters, "topsecret");

    //復号化
    var privateParameters = CreatePrivateKeyParameters(PrivateKey);
    var plainText = Decrypt(privateParameters, cipherText);
}

string Encrypt(RSAParameters parameters, string plainText)
{
    using (var rsa = new RSACryptoServiceProvider())
    {
        rsa.ImportParameters(parameters);
        var plainBytes = Encoding.UTF8.GetBytes(plainText);
        var cipherBytes = rsa.Encrypt(plainBytes, RSAEncryptionPadding.Pkcs1);

        return Convert.ToBase64String(cipherBytes);
    }
}

string Decrypt(RSAParameters parameters, string cipherText)
{
    using (var rsa = new RSACryptoServiceProvider())
    {
        rsa.ImportParameters(parameters);
        var cipherBytes = Convert.FromBase64String(cipherText);
        var plainBytes = rsa.Decrypt(cipherBytes, RSAEncryptionPadding.Pkcs1);

        return Encoding.UTF8.GetString(plainBytes);
    }
}

RSAParameters CreatePublicKeyParameters(string publicKey)
{
    using (var reader = new StringReader(publicKey))
    {
        var pem = new Org.BouncyCastle.Utilities.IO.Pem.PemReader(reader).ReadPemObject();

        using (var stream = new MemoryStream(pem.Content, false))
        {
            var asn1 = new Asn1InputStream(stream, pem.Content.Length).ReadObject();
            var info = SubjectPublicKeyInfo.GetInstance(asn1);
            var parameters = PublicKeyFactory.CreateKey(pem.Content) as RsaKeyParameters;

            return new RSAParameters
            {
                Exponent = parameters?.Exponent?.ToByteArray(),
                Modulus = parameters?.Modulus?.ToByteArray(),
            };
        }
    }
}

RSAParameters CreatePrivateKeyParameters(string privateKey)
{
    using (var reader = new StringReader(privateKey))
    {
        var pem = new Org.BouncyCastle.OpenSsl.PemReader(reader);
        var keyPair = pem.ReadObject() as AsymmetricCipherKeyPair;
        var parameters = keyPair?.Private as RsaPrivateCrtKeyParameters;

        return new RSAParameters
        {
            D = parameters?.Exponent?.ToByteArray(),
            DP = parameters?.DP?.ToByteArray(),
            DQ = parameters?.DQ?.ToByteArray(),
            Exponent = parameters?.PublicExponent?.ToByteArray(),
            InverseQ = parameters?.QInv?.ToByteArray(),
            Modulus = parameters?.Modulus?.ToByteArray(),
            P = parameters?.P?.ToByteArray(),
            Q = arameters?.Q?.ToByteArray(),
        };
    }
}

const string PrivateKey = @"-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC7Np2qTFhnZD0meg7OqzPMLzQjZLhCjLpmVoeTPAaeTpvQij0r
EG9FmS+Gbj4oGBiV8BaOraWlRTmPh2DnJJv/WmsAmd+WRsbCowGfpmukG7X0bEp7
XIRvbwzTHV34X3JyWdq9eGyr6FjidyiMPnA+sBRuvJhvsf4kmadrHIGXuQIDAQAB
AoGACJHfdcQ458K26eP8eYlsvZQLlvMtXB6FCdo8Kok85FJE3670Tdxau5zfiA/T
2JIBzhtHbRCV3JLmp6NS1EHgwiOVgohrzLj2QME+pN05Kxs+d8snfyl+izmuluV/
4qfOEcq1q3BiKI5jjrF78vVJzkRJNN/xLDxfk+duXYxnHeECQQDoQCcVGRKThyDp
cD/flayKf0ejtfKFZieTkOcKmU5zNdACDu7QgvcgwA2emu/cH/yFIExzPcg6vAXc
cXAg6CLTAkEAzlt6zfaLfGGeWrp+2W+J9svrc060ckeFRzDHiwWA/T3c/F1fjCDs
B2qouZuRWxSQJ5XTro4uKRvd+GjBZxALwwJAC5+qVRP8KgYJT6WejMIg8I2OOFNS
3/pM4MXPymgB1SJMVs4IhccIN0tlYxq+BnZTXRZQvTPKYaYnZPEIe50ioQJBALe0
mrvWu1BsexPcKOcbM+Ago6gQLfX6k7zPwSSTLsTDmQKVgWT3PghQPIqX4qwJv1du
O3ePHar9IvttDpfYog8CQAMy9RiFqJ3K/St4iVZizsoeG/GAa+o/kKiTjK4+wnox
UdkJEZh/7lkKbw9h2CVURIjUitmKDrfjkeJ7GsdKvAM=
-----END RSA PRIVATE KEY-----";

const string PublicKey = @"-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7Np2qTFhnZD0meg7OqzPMLzQj
ZLhCjLpmVoeTPAaeTpvQij0rEG9FmS+Gbj4oGBiV8BaOraWlRTmPh2DnJJv/WmsA
md+WRsbCowGfpmukG7X0bEp7XIRvbwzTHV34X3JyWdq9eGyr6FjidyiMPnA+sBRu
vJhvsf4kmadrHIGXuQIDAQAB
-----END PUBLIC KEY-----";

4. 実行結果

復号化のタイミングでエラーとなってしまいました。
鍵の読み込みで問題が発生しているのがわかります。

err.PNG

5. 原因を探る

コチラを使ってCryptoAPIで鍵を読み込んで確認してみるとmodulusが128byteなのがわかります。(CryptoAPIで読み込んだ鍵の暗号化復号化は動作OKでした)

check3.PNG

先ほどの実行結果と比べると

check1.PNG

modullusが1byte多いのがわかります。他にも1byte多いパラメータがあるのがわかります。

それぞれを比較すると頭1byteに0x00が含まれているのが差異であることがわかりました。
試しにコレを削ったら動作するようになりました。

success.PNG

stack overflowに似たようなケースの対応方法がありましたが、やはりmodulusを1byte削っており、私のアプローチと同じです。

5. 整理すると

  1. OpenSSLの鍵の一部のパラメータは頭1byteに0x00が入っている
  2. RSACryptoServiceProviderは0x00が入っているとエラーを吐く
  3. CryptoAPIで鍵を読み込むと0x00が除去されている
  4. BouncyCastleで鍵を読み込むと0x00が含まれたまま

今回使用したOpenSSLの鍵は負値の場合、頭に0x00の追加が必要で、これはバイナリ変換規則ASN.1の仕様のようです。

修正したコードは以下で、first byteを調整しています。
現在の.NET Frameworkは、CryptoAPIのようなASN.1をデコードする機能が存在しないんでしょうかね?

success.cs
void Main()
{
        //暗号化
    var publicParameters = CreatePublicKeyParameters(PublicKey);
    var cipherText = Encrypt(publicParameters, "topsecret");
    $"encrypted:{cipherText}".Dump();

        //復号化
    var privateParameters = CreatePrivateKeyParameters(PrivateKey);
    var plainText = Decrypt(privateParameters, cipherText);
    $"decrypted:{plainText}".Dump();
}

bool RSA暗号鍵の各種パラメータの頭1バイトが0x00だったら除外する { get; set; } = true;

string Encrypt(RSAParameters parameters, string plainText)
{
    using (var rsa = new RSACryptoServiceProvider())
    {
        rsa.ImportParameters(parameters);
        var plainBytes = Encoding.UTF8.GetBytes(plainText);
        var cipherBytes = rsa.Encrypt(plainBytes, RSAEncryptionPadding.Pkcs1);

        return Convert.ToBase64String(cipherBytes);
    }
}

string Decrypt(RSAParameters parameters, string cipherText)
{
    using (var rsa = new RSACryptoServiceProvider())
    {
        rsa.ImportParameters(parameters);
        var cipherBytes = Convert.FromBase64String(cipherText);
        var plainBytes = rsa.Decrypt(cipherBytes, RSAEncryptionPadding.Pkcs1);

        return Encoding.UTF8.GetString(plainBytes);
    }
}

RSAParameters CreatePublicKeyParameters(string publicKey)
{
    using (var reader = new StringReader(publicKey))
    {
        var pem = new Org.BouncyCastle.Utilities.IO.Pem.PemReader(reader).ReadPemObject();

        using (var stream = new MemoryStream(pem.Content, false))
        {
            var asn1 = new Asn1InputStream(stream, pem.Content.Length).ReadObject();
            var info = SubjectPublicKeyInfo.GetInstance(asn1);
            var parameters = PublicKeyFactory.CreateKey(pem.Content) as RsaKeyParameters;

            //$"OID:{info.AlgorithmID.Algorithm.Id}".Dump();

            return new RSAParameters
            {
                Exponent = Adjustment(parameters?.Exponent?.ToByteArray()),
                Modulus = Adjustment(parameters?.Modulus?.ToByteArray())
            };
        }
    }
}

RSAParameters CreatePrivateKeyParameters(string privateKey)
{
    using (var reader = new StringReader(privateKey))
    {
        var pem = new Org.BouncyCastle.OpenSsl.PemReader(reader);
        var keyPair = pem.ReadObject() as AsymmetricCipherKeyPair;
        var parameters = keyPair?.Private as RsaPrivateCrtKeyParameters;

        return new RSAParameters
        {
            D = Adjustment(parameters?.Exponent?.ToByteArray()),
            DP = Adjustment(parameters?.DP?.ToByteArray()),
            DQ = Adjustment(parameters?.DQ?.ToByteArray()),
            Exponent = Adjustment(parameters?.PublicExponent?.ToByteArray()),
            InverseQ = Adjustment(parameters?.QInv?.ToByteArray()),
            Modulus = Adjustment(parameters?.Modulus?.ToByteArray()),
            P = Adjustment(parameters?.P?.ToByteArray()),
            Q = Adjustment(parameters?.Q?.ToByteArray()),
        };
    }
}

byte[] Adjustment(byte[] bytes)
{
    if (!RSA暗号鍵の各種パラメータの頭1バイトが0x00だったら除外する)
    {
        return bytes;
    }

    if (bytes == null) return bytes;

    if (bytes.Length > 0 && bytes[0] == 0x00)
    {
        var offset = 1;
        var size = bytes.Length - offset;
        var buffer = new byte[size];
        Buffer.BlockCopy(bytes, offset, buffer, 0, size);
        return buffer;
    }

    return bytes;
}

//CMD:openssl genrsa > private-key.pem
const string PrivateKey = @"-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQC7Np2qTFhnZD0meg7OqzPMLzQjZLhCjLpmVoeTPAaeTpvQij0r
EG9FmS+Gbj4oGBiV8BaOraWlRTmPh2DnJJv/WmsAmd+WRsbCowGfpmukG7X0bEp7
XIRvbwzTHV34X3JyWdq9eGyr6FjidyiMPnA+sBRuvJhvsf4kmadrHIGXuQIDAQAB
AoGACJHfdcQ458K26eP8eYlsvZQLlvMtXB6FCdo8Kok85FJE3670Tdxau5zfiA/T
2JIBzhtHbRCV3JLmp6NS1EHgwiOVgohrzLj2QME+pN05Kxs+d8snfyl+izmuluV/
4qfOEcq1q3BiKI5jjrF78vVJzkRJNN/xLDxfk+duXYxnHeECQQDoQCcVGRKThyDp
cD/flayKf0ejtfKFZieTkOcKmU5zNdACDu7QgvcgwA2emu/cH/yFIExzPcg6vAXc
cXAg6CLTAkEAzlt6zfaLfGGeWrp+2W+J9svrc060ckeFRzDHiwWA/T3c/F1fjCDs
B2qouZuRWxSQJ5XTro4uKRvd+GjBZxALwwJAC5+qVRP8KgYJT6WejMIg8I2OOFNS
3/pM4MXPymgB1SJMVs4IhccIN0tlYxq+BnZTXRZQvTPKYaYnZPEIe50ioQJBALe0
mrvWu1BsexPcKOcbM+Ago6gQLfX6k7zPwSSTLsTDmQKVgWT3PghQPIqX4qwJv1du
O3ePHar9IvttDpfYog8CQAMy9RiFqJ3K/St4iVZizsoeG/GAa+o/kKiTjK4+wnox
UdkJEZh/7lkKbw9h2CVURIjUitmKDrfjkeJ7GsdKvAM=
-----END RSA PRIVATE KEY-----";

//CMD:openssl rsa -pubout < private-key.pem > public-key.pem
const string PublicKey = @"-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7Np2qTFhnZD0meg7OqzPMLzQj
ZLhCjLpmVoeTPAaeTpvQij0rEG9FmS+Gbj4oGBiV8BaOraWlRTmPh2DnJJv/WmsA
md+WRsbCowGfpmukG7X0bEp7XIRvbwzTHV34X3JyWdq9eGyr6FjidyiMPnA+sBRu
vJhvsf4kmadrHIGXuQIDAQAB
-----END PUBLIC KEY-----";
18
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
9