jCryption.NET HTTP FORM POSTの暗号化

Fork me on GitHub

jCryptionとは

http://www.jcryption.org/

jCryptionはウェブページのフォームデータをHTTPでPOSTする際に、データをRSA/AESで暗号化してサーバーに送信するjQueryのプラグインライブラリです。

jCryption.NETはjCryptionの送信した暗号データをサーバーで復号化するためのASP.NET用のライブラリです。

背景

ログインフォーム、問い合わせフォーム、アンケートフォームなど、ウェブページ上で利用者の入力を受けサーバーへデータを送信する場面は多くあります。一方で、個人情報、IDやパスワードなど他者に見られたくない内容を含む場合、そのフォーム送信は「暗号化」によって保護された状態で送信されるべきで、通常、平文通信であるHTTPでの送信は避けHTTPSで保護されるべきです。しかし、HTTPS/SSLの通信をサポートするためには高額(ベリサインの場合8万円程度)のサーバー証明書のための費用とウェブサーバーのIPアドレスを一つ独占するというコストがかかり、ちょっとしたアンケートフォームなどの目的には導入を躊躇する場面もあります。

本格的にウェブサーバーとの通信にHTTPS/SSLを導入することなく、クライアントからサーバーへ送信される情報を暗号化により保護しようというのがjQueryのプラグインであるjCryptionというJavascriptのライブラリのコンセプトです。

一般にHTTPS/SSLには

  1. 通信の相手であるサーバーが本物であるかどうかを保証すること(サーバー証明書)
  2. 通信中のデータの暗号化

の2種類の目的がありますが、jCryptionは2つ目の通信中のデータの暗号化のみをサポートします。

jCryptionが使用する暗号は RSA/AESで、RSAはクライアント側で生成されたAESの鍵を暗号化してサーバーへ送信するために使われます。実際にPOSTされるフォームのデータはAESで暗号化されて送信されます。

これから先のjCryptionそのものの詳しい内容は本家のウェブサイトに譲ります。http://www.jcryption.org/(英語)

ここでは、jCryption.NETを使った例を紹介します。

jCryption.NET

ソースコードとサンプルはGitHub で公開しています。

