9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

「大石泉すき」Advent Calendar 2019

Day 4

「大石泉すき」を公開鍵暗号標準(RSA)で暗号化/復号する

Last updated at Posted at 2019-12-04

この記事は「大石泉すき」アドベントカレンダー 4日目の記事となります。
4日目は、「大石泉すきを暗号化しよう!」とします。

2019/12/04 21:37追記
当初「復号」を「復号化」と記載していたため、修正いたしました。ご指摘いただき、ありがとうございます。

何をしたいのか

「大石泉すき」という言葉を、一度暗号に直し、再度意味の通る文字に戻したい(復号したい)です。
今回は、公開鍵暗号を用いてこれを行っていこうと思います。

公開鍵暗号とは

仕組み自体は本筋ではないので、詳しくは下記の記事などを参照してください。

公開鍵暗号方式とは、暗号化と復号に別々の鍵を用いる暗号方式である。「非対称鍵暗号方式」とも呼ばれる。
~(中略)~
公開鍵暗号方式では、「暗号文を作り出す鍵」と「暗号文を元に戻す鍵」が異なる。暗号通信を行いたい人は、まず独自に2つの鍵のペアを作成する。同時に生成された一対の鍵のうち一方を公開鍵として公開し、他方を秘密鍵として厳重に管理する。送信者は受信者の公開鍵で暗号文を作成して送る。受信者は、自分の秘密鍵で受け取った暗号文を復号する。
引用元:@IT 公開鍵暗号方式とは

AliceとBobという名前の二者間の通信を例に出すと、

  • Bobは、Aliceに**秘密の文字列(平文)**を送りたい
  • Bobは、Aliceが公開している公開鍵(誰でも知ることが出来る)を使って、文字列を暗号化する
  • Aliceは、Bobから暗号化した文字列を受け取る
  • Aliceは、自分が持っている秘密鍵(Aliceしか知らない)で文字列を復号する
  • Aliceは、Bobから送られた秘密の文字列を受け取る

こんな風に、文字列の送受信が秘密に行えます。
(実際に利用される場合は、公開鍵が本当に正しいか、Aliceが所持しているかを証明する第三者機関が居たりしますが、今回は割愛します)

環境

今回、二者間の通信を表現するために、以下のような構成でソースコードを書きました。
C#で書く場合、AliceとBobでソリューションを分けてください。

Alice(メッセージ受信側)

  • 役割
    • HTTPサーバ上で公開鍵を公開する
    • 受信した暗号オブジェクトを復号する
  • 言語・フレームワーク
    • C# 8.0
    • ASP .net Core 3.0

Bob(メッセージ送信側)

  • 役割
    • Aliceが公開している公開鍵を入手し、秘密の文字列を暗号化する
    • 暗号化した文字列をAliceに送信する
  • 言語・フレームワーク
    • C# 8.0
    • .net Core 3.0 コンソールアプリケーション

実装

暗号化・復号の実装

下記のライブラリを使いました。

使い方は下記の記事を参照。
更新日付を見る限り .net Framework用のライブラリを使っての紹介記事だと思われるが、.net Core用のライブラリでも同様の使い方で暗号化・復号が可能。

使い方は大体こんな感じ。暗号文・復号文共にbyte[]配列で表される。

EncryptDecrypt.cs
        // 暗号化
        public static byte[] Encrypt(byte[]) bytes, string publickey)
        {
            // PEMフォーマットの公開鍵を読み込んで KeyParam を生成
            var publicKeyReader = new PemReader(new StringReader(publickey));
            var publicKeyParam = (AsymmetricKeyParameter)publicKeyReader.ReadObject();

            var RSA = new Pkcs1Encoding(new RsaEngine());
            // RSA暗号オブジェクトを初期化(第1引数trueは暗号化、falseは復号)
            RSA.Init(true, publicKeyParam);

            // 暗号化対象のバイト列・長さを渡し、暗号化した結果のバイト列を受け取る
            byte[] encrypted = RSA.ProcessBlock(bytes, 0, bytes.Length);

            return encrypted;
        }

        // 復号
        public byte[] Decrypto(byte[] cipher, string privateKey)
        {
            // PEMフォーマットの秘密鍵を読み込んで KeyParam を生成
            var privateKeyReader = new PemReader(new StringReader(privateKey));
            var privateKeyParam = (AsymmetricCipherKeyPair)privateKeyReader.ReadObject();

            var RSA = new Pkcs1Encoding(new RsaEngine());
            // RSA暗号オブジェクトを初期化(第1引数trueは暗号化、falseは復号)
            RSA.Init(false, privateKeyParam.Private);

            // 復号対象のバイト列・長さを渡し、復号した結果のバイト列を受け取る
            var decrypto = RSA.ProcessBlock(cipher, 0, cipher.Length);
            return decrypto;
        }

