1. 概要

このチュートリアルでは、Javaプラットフォームのセキュリティの基本について説明します。 また、安全なアプリケーションを作成するために利用できるものにも焦点を当てます。

セキュリティは多くの分野を網羅する広大なトピックです。 これらのいくつかは、アクセス修飾子やクラスローダーのように、言語自体の一部です。 さらに、データの暗号化、安全な通信、認証、承認などのサービスとして利用できるものもあります。

したがって、このチュートリアルでこれらすべてについて意味のある洞察を得るのは実用的ではありません。 ただし、少なくとも意味のある語彙を獲得しようとします。

2. 言語機能

とりわけ、Javaのセキュリティは言語機能のレベルから始まります。 これにより、安全なコードを記述できるだけでなく、多くの暗黙的なセキュリティ機能の恩恵を受けることができます。

  • 静的データ型付け:Javaは静的に型付けされた言語であり、型関連エラーの実行時検出の可能性を減らします
  • アクセス修飾子:Javaでは、 publicやprivateなどのさまざまなアクセス修飾子を使用して、フィールド、メソッド、クラスへのアクセスを制御できます
  • 自動メモリ管理:Javaにはガベージコレクションベースのメモリ管理があり、開発者はこれを手動で管理する必要がありません。
  • バイトコード検証:Javaはコンパイル型言語です。つまり、コードをプラットフォームに依存しないバイトコードに変換し、ランタイムは実行のためにロードするすべてのバイトコードを検証します

これはJavaが提供するセキュリティ機能の完全なリストではありませんが、ある程度の保証を与えるには十分です。

3. Javaのセキュリティアーキテクチャ

特定の領域の調査を開始する前に、Javaのセキュリティのコアアーキテクチャを理解するために時間を費やしましょう。

Javaのセキュリティのコア原則は、相互運用可能で拡張可能なプロバイダー実装によって推進されています。 Provider の特定の実装は、セキュリティサービスの一部またはすべてを実装する場合があります。

たとえば、Providerが実装する可能性のある一般的なサービスの一部は次のとおりです。

  • 暗号化アルゴリズム(DSA、RSA、SHA-256など)
  • キーの生成、変換、および管理機能(アルゴリズム固有のキーなど)

Javaには、多くの組み込みプロバイダーが付属しています。 また、アプリケーションで複数のプロバイダーを優先順に構成することもできます。

 

その結果、 Javaのプロバイダーフレームワークは、すべてのプロバイダーで、設定されている優先度の順序でサービスの特定の実装を検索します。

さらに、このアーキテクチャでは、プラグイン可能なセキュリティ機能を備えたカスタムプロバイダーを実装することが常に可能です。

4. 暗号化

暗号化は、一般およびJavaのセキュリティ機能の基礎です。 これは、敵対者の存在下での安全な通信のためのツールとテクニックを指します。

4.1. Java暗号化

Java暗号化アーキテクチャ(JCA)は、次のようなJavaの暗号化機能にアクセスして実装するためのフレームワークを提供します。

最も重要なことは、Javaが暗号化機能にプロバイダーベースの実装を利用していることです。

さらに、Javaには、RSA、DSA、AESなどの一般的に使用される暗号化アルゴリズム用の組み込みプロバイダーが含まれています。 これらのアルゴリズムを使用して、休止中、使用中、または移動中のデータにセキュリティを追加できます。

4.2. 実際の暗号化

アプリケーションでの非常に一般的な使用例は、ユーザーパスワードを保存することです。 これは、後の認証に使用します。 プレーンテキストのパスワードを保存するとセキュリティが危険にさらされることは明らかです。

したがって、1つの解決策は、プロセスが繰り返し可能であるが一方向のみであるような方法でパスワードをスクランブルすることです。 このプロセスは暗号化ハッシュ関数として知られており、SHA1はそのような一般的なアルゴリズムの1つです。

それでは、Javaでこれを行う方法を見てみましょう。

MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] hashedPassword = md.digest("password".getBytes());

ここで、 MessageDigest は、私たちが関心を持っている暗号化サービスです。 メソッドgetInstance()を使用して、利用可能なセキュリティプロバイダーのいずれかにこのサービスを要求しています。

5. 公開鍵インフラストラクチャ

