LoginSignup
9

More than 5 years have passed since last update.

posted at

updated at

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

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-----";

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
What you can do with signing up
9