BouncyCastle入手方法

  • ソリューションエクスプローラから「依存関係」を右クリック
  • 「NuGet パッケージの管理」をクリック > 「参照」をクリック
  • 「BouncyCastle.NetCore」を検索欄に入力し、出てきたものをインストール
    • 「BouncyCastle」は .net Framework用なので注意

鍵ペアの生成

動確検証用の公開鍵/暗号鍵のペアは、このサイトで生成しました。
https://travistidwell.com/jsencrypt/demo/index.html

個別実装

メッセージ受信側(HTTPサーバ)

RsaRemoteController.cs
        /// <summary>
        /// 暗号byte[]配列の復号
        /// </summary>
        /// <returns></returns>
        [HttpPost("")]
        public async System.Threading.Tasks.Task<string> DecryptoAsync()
        {
            byte[] encrypto;
            using (var ms = new MemoryStream(2048))
            {
                await Request.Body.CopyToAsync(ms);
                encrypto = ms.ToArray();  // returns base64 encoded string JSON result
            }

            var cert = new Cert();
            var decryptoByte = cert.Decrypto(encrypto, Cert.PRIVATE_KEY);
            
            // ログ
            _logger.LogInformation($"Decrypto [{Encoding.UTF8.GetString( decryptoByte )}]");
            return Encoding.UTF8.GetString( decryptoByte );
        }

        /// <summary>
        /// 公開鍵
        /// </summary>
        /// <returns></returns>
        [HttpGet("Alice/cert")]
        public string GetCert()
        {
            return Cert.PUBLIC_KEY;
        }
  • {ルートパス}/Alice/certに、Getリクエスト:公開鍵を生のstring型で返却する
  • ルートパスに、bodyに生のbyte配列(暗号文)を添付しPostリクエスト:リクエストのbyte配列を復号したbyte配列を、文字列に変換する。
    • 今回は、どのように変換したのか分かるように、stringで結果を返却する実装にした
RsaRemoteController.cs
    class Cert
    {
        // 実際の運用時はハードコーディングせず、セキュアな場所に保存し逐一読み込むこと
        // Generate by https://travistidwell.com/jsencrypt/demo/index.html
        internal static readonly string PUBLIC_KEY = @"(略)";

        // 実際の運用時はハードコーディングせず、セキュアな場所に保存し逐一読み込むこと
        // Generate by https://travistidwell.com/jsencrypt/demo/index.html
        internal static readonly string PRIVATE_KEY =  @"(略)";

        internal Pkcs1Encoding RSA { get; }

        public Cert()
        {
            RSA = new Pkcs1Encoding(new RsaEngine());
        }

        /// <summary>
        /// 対称鍵暗号で暗号文を復号する
        /// </summary>
        /// <param name="cipher">平文の文字列</param>
        /// <param name="privatekey">秘密鍵</param>
        /// <returns>復号された文字列</returns>
        public byte[] Decrypto(byte[] cipher, string privateKey)
        {
            // PEMフォーマットの秘密鍵を読み込んで KeyParam を生成
            var privateKeyReader = new PemReader(new StringReader(privateKey));
            var privateKeyParam = (AsymmetricCipherKeyPair)privateKeyReader.ReadObject();

            var RSA = new Pkcs1Encoding(new RsaEngine());
            // RSA暗号オブジェクトを初期化(第1引数trueは暗号化、falseは復号)
            RSA.Init(false, privateKeyParam.Private);

            // 復号対象のバイト列・長さを渡し、復号した結果のバイト列を受け取る
            var decrypto = RSA.ProcessBlock(cipher, 0, cipher.Length);
            return decrypto;
        }
    }
  • 単純な復号処理。公開鍵・秘密鍵は絶対にハードコーディングしないこと