公開鍵インフラストラクチャ(PKI)は、公開鍵暗号化を使用してネットワーク上で情報を安全に交換できるようにするセットアップを指します。 この設定は、通信に関与する当事者間に構築された信頼に依存しています。 この信頼は、認証局(CA)と呼ばれる中立で信頼できる機関によって発行されたデジタル証明書に基づいています。

5.1. JavaでのPKIサポート

Javaプラットフォームには、デジタル証明書の作成、保存、および検証を容易にするAPIがあります

  • KeyStore :Javaは、暗号化キーと信頼できる証明書を永続的に保存するためのKeyStoreクラスを提供します。 ここで、 KeyStoreは、キーストアファイルとトラストストアファイルの両方を表すことができます。 これらのファイルの内容は似ていますが、使用法が異なります。
  • CertStore :さらに、Javaには CertStore クラスがあります。これは、信頼できない可能性のある証明書と失効リストのパブリックリポジトリを表します。 他の使用法の中でも特に証明書パスを構築するために、証明書と失効リストを取得する必要があります

Javaには、「cacerts」と呼ばれる組み込みのトラストストアがあり、よく知られているCAの証明書が含まれています。

5.2. PKI用のJavaツール

Javaには、信頼できるコミュニケーションを促進するための非常に便利なツールがいくつかあります。

  • キーストアとトラストストアを作成および管理するための「keytool」と呼ばれる組み込みツールがあります
  • JARファイルの署名と検証に使用できる別のツール「jarsigner」もあります

5.3. Javaでの証明書の操作

Javaで証明書を操作して、SSLを使用して安全な接続を確立する方法を見てみましょう。 相互に認証されたSSL接続では、次の2つのことを行う必要があります。

  • 証明書の提示—通信の別の当事者に有効な証明書を提示する必要があります。 そのためには、キーストアファイルをロードする必要があります。このファイルには、公開キーが必要です。
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
char[] keyStorePassword = "changeit".toCharArray();
try(InputStream keyStoreData = new FileInputStream("keystore.jks")){
    keyStore.load(keyStoreData, keyStorePassword);
}
  • 証明書の確認—通信で他の当事者から提示された証明書も確認する必要があります。 このために、トラストストアをロードする必要があります。トラストストアには、以前に他の関係者からの信頼できる証明書が必要です。
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
// Load the trust-store from filesystem as before

これをプログラムで行う必要はめったになく、通常は実行時にシステムパラメータをJavaに渡します。

-Djavax.net.ssl.trustStore=truststore.jks 
-Djavax.net.ssl.keyStore=keystore.jks

6. 認証

認証は、パスワード、トークン、または現在利用可能なその他のさまざまな資格情報などの追加データに基づいて、ユーザーまたはマシンの提示されたIDを検証するプロセスです。

6.1. Javaでの認証

Java APIは、プラグ可能なログインモジュールを利用して、アプリケーションにさまざまな、多くの場合複数の認証メカニズムを提供します。 LoginContext はこの抽象化を提供します。これは構成を参照し、適切なLoginModuleをロードします。

複数のプロバイダーがログインモジュールを利用できるようにしていますが、Javaにはいくつかのデフォルトのログインモジュールがあります

  • Krb5LoginModule 、Kerberosベースの認証用
  • JndiLoginModule 、LDAPストアに基づくユーザー名およびパスワードベースの認証用
  • KeyStoreLoginModule 、暗号化キーベースの認証用

6.2. 例によるログイン

認証の最も一般的なメカニズムの1つは、ユーザー名とパスワードです。 JndiLoginModuleを介してこれを実現する方法を見てみましょう。

このモジュールは、ユーザーからユーザー名とパスワードを取得し、JNDIで構成されたディレクトリー・サービスに対してそれを検証する役割を果たします。

LoginContext loginContext = new LoginContext("Sample", new SampleCallbackHandler());
loginContext.login();

ここでは、LoginContextのインスタンスを使用してログインを実行しています。 LoginContext は、ログイン構成のエントリの名前を取ります—この場合、それは「サンプル」です。 また、ユーザー名やパスワードなどの詳細についてユーザーと対話する LoginModule を使用して、CallbackHandlerのインスタンスを提供する必要があります。

ログイン設定を見てみましょう。

Sample {
  com.sun.security.auth.module.JndiLoginModule required;
};

簡単に言えば、JndiLoginModuleを必須のLoginModuleとして使用していることを示しています。

7. 安全な通信