クライアント側の使用法は本家で解説されているように、jQueryとjCryptionのライブラリを<script>で読み込み
 $(“#form”).jcryption(….)
で指定したフォームをjCryption対応にセットします。
このフォームを送信するにあたって、暗号の鍵の交換、データの解読にサーバー側のプログラムが必要になります。本家のパッケージではPHPやPerl CGIによる実装がすでに含まれています。ここでは、Windows Server + IIS + ASP.NETな環境でサーバー側の実装を紹介します。

想定環境

  • IIS6以上のウェブサーバー(Windows 2003 Server以降)
  • .NET 4.0 以上の.NET Framework
  • ASP.NET WebPagesと呼ばれる cshtml ファイルによるページ

ASPXページの場合

ページをjCryption.SecurePage を継承させることで、暗号処理を処理させられます。javascript部分は手作業で挿入します。
<%@  Language="C#" Inherits="jCryption.SecurePage" %>
<html>
<head>
    <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
    <script type="text/javascript" src="Scripts/jquery.jcryption.3.1.0.js"></script>
    <script type="text/javascript">
        $(function () {
            $("#normal").jCryption({
                getKeysURL: "<%= Request.Path %>?getPublicKey=true",
		            handshakeURL: "<%= Request.Path %>?handshake=true"
		        });
		    });
    </script>
</head>

CSHTMLページの場合

  1. jCryption.csファイルを App_Codeフォルダへ配置します。
  2. フォームのあるcshtmlページの先頭部分に@using jCryption を記述します。
  3. 鍵の交換、復号化などを同じページ上(URL)で行うため、処理の初めに
    jCryption.HandleRequest(Request);
    を呼び出します。この呼び出しの後で、Request.Formは復号化されたデータにアクセスできます。
  4. HTML内で、依存するjqueryを読み込みます。jquery.validateも読み込み可能です。
  5. HTML内、上記のjqueryの読み込みの後、適当な場所に
    @jCryption.RenderScriptFor(“#login”, src:”/Scripts/jquery.jcryption….js”);
    を記述します。jcryptionのjsファイルを直接<script>で指定しないのは、このページがHTTPSでアクセスされた場合に、自動的にjcryptionを無視して読み込まない動作を行うためです。

@using jCryption
@{
    jCryption.HandleRequest(Request);
}
<!doctype html>
<html>
   <head>
      <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
      @jCryption.RenderScriptFor("#normal", src: "Scripts/jquery.jcryption.3.1.0.js")
   </head>
   ......
   <form id="normal">
      .....
   </form>
   ......

フォームの初期データ、部分的にHTMLを暗号化して読み込む機能

jCryption.NETをcshtmlで使用する場合に、フォームデータと、HTMLテキストの暗号化にも対応しています。もともとjCryptionはクライアントからサーバーへ送信されるデータの保護を目的としていますが、ログインIDやパスワード、住所氏名などはフォーム上であらかじめわかっている内容を入れてHTML出力されることがあります。この場合HTTPのままですと、データは平文で保護されておらず、片手落ちとなります。

  1. フォームの各要素を次のように記述します。
    <input type=text name=Name value=”山田” />は
    <input type=text @jCryption.SecureNameValue(“Name”,”山田”) />

    チェックボックスやラジオボタンは
    <input type=”checkbox” @jCryption.SecureNameValueCheck(“Sex”,”Male”, true ) />

  2. HTMLまたはテキスト領域を暗号化で保護したい場合は
    @jCryption.SecureHtml()
    @jCryption.SecureText()
  3. 上記の呼び出しの後に
    @jCryption.LoadSecureContents()
    を呼び出します。ここに、一連の復号化処理、サーバーとの通信処理が記述されます。

ライブラリリファレンス

@jCryption.RenderScriptFor( String selector, String src = null, String script = null )

jCryption を利用するFORMをjQueryのセレクタで指定します。この呼び出しは通常 head タグか body 内最後の方に配置します。jquery.validate と同時に使用する場合は jquery.validate を読み込む script タグの後になるようにします。

selector: jCryptionで初期化する対象のFORMを jQuery のセレクタで指定します。

src: jCryption のjsファイルへのパスを指定します。

script: その他のjavascriptが必要な場合に <script>...</script>を含むHTMLを指定します。


@jCryption.SecureNameValue( String name, String value )

フォーム要素の初期状態で表示されるデータを保護します。<input> タグ内に記述します。.

 <input type='text' @jCryption.SecureNameValue("Name", "Smith") />

input タグの name と value を引数に指定します。


@jCryption.SecureNameValueCheck( String name, String value, bool check )

input の checkbox radiobutton に対応したバージョンです。
    <input type='checkbox' @jCryption.SecureNameValueCheck("Animal", "Dog", true ) />
    <input type='checkbox' @jCryption.SecureNameValueCheck("Animal", "Cat", false ) />

@jCryption.SecureText( String text )

暗号化されたテキストコンテンツを設定します。HTML内の任意の位置に記述可能です。<div>で囲まれ暗号化データとともにHTMLとして出力されます。

@jCryption.SecureHtml( String html )

暗号化されたHTMLコンテンツを設定します。HTML内の任意の位置に記述可能です。<div>で囲まれ暗号化データとともにHTMLとして出力されます。

@jCryption.LoadSecureContents()

SecureNameValue/SecureNameValueCheck/SecureText/SecureHtml の内容を保護された実際のデータをDOM要素中に読み込むことを指示します。上記4種類メソッド呼び出しの一番最後にこの呼び出しを追加してください。

jCryption version 3.1.0に内在するいくつかの技術的問題点

最後に本家jCryptionに見られるいくつかの技術的問題点を指摘しておきます。

  1. AESの鍵が<form>単位で異なる問題
    クライアントはformのsubmit時にAESキーを生成しajax呼び出しでサーバーに通知します。この時サーバーはSessionにAES鍵を保存します。同じSession上で同時にformのsubmitが起こることはまれですが、原理上、クライアントとサーバーで鍵の1対1の整合性が保てていません。
  2. 先の理由により、<form> POST後にブラウザのF5(更新)によって再度POSTした場合にSession上に保存された鍵との一貫性の保証がなく、再POSTがエラーとなります。これには、POSTデータに暗号化されたフォームデータとともに暗号化されたAES鍵も同時に送信されるようにすべきです。
  3. jquery.validateライブラリと一緒に使用したときの問題があります。jCryptionはSubmitボタンclickイベントをフックして暗号化処理ののち改めてformのsubmitを呼び出します。一方で、jquery.validateはformのsubmitイベントでvalidationを実行しています。処理順序が逆転することで2つのライブラリを同時に使用することができません。(jCryption.NETではこの点を改善するjavascriptのコードを出力しています。)
※上記の問題を修正したバージョンを jquery.jcryption.3.1.0.mod.js として同梱しています。


Androidの非同期通知機能 Google Cloud Messaging (GCM) のサーバー側コードをC#で書いてみた

.NETの標準以外に Newtonsoft のJSON.NETを使用します。http://james.newtonking.com/pages/json-net.aspx
使い方は

    var container = new Google.GoogleCloudMessaging.Container {
        RegistrationIds = new String [] { "APA*******PQZQ" },
        Data = new { message = "Hello World!" }
    };
    var response = Google.GoogleCloudMessaging.Broadcast( "AIz************", container );

のようになります。

------------------------------

 

using System;
using System.Net;

using Newtonsoft.Json;

namespace Google
{
    public class GoogleCloudMessaging
    {
        public class Container
        {
            [JsonProperty("registration_ids")]
            public String[] RegistrationIds { get; set; }
            [JsonProperty("collapse_key", NullValueHandling=NullValueHandling.Ignore )]
            public String CollapseKey { get; set; }
            [JsonProperty("data", NullValueHandling = NullValueHandling.Ignore)]
            public object Data { get; set; }
            [JsonProperty("delay_while_idle", NullValueHandling = NullValueHandling.Ignore)]
            public Boolean? DelayWhileIdle { get; set; }
            [JsonProperty("time_to_live", NullValueHandling = NullValueHandling.Ignore)]
            public int? TimeToLive { get; set; }
            [JsonProperty("restricted_package_name", NullValueHandling = NullValueHandling.Ignore)]
            public String RestrictedPackageName { get; set; }
            [JsonProperty("dry_run", NullValueHandling = NullValueHandling.Ignore)]
            public Boolean? DryRun { get; set; }
        }
        public class Response
        {
            [JsonProperty("multicast_id")]
            public String MulticastId { get; set; }
            [JsonProperty("success")]
            public int Success { get; set; }
            [JsonProperty("failure")]
            public int Failure { get; set; }
            [JsonProperty("canonical_ids")]
            public int CanonicalIds { get; set; }
            [JsonProperty("results")]
            public Result[] Results { get; set; }
        }
        public class Result
        {
            [JsonProperty("message_id")]
            public String MessageId { get; set; }
            [JsonProperty("registration_id")]
            public String RegistrationId { get; set; }
            [JsonProperty("error")]
            public String Error { get; set; }
        }
        static public Response Broadcast(String API_KEY, Container container)
        {
            var request = WebRequest.Create("https://android.googleapis.com/gcm/send");
            var json = Newtonsoft.Json.JsonConvert.SerializeObject(container);
            using( WebClient web = new WebClient()){
                web.Headers.Add("Authorization", "key=" + API_KEY );
                web.Headers.Add("Content-Type", "application/json");
                byte[] result = web.UploadData("https://android.googleapis.com/gcm/send", "POST", System.Text.Encoding.UTF8.GetBytes(json) );
                String resultString = System.Text.Encoding.UTF8.GetString(result);
                var response = Newtonsoft.Json.JsonConvert.DeserializeObject<Response>( resultString );
                return response;
            }
        }
    }
}