Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9360

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

$
0
0

この記事は「大石泉すき」アドベントカレンダー 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
// 暗号化publicstaticbyte[]Encrypt(byte[])bytes,stringpublickey){// PEMフォーマットの公開鍵を読み込んで KeyParam を生成varpublicKeyReader=newPemReader(newStringReader(publickey));varpublicKeyParam=(AsymmetricKeyParameter)publicKeyReader.ReadObject();varRSA=newPkcs1Encoding(newRsaEngine());// RSA暗号オブジェクトを初期化(第1引数trueは暗号化、falseは復号)RSA.Init(true,publicKeyParam);// 暗号化対象のバイト列・長さを渡し、暗号化した結果のバイト列を受け取るbyte[]encrypted=RSA.ProcessBlock(bytes,0,bytes.Length);returnencrypted;}// 復号publicbyte[]Decrypto(byte[]cipher,stringprivateKey){// PEMフォーマットの秘密鍵を読み込んで KeyParam を生成varprivateKeyReader=newPemReader(newStringReader(privateKey));varprivateKeyParam=(AsymmetricCipherKeyPair)privateKeyReader.ReadObject();varRSA=newPkcs1Encoding(newRsaEngine());// RSA暗号オブジェクトを初期化(第1引数trueは暗号化、falseは復号)RSA.Init(false,privateKeyParam.Private);// 復号対象のバイト列・長さを渡し、復号した結果のバイト列を受け取るvardecrypto=RSA.ProcessBlock(cipher,0,cipher.Length);returndecrypto;}

BouncyCastle入手方法

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

鍵ペアの生成

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

個別実装

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

RsaRemoteController.cs
/// <summary>/// 暗号byte[]配列の復号/// </summary>/// <returns></returns>[HttpPost("")]publicasyncSystem.Threading.Tasks.Task<string>DecryptoAsync(){byte[]encrypto;using(varms=newMemoryStream(2048)){awaitRequest.Body.CopyToAsync(ms);encrypto=ms.ToArray();// returns base64 encoded string JSON result}varcert=newCert();vardecryptoByte=cert.Decrypto(encrypto,Cert.PRIVATE_KEY);// ログ_logger.LogInformation($"Decrypto [{Encoding.UTF8.GetString(decryptoByte)}]");returnEncoding.UTF8.GetString(decryptoByte);}/// <summary>/// 公開鍵/// </summary>/// <returns></returns>[HttpGet("Alice/cert")]publicstringGetCert(){returnCert.PUBLIC_KEY;}
  • {ルートパス}/Alice/certに、Getリクエスト:公開鍵を生のstring型で返却する
  • ルートパスに、bodyに生のbyte配列(暗号文)を添付しPostリクエスト:リクエストのbyte配列を復号したbyte配列を、文字列に変換する。
    • 今回は、どのように変換したのか分かるように、stringで結果を返却する実装にした
RsaRemoteController.cs
classCert{// 実際の運用時はハードコーディングせず、セキュアな場所に保存し逐一読み込むこと// Generate by https://travistidwell.com/jsencrypt/demo/index.htmlinternalstaticreadonlystringPUBLIC_KEY=@"(略)";// 実際の運用時はハードコーディングせず、セキュアな場所に保存し逐一読み込むこと// Generate by https://travistidwell.com/jsencrypt/demo/index.htmlinternalstaticreadonlystringPRIVATE_KEY=@"(略)";internalPkcs1EncodingRSA{get;}publicCert(){RSA=newPkcs1Encoding(newRsaEngine());}/// <summary>/// 対称鍵暗号で暗号文を復号する/// </summary>/// <param name="cipher">平文の文字列</param>/// <param name="privatekey">秘密鍵</param>/// <returns>復号された文字列</returns>publicbyte[]Decrypto(byte[]cipher,stringprivateKey){// PEMフォーマットの秘密鍵を読み込んで KeyParam を生成varprivateKeyReader=newPemReader(newStringReader(privateKey));varprivateKeyParam=(AsymmetricCipherKeyPair)privateKeyReader.ReadObject();varRSA=newPkcs1Encoding(newRsaEngine());// RSA暗号オブジェクトを初期化(第1引数trueは暗号化、falseは復号)RSA.Init(false,privateKeyParam.Private);// 復号対象のバイト列・長さを渡し、復号した結果のバイト列を受け取るvardecrypto=RSA.ProcessBlock(cipher,0,cipher.Length);returndecrypto;}}
  • 単純な復号処理。公開鍵・秘密鍵は絶対にハードコーディングしないこと

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

Bob.cs
staticvoidMain(){// HttpClientを使うための準備。今回はあまり関係ない// HTTPConnectionFactoryを使うため、DI設定を行うvarserviceCollection=newServiceCollection().AddHttpClient()// IHttpClientFactoryの依存設定.AddSingleton<IHttpConnection,HttpConnectionSample>()// IHTTPConnectionの依存設定.BuildServiceProvider();// DI設定済みのIHttpConnectionを実装したクラスを取得varconnector=serviceCollection.GetService<IHttpConnection>();// 大石泉すきstringplainText="大石泉すき";Console.WriteLine($"PlainText\r\n{plainText}\r\n");// サーバから公開鍵を取得する// SendGetメソッドの中身はただのHttpClient.GetAsyncですvarpublicKey=connector.SendGet($"https://{メッセージ受信側HTTPサーバのIP:Port}/Alice/cert").Result;// RSA暗号標準オブジェクト(PKCS#1)を生成varrsa=newPkcs1Encoding(newRsaEngine());// 暗号化varencrypted=Encrypt(plainText,publicKey,rsa);// byte配列は化けるのでBase64でエンコードしておくConsole.WriteLine($"Encrypted(Base64 Encoded)\r\n{Convert.ToBase64String(encrypted)}\r\n");// 暗号文(配列)を復号するべく、サーバに暗号文を送信// SendPostメソッドの中身はただのHttpClient.PostAsyncですvardecrypted=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>publicstaticbyte[]Encrypt(stringtext,stringpublickey,Pkcs1Encodingrsa){varbytes=Encoding.UTF8.GetBytes(text);// PEMフォーマットの公開鍵を読み込んで KeyParam を生成varpublicKeyReader=newPemReader(newStringReader(publickey));varpublicKeyParam=(AsymmetricKeyParameter)publicKeyReader.ReadObject();// RSA暗号オブジェクトを初期化(第1引数 true は「暗号化」を示す)rsa.Init(true,publicKeyParam);// 対象のバイト列を渡し暗号化した結果のバイト列を受け取るbyte[]encrypted=rsa.ProcessBlock(bytes,0,bytes.Length);returnencrypted;}
  • 単純な暗号化処理。公開鍵・秘密鍵は今回クライアントは持っていない

動作確認

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

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

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

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

参考資料


Viewing all articles
Browse latest Browse all 9360

Latest Images

Trending Articles