ネットワークを介した通信は、多くの攻撃ベクトルに対して脆弱です。 たとえば、誰かがネットワークを利用して、転送中にデータパケットを読み取る可能性があります。 長年にわたり、業界はこの通信を保護するために多くのプロトコルを確立してきました。

7.1. 安全な通信のためのJavaサポート

Javaは、暗号化、メッセージ整合性、およびクライアントとサーバーの両方の認証を使用してネットワーク通信を保護するためのAPIを提供します。

  • SSL / TLS:SSLとその後継であるTLSは、データ暗号化と公開鍵インフラストラクチャを介して、信頼できないネットワーク通信を介したセキュリティを提供します。 Javaは、パッケージ「java.security.ssl」で定義されているSSLSocketを介してSSL/TLSのサポートを提供します。
  • SASL:Simple Authentication and Security Layer(SASL)は、クライアントとサーバー間の認証の標準です。 Javaは、パッケージ「java.security.sasl」の一部としてSASLをサポートしています。
  • GGS-API / Kerberos:Generic Security Service API(GSS-API)は、Kerberosv5などのさまざまなセキュリティメカニズムを介してセキュリティサービスへの均一なアクセスを提供します。 Javaは、パッケージ「java.security.jgss」の一部としてGSS-APIをサポートしています。

7.2. 動作中のSSL通信

ここで、SSLSocket を使用して、Javaで他のパーティとの安全な接続を開く方法を見てみましょう。

SocketFactory factory = SSLSocketFactory.getDefault();
try (Socket connection = factory.createSocket(host, port)) {
    BufferedReader input = new BufferedReader(
      new InputStreamReader(connection.getInputStream()));
    return input.readLine();
}

ここでは、SSLSocketFactoryを使用してSSLSocketを作成しています。 この一環として、暗号スイートや使用するプロトコルなどのオプションのパラメーターを設定できます。

これが正しく機能するためには、前に見たように、キーストアとトラストストアを作成して設定している必要があります。

8. アクセス制御

アクセス制御とは、ファイルシステムやコードベースなどの機密性の高いリソースを不当なアクセスから保護することを指します。 これは通常、そのようなリソースへのアクセスを制限することによって実現されます。

8.1. Javaでのアクセス制御

SecurityManagerクラスを介したクラスPolicyおよびPermissionを使用して、Javaでアクセス制御を実現できます。 SecurityManager は、「 java.lang 」パッケージの一部であり、Javaでのアクセス制御チェックの実施を担当します。

クラスローダーが実行時にクラスをロードすると、Permissionオブジェクトにカプセル化されたクラスにいくつかのデフォルトのアクセス許可が自動的に付与されます。 これらのデフォルトの権限を超えて、セキュリティポリシーを通じてクラスにより多くのレバレッジを付与できます。 これらは、クラスPolicyによって表されます。

コード実行のシーケンス中に、ランタイムが保護されたリソースの要求を検出した場合、 SecurityManagerは、呼び出しスタックを介して、インストールされたポリシーに対して要求されたアクセス許可を確認します。 その結果、アクセス許可を付与するか、SecurityExceptionをスローします。

8.2. ポリシー用のJavaツール

Javaには、プロパティファイルから認証データを読み取るPolicyのデフォルト実装があります。 ただし、これらのポリシーファイルのポリシーエントリは特定の形式である必要があります。

Javaには、ポリシーファイルを作成するためのグラフィカルユーティリティである「policytool」が付属しています。

8.3. 例によるアクセス制御

Javaのファイルなどのリソースへのアクセスを制限する方法を見てみましょう。

SecurityManager securityManager = System.getSecurityManager();
if (securityManager != null) {
    securityManager.checkPermission(
      new FilePermission("/var/logs", "read"));
}

ここでは、 SecurityManager を使用して、FilePermissionでラップされたファイルの読み取り要求を検証しています。

ただし、SecurityManagerはこの要求をAccessControllerに委任します。 AccessController は、インストールされているポリシーを内部的に利用して決定を下します。

ポリシーファイルの例を見てみましょう。

grant {
  permission 
    java.security.FilePermission
      <<ALL FILES>>, "read";
};

基本的に、すべてのファイルに読み取り権限を付与しています。 ただし、セキュリティポリシーを使用すると、よりきめ細かい制御を提供できます。

SecurityManagerがJavaにデフォルトでインストールされていない可能性があることに注意してください。 これを確実にするには、常に次のパラメータを使用してJavaを起動します。

