はじめに
News.comやZDNetなどの技術ニュースサイトでは、ある有名企業のデータベースがハッカーの侵入を受けて、顧客の個人情報が大量に流出したというニュースが、ほぼ数週間おきに盛んに報じられています。もし、一般公開されるデータドリブンのWebアプリケーションを構築する立場であれば、このようなニュースを耳にして思わずぞっとすることでしょう。外部の脅威からアプリケーションのデータを守るには、どのような手段を講じればよいのでしょうか。
データドリブンアプリケーションを悪意のあるハッカーから保護するために実施できる手段は、数多くあります。最初の、そして最も重要な方法は、データベースソフトウェアを強化することです。最新のサービスパックがインストールされていますか? Microsoft SQL Serverを使用している場合は、saアカウントに対して十分に複雑なパスワードを設定していますか? データベースは、Windows認証のみを許可するように設定されていますか? データベースのセキュリティ向上の詳細については、「10 Steps to Help Secure SQL Server 2000」を参照してください。SQL Serverを可能な限り最適に設定したとしても、不測の事態が発生して、悪意ある人の手にデータが渡らないとは限りません。財務記録や社会保障番号など、特に機密性の高い情報がある場合は、セキュリティのレイヤを新たに追加し、機密データを暗号化する必要があるかもしれません。
Microsoft SQL Server 2000以前のバージョンは、データベーステーブルのコンテンツを自動的に暗号化する方法を備えていません(この記事の執筆時点でSQL Server 2005はまだベータ版ですが、ネイティブの暗号化機能を備えています。詳細については、「SQL Server 2005 Security - Encryption」を参照してください)。したがって、テーブルのコンテンツを暗号化するのであれば、自分自身で行う必要があります。さまざまな方法がありますが、この記事では、.NETレイヤ内でコードを使用して、SQL Serverに機密データを書き込む前にそのデータを暗号化する方法と、SQL Serverから暗号化されたコンテンツを読み取るときにそれをプレーンテキスト形式に復号する方法について説明します。
データベースコンテンツの暗号化の概要
Eコマースサイトのデータベースを作成するものとしましょう。このデータベースの「Customers」テーブルには、Name
、Address
、Email
、CustomerSince
、CreditCardNumber
、CreditCardExpiration
のようなフィールドが含まれています。「Customers」内のすべてのレコードとフィールドはもちろん非公開でなければなりませんが、支払いに関するフィールド(CreditCardNumber
およびCreditCardExpiration
)は、特に非公開であることが必要です。顧客の電子メールアドレスや自宅住所がハッカーに流出した場合、顧客は当然ながら不愉快な思いをしますが、支払い情報が流出した場合は、不愉快なだけでなく、実際の経済的な被害を引き起こすおそれがあります。そこで、この記事では、最悪の場合を想定し、次のように考えることにします。「データベースサーバーは可能な限り強化したので、これを破るのは非常に困難だ。たとえ、セキュリティが破られてデータが危険にさらされても、支払い情報が暗号化されていれば、最も重要な情報はさらに保護されているわけだから大丈夫だ。」
これを実現するには、いくつかの課題、懸案、および問題があります。
- データベースのスキーマ
- データベースにデータを挿入する前にデータを暗号化する方法
- 使用する暗号化アルゴリズムと、選択した暗号化ルーチンが全体的なセキュリティに及ぼす影響
- テーブルに格納されているデータを暗号化した場合の影響(ある場合)
CreditCardNumber
フィールドとCreditCardExpiration
フィールドを単にプレーンテキストとして格納している場合、これらはおそらく単なるvarchar
フィールドです。しかし、これらを暗号化する場合、フィールドのデータ型は何が適当でしょうか?これらはすべて、この記事全体を通して取り組む重要な問題です。すぐに取り上げる問題もあれば、後で扱う問題もあります。
暗号化されたデータを格納するには、varbinary
型のフィールドを使用します。つまり、暗号化データは、一連のバイトとして保存されます。そのため、暗号化データを挿入または更新する場合は、ASP.NETアプリケーションから次の操作を実行する必要があります。
- ユーザーのプレーンテキスト入力を取得します。
- それを暗号化します。
- それを文字列からバイト配列に変換します。
INSERT
/UPDATE
ステートメントを実行します。
反対に、データベースから暗号化データを読み取る場合は、基本的に逆の操作を実行する必要があります。
SELECT
ステートメントを使用して、データベースから暗号化データを読み取ります。- それをバイト配列から文字列に変換します。
- それを復号します。
- プレーンテキストデータを処理します。
この手順は、暗号化データを挿入/更新または読み取るたびに毎回繰り返す必要があります。そこで、何度も繰り返されるこの機能を、ラッパークラスに移動することを強くお勧めします。ASP.NETにおけるコード再利用法の詳細については、「Accessing Common Code, Constants, and Functions in an ASP.NET Project」を参照してください。
CreditCardNumber
フィールドおよびCreditCardExpiration
フィールドだけであり、他のすべてのフィールドはプレーンテキストであることに注意してください。したがって、顧客の支払い情報の読み取りや挿入/更新を行う場合のみ、これらの手順を実行する必要があります。暗号化アルゴリズムの選択
暗号化アルゴリズムには、対称と非対称の2種類があります。対称アルゴリズムでは共有キー(secret key)を使用します。これは、暗号化と復号の当事者のみが知るビット情報です。非対称暗号化では、公開キー(public key)と秘密キー(private key)を使用します。これらの暗号化モデルの詳細については、この記事では触れません。今回のサンプルでは、対称暗号化を使用します。
話はそれますが、このことは、セキュリティの基本概念の1つをよく表しています。つまり、「絶対的なセキュリティ」などというものは現実には存在しないということです。もちろん、アプリケーションのセキュリティを強化する手段を講じることはできます。しかし、完璧に保護されたセキュリティを実現することは不可能です。抜け穴や裏口は常に存在するものであり、サーバールームにアクセスできる不満を抱えた従業員などもいるでしょう。セキュリティに優れたアプリケーションを作成するということは、攻撃者にとっての攻略難易度を可能な限り高めることです。
暗号化ルーチンの中にも、特に堅牢性に優れているものがいくつかあります。最も堅牢な対称暗号化ルーチンの1つがAESです。AESは、米国政府が使用する対称キー暗号化の標準規格です。実際、AESを使用してデータベースの暗号化を実現する方法について、優れた記事が既に発表されています(『Implementing Encrypted SQL Server Database Columns with .NET』)。.NET FrameworkのSystem.Security.Cryptography
名前空間には、この暗号化アルゴリズムを実装しているクラスが用意されています。
AESの観点については既に説明されているため、この記事では別の対称暗号化ルーチンを使用します。ここでは、特にRC4を使用します。RC4を選択した1つの理由は、4Guysで以前に発表されたMike Shafferの記事『RC4 Encryption Using ASP & VBScript』が、Chris Scottの記事『Converting Mike Shaffer's VBScript RC4 Encryption Code to C#』の中で.NETコードに変換されたことです。このアルゴリズム(さらに言えば、セキュリティに関係するアルゴリズム)を実際のコードで使用する前に、RC4およびその長所と短所についてしっかり理解してください。
対称暗号化について、最後に1つだけ述べておきます。共有キーでデータを暗号化する場合、同じデータに対して同じ共有キーを使用すると、同じ暗号化出力が得られることに注意してください。暗号化している出力が複製を持つ場合、このことは、システムの弱点になり得ます。たとえば、顧客xのクレジットカードの有効期限が07/09であることを、ハッカーが知っているものとします(彼自身が顧客xの場合もあれば、他のサイトで顧客xの支払い情報を入手した場合もあります)。顧客xの暗号化された有効期限が、たとえば「&9k@m」であることがハッカーに知られた場合、暗号化された有効期限が同じ「&9k@m」である他の顧客についても、そのクレジットカードの有効期限が07/09であることが判明してしまいます。この情報だけでは、ハッカーが何をできるわけでもありませんが、不正な目的に一歩近づいたことは確かです。
この弱点を克服するには、共有キーに、各行固有の特別なビット情報を加えることが重要です。つまり、まったく同じ共有キーを使用してすべての顧客の有効期限を暗号化するのではなく、共通の共有キーに顧客のCustomerID
フィールドを連結して作成した共有キーを使用するのです(もちろんこれは、CustomerID
フィールドが変わらないことを前提にしています。それよりも、ソルト(salt)を保持する別のフィールドをテーブルに作成する方が賢明です)。ソルト処理の詳細については、4Guysに掲載されたThomas Tomiczekの記事『Could you Pass the Salt? Improving the Security in Encrypting Passwords using MD5』を参照してください。
以降では、データベースの作成と、必要な暗号化/復号を実行するコードの作成について説明します。
Customersデータベーステーブルの作成
ここまでに説明したように、暗号化データをデータベースに格納する場合はvarbinary
型のフィールドを使用するのに対して、非暗号化フィールドは、通常のデータ型を維持します。したがって、「Customers」テーブルのスキーマは次のようになります。
CustomerID | int, PK, IDENTITY(1,1) |
Name | nvarchar(100) |
CustomerSince | datetime |
varchar(150) | |
CreditCardNumber | varbinary(25) |
CreditCardExpiration | varbinary(10) |
... | ... |
顧客ごとに一意なソルトを格納する、varchar
型のSalt
フィールドを追加することもできます。または、既存の顧客のCustomerID
フィールドが変わらないという前提で、CustomerID
をソルトとして使用することもできます。
暗号化データの挿入および更新
今回の例では、「Customer」テーブルの支払い関係のフィールドのみを暗号化するため、通常は、顧客情報を挿入または更新する前に、データの暗号化について気にする必要はありません。暗号化処理が必要になるのは、顧客の支払い情報の挿入または更新時だけです。
これを実現するコードは、比較的簡単です。
'Start by creating an instance of the RC4Encrypt class Dim rc4 As New main.rc4encrypt 'Set the secret key and data to encrypt rc4.Password = <YourSecretKey> & <customerID> rc4.PlainText = <Data To Encrypt> 'Get the encrypted results back as a string Dim encString As String = rc4.EnDeCrypt() 'Convert the string into a byte array Dim encData() as Byte = System.Text.Encoding.UTF8.GetBytes(encString) 'Update the database Dim sql = "UPDATE Customers SET CreditCardNumber = @CCN " & _ "WHERE CustomerID = @CustID" ... myCommand.Parameters.Add("@CustID", <customerID>) myCommand.Parameters.Add("@CCN", encData) myCommand.ExecuteNonQuery() ...
このコード例では詳細が省略されていますが、キーポイントは明らかです。暗号化を実行するために、『Converting Mike Shaffer's VBScript RC4 Encryption Code to C#』で紹介されているmain.rc4encrypt
クラスを使用しています。最初に、このクラスのPassword
プロパティとPlainText
プロパティを、共有キーと暗号化するデータにそれぞれ設定します。ここで使用する共有キーは、ある基本文字列と、顧客のCustomerID
(またはキーをソルト処理するために使用するもの)で構成されます。次に、実際のデータが暗号化され、暗号化された結果を含む文字列が返されます。さらに、文字列がバイト配列に変換され、アドホックSQLステートメントを使用して、「Customers」テーブル内の該当する行が更新されます(ストアドプロシージャを使用するのが最適です)。
ASP.NETアプリケーション内のデータの復号と操作
上記のコード例は、データをデータベースに格納する前に暗号化する方法を示していました。既存の支払い情報を操作する必要がある場合は、ASP.NETアプリケーションの中で、最初に、暗号化されたデータベースデータを取得し、それを復号する必要があります。これは、先ほど紹介した手順の逆に相当します。
'Read in the encrypted database data Dim sql = _ "SELECT CreditCardNumber FROM Customers WHERE CustomerID = @CustID" ... myCommand.Parameters.Add("@CustID", <customerID>) Dim reader as SqlDataReader = myCommand.ExecuteReader() If reader.Read() Then 'Ok, let's decrypt this data! First, read the data into a byte array Dim encData() as Byte = reader.GetSqlBinary(0).Value 'Convert byte array into string Dim encString as String = System.Text.Encoding.UTF8.GetString(encData) 'Creating an instance of the RC4Encrypt class Dim rc4 As New main.rc4encrypt 'Set the secret key and data to encrypt rc4.Password = <YourSecretKey> & <customerID> rc4.PlainText = encString 'Get the decrypted results back as a string Dim CCN As String = rc4.EnDeCrypt() 'Work with the data! ... End If
ここでは、暗号化の場合と同じ手順を逆に繰り返します。まず、バイト配列として格納されている、データベース内の暗号化された値を読み取ります。このバイト配列を、文字列に変換します。この暗号化文字列を、暗号化の際に使用したものと同じ共有キー/ソルト値と共に、main.rc4encrypt
クラスに設定します。最後に、EnDeCrypt()
を呼び出して値を復号し、文字列としてプレーンテキスト値を返します。この時点から、復号されたデータを必要に応じて操作できます。
暗号化:実装の価値はあるか?
2005年冬に発行されたTechNet Magazineの「SQL Questions & Answers」セクションでは、データの暗号化について述べられていました。SQL Server 2000内のテーブルデータの暗号化に関する読者からの質問に対して、編集者(Nancy Michell)は次のように答えています。
「データベース管理者が、SQL Serverデータベース内のデータを暗号化することがあります。一般に、これは道を誤っています。安全なボックスを構築し、監査を行い、厳密なアクセス制御でアクセスを保護するならば、実際には、データ自体を暗号化する意味はありません。データの暗号化には、オーバーヘッド、ソート、ストアドプロシージャなど、多くの問題が伴います。」
データベースのコンテンツを暗号化するには、コーディング時間、デバッグ時間、および労力を余分に要しますが、それだけの価値はあるのでしょうか。それはひとえに、アプリケーションの要件、データの機密性、顧客や上司の要望にかかっています。データの暗号化は、システムに対して新たな保護のレイヤを追加しますが、明らかな弱点がいくつかあります(たとえば、対称暗号化の場合、ハッカーの手に共有キーが渡ってしまうと暗号化は役に立ちません。また、秘密キーをなくした場合は暗号化データを利用できなくなります)。コンテンツを暗号化すると、パフォーマンスオーバーヘッドが生じ、クエリーも問題になってきます。たとえば、クレジットカードの有効期限が3か月以内であるすべての顧客を返すクエリーを作成するものとします。クレジットカード情報が暗号化されていて、各顧客に対して一意の共有キーを使用している場合、このようなクエリーを作成することはできません(代わりに、すべてのレコードをアプリケーションに読み込み、クレジットカードの有効期限を復号し、フィルタ処理によって、有効期限が3か月以内のものを探し出す必要があります)。
また、Nancyが指摘するように、T-SQLステートメントでは暗号化データを処理できません。暗号化されていないデータを使用すれば、ストアドプロシージャ内で簡単に調査、編集、および実行時判断を行うことができます。暗号化データの場合、アプリケーションレイヤで実際の暗号化/復号処理が発生するため、そうはいきません(もちろん、暗号化するデータをT-SQLステートメントで使用する必要がない場合や、多くのユーザーが関係するレポートでクエリーを行う必要がない場合もあります。そうした場合、欠点の多くは問題ではなくなります)。
このような問題があるにもかかわらず、顧客や上司は、新たなセキュリティレイヤをシステムに追加せよと強行に主張するかもしれません。そのときは、難攻不落のセキュリティ手段など決して存在しないことを教え、新しいセキュリティレイヤを追加した場合に考えられる不利益を指摘してあげましょう。
まとめ
この記事では、実際に暗号化データを格納するというアプローチで機密データのセキュリティを強化する方法を説明しました。特にこの記事では、対称キー暗号化アルゴリズムのRC4を使用して、ASP.NETアプリケーションのデータを暗号化/復号する方法に注目しました。この結果、ハッカーがデータベースサーバーに侵入しても、重要なデータは保護されます。もちろん、完全なセキュリティ機構などというものはありません。その目的はあくまでも、ハッカーが機密情報を入手することを、可能な限り困難にすることです。非常に重要なデータベース情報(財務データや社会保障番号など)のコンテンツを暗号化することは、このような強化手段の1つです。
では、ハッピープログラミング!