この記事は「大石泉すき」アドベントカレンダー 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[]配列で表される。
// 暗号化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サーバ)
/// <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で結果を返却する実装にした
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;}}
- 単純な復号処理。公開鍵・秘密鍵は絶対にハードコーディングしないこと
メッセージ送信側(コンソールアプリ)
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に伝わるだろうか。
/// <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 [大石泉すき]
参考資料
- Accepting Raw Request Body Content in ASP.NET Core API Controllers
- Asp .net Coreで生のByte配列を取得するのに躓いたので、半日くらいこの文書を探していました。