-Djava.security.manager -Djava.security.policy=/path/to/sample.policy

9. XML署名

XML署名は、データの保護に役立ち、データの整合性を提供します。 W3Cは、XML署名のガバナンスに関する推奨事項を提供します。 XML署名を使用して、バイナリデータなどのあらゆるタイプのデータを保護できます。

9.1. JavaでのXML署名

Java APIは、推奨ガイドラインに従ってXML署名の生成と検証をサポートします。 Java XMLデジタル署名APIは、パッケージ「java.xml.crypto」にカプセル化されています。

署名自体は単なるXMLドキュメントです。 XML署名には、次の3つのタイプがあります。

  • 切り離された:このタイプの署名は、Signature要素の外部にあるデータ上にあります
  • エンベロープ:このタイプの署名は、Signature要素の内部にあるデータの上にあります
  • エンベロープ:このタイプの署名は、Signature要素自体を含むデータ上にあります

確かに、Javaは、上記のすべてのタイプのXML署名の作成と検証をサポートしています。

9.2. XML署名の作成

次に、袖をまくり上げて、データのXML署名を生成します。 たとえば、ネットワークを介してXMLドキュメントを送信しようとしている可能性があります。 したがって、受信者がその整合性を検証できるようにする必要があります

それでは、Javaでこれを実現する方法を見てみましょう。

XMLSignatureFactory xmlSignatureFactory = XMLSignatureFactory.getInstance("DOM");
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
 
Document document = documentBuilderFactory
  .newDocumentBuilder().parse(new FileInputStream("data.xml"));
 
DOMSignContext domSignContext = new DOMSignContext(
  keyEntry.getPrivateKey(), document.getDocumentElement());
 
XMLSignature xmlSignature = xmlSignatureFactory.newXMLSignature(signedInfo, keyInfo);
xmlSignature.sign(domSignContext);

明確にするために、ファイル“ data.xml”に存在するデータのXML署名を生成しています。一方、このコードについて注意すべき点がいくつかあります。

  • まず、 XMLSignatureFactory は、XML署名を生成するためのファクトリクラスです。
  • XMLSigntaure には、署名を計算するためのSignedInfoオブジェクトが必要です。
  • XMLSigntaureにはKeyInfoも必要です。これは、署名キーと証明書をカプセル化します。
  • 最後に、 XMLSignature は、DOMSignContextとしてカプセル化された秘密鍵を使用してドキュメントに署名します。

その結果、 XMLドキュメントには署名要素が含まれるようになり、これを使用して整合性を検証できます。

10. コアJavaを超えたセキュリティ

これまで見てきたように、Javaプラットフォームは、安全なアプリケーションを作成するために必要な多くの機能を提供します。 ただし、これらは非常に低レベルであり、たとえばWebの標準的なセキュリティメカニズムに直接適用できない場合があります。

たとえば、私たちのシステムで作業する場合、通常、完全なOAuthRFCを読み取って自分で実装する必要はありません。 多くの場合、セキュリティを実現するために、より迅速で高レベルの方法が必要です。 ここでアプリケーションフレームワークが登場します。これらは、ボイラープレートコードを大幅に削減して目的を達成するのに役立ちます。

そして、Javaプラットフォームでは–一般的にはSpringSecurityを意味します。 フレームワークはSpringエコシステムの一部ですが、実際には純粋なSpringアプリケーションの外部で使用できます。

簡単に言うと、認証、承認、およびその他のセキュリティ機能を、単純で宣言的な高レベルの方法で実現するのに役立ちます。

もちろん、Spring Securityは、一連のチュートリアルと、 LearnSpringSecurityコースのガイド付きの方法で幅広く取り上げられています。

11. 結論

つまり、このチュートリアルでは、Javaのセキュリティの高レベルのアーキテクチャについて説明しました。 また、Javaがいくつかの標準暗号化サービスの実装をどのように提供するかを理解しました。

また、認証やアクセス制御などの分野で拡張可能でプラグ可能なセキュリティを実現するために適用できる一般的なパターンのいくつかを見ました。

要約すると、これはJavaのセキュリティ機能の概要を示しています。 したがって、このチュートリアルで説明する各領域は、さらに調査する価値があります。 しかし、うまくいけば、この方向で始めるのに十分な洞察が必要です!