メッセージ送信側(コンソールアプリ)

Bob.cs
        static void Main()
        {
            // HttpClientを使うための準備。今回はあまり関係ない
            // HTTPConnectionFactoryを使うため、DI設定を行う
            var serviceCollection = new ServiceCollection()
                .AddHttpClient()                                         // IHttpClientFactoryの依存設定
                .AddSingleton<IHttpConnection, HttpConnectionSample>()    // IHTTPConnectionの依存設定
                .BuildServiceProvider();

            // DI設定済みのIHttpConnectionを実装したクラスを取得
            var connector = serviceCollection.GetService<IHttpConnection>();


            // 大石泉すき
            string plainText = "大石泉すき";
            Console.WriteLine($"PlainText\r\n{plainText}\r\n");

            // サーバから公開鍵を取得する
            // SendGetメソッドの中身はただのHttpClient.GetAsyncです
            var publicKey = connector.SendGet($"https://{メッセージ受信側HTTPサーバのIP:Port}/Alice/cert").Result;

            // RSA暗号標準オブジェクト(PKCS#1)を生成
            var rsa = new Pkcs1Encoding(new RsaEngine());

            // 暗号化
            var encrypted = Encrypt(plainText, publicKey, rsa);

            // byte配列は化けるのでBase64でエンコードしておく
            Console.WriteLine($"Encrypted(Base64 Encoded)\r\n{Convert.ToBase64String(encrypted)}\r\n");

            // 暗号文(配列)を復号するべく、サーバに暗号文を送信
            // SendPostメソッドの中身はただのHttpClient.PostAsyncです
            var decrypted = connector.SendPost($"https://{メッセージ受信側HTTPサーバのIP:Port}/rsaremote/", encrypted).Result;

            // サーバで復号した結果を表示
            Console.WriteLine($"Decrypted\r\n{decrypted}\r\n");
        }
  • 公開鍵取ってきてーの暗号化してーの復号してもらいーののコントローラクラス
  • 「大石泉すき」を知っているのはBobだけ。暗号化してAliceに伝わるだろうか。
Bob.cs

        /// <summary>
        /// 公開鍵で文字列を暗号化する
        /// </summary>
        /// <param name="text">平文の文字列</param>
        /// <param name="publickey">Pem形式の公開鍵</param>
        /// <returns>暗号化されたByte</returns>
        public static byte[] Encrypt(string text, string publickey, Pkcs1Encoding rsa)
        {
            var bytes = Encoding.UTF8.GetBytes(text);

            // PEMフォーマットの公開鍵を読み込んで KeyParam を生成
            var publicKeyReader = new PemReader(new StringReader(publickey));
            var publicKeyParam = (AsymmetricKeyParameter)publicKeyReader.ReadObject();

            // RSA暗号オブジェクトを初期化(第1引数 true は「暗号化」を示す)
            rsa.Init(true, publicKeyParam);

            // 対象のバイト列を渡し暗号化した結果のバイト列を受け取る
            byte[] encrypted = rsa.ProcessBlock(bytes, 0, bytes.Length);

            return encrypted;
        }
  • 単純な暗号化処理。公開鍵・秘密鍵は今回クライアントは持っていない

動作確認

サーバ側を起動させた状態で、クライアント側を実行

クライアント側
PlainText
大石泉すき

Encrypted(Base64 Encoded)
W2joxjgxL+Q6CtCYaSGCzpx4fJYspCb7KRI/2Ddlnt//70o0R/039Hx6R2fywqCEF0Q21MqpF4/BbjzDM8lAKJgPEIFx5Gp2kYBO08B6bjYdrhSPgIeWEIj7ulwZPO4TD+G5bGwrZn/ogapQfUbTY748B49h1/d4t0IowxRartc=

Decrypted
大石泉すき
サーバ側
RsaServer.Controllers.RsaRemoteController: Information: Decrypto [大石泉すき]

参考資料

9
1
2

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
9
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?