Fintan

鍵ファイルの生成と取得で色々調べた話

CONTENTS

  • arrow_forward鍵ファイルの生成と取得で色々調べた話
     

    ブログ, 大阪

はじめに

こんにちは。西日本テクノロジー&イノベーション室の田村です。

私の所属する組織はTISの本部組織として、他部門が担う顧客向け開発PJに、技術難易度の高い領域のサポートを期待されて参画する機会があります。
いわゆるアーキテクトであったり、ハイスキルエンジニアとしての役割を期待されてPJに参画するのですが、未経験の技術に触れることも少なくありません。 その場合は、調査・技術修得をすすめながらの作業となります。
他部門への事業貢献を担いながら、時には自身のスキルアップを臨むことも出来る、やりがいのある仕事です。

そういった業務の中で、私が未経験ながらにJOSEのJWE、JWSを用いた暗号化処理の実装を担当する機会がありました。

私は暗号化処理の実装が未経験なことに加え、鍵や証明書に関する知識が不足していたため、作業を進める上で調査が不可欠でした。

この記事では、そんな私が暗号化の実装を進める上で調査して苦戦した部分や勉強になった内容を紹介します。

※この開発時に扱った技術は「JWEとJWSによる暗号化の実装」として事例を公開しています。
JOSEのJWE, JWSを使った暗号化の具体的な実装方法にも触れていますのでぜひご覧ください。

この記事で説明すること

実装を進める上で特に苦戦した部分が、暗号鍵の生成とその取得方法です。

暗号化を実現するためには、予め鍵ファイルとして生成したRSA秘密鍵と公開鍵のペアを生成し、アプリへ取り込めるようにする必要がありました。

調査を進める上で、暗号鍵の生成方法も鍵ファイルの取得方法も調べると色々な方法が見つかりました。
しかし、調べた通りに鍵ファイルを生成し取り込む処理を組み合わせても、エラーが発生してしまい上手くいきませんでした。
それぞれの方法で前提としていた情報が異なっていたため、必要な前提知識を理解していなかった私は、エラー内容を調べても直接的な理由が理解できず苦戦していたのです。

この記事では、そんな鍵ファイルの生成方法と取得方法で苦戦した部分について説明します。

STEP1:仕様を理解する

今回の案件では、暗号化に必要な公開鍵はクライアント側で生成したものを使用することが決まっていました。
ですが、署名で必要な秘密鍵は私たちサーバ側担当者が生成し、署名検証用の公開鍵をクライアント側に連携する必要がありました。
また、その際の公開鍵は有効期限つきの証明書の形式で連携する必要がありました。

以下が、署名に必要な鍵ペアの仕様です。

  • RSA暗号方式
  • PEM形式
  • 秘密鍵長:2048bit
  • 公開鍵証明書の有効期限:10年

STEP2:わからない部分を調査する

当初PEM形式とその他の形式、普通の公開鍵と公開鍵証明書の違いを理解していなかった私は、実装を進めながらいくつかつまづいてしまった点がありました。
具体的に以下のような用語がわからず、調べながら進めました。

PEM形式とDER形式

鍵ファイルを生成する際、PEM形式かDER形式かを指定する必要があります。
PEM形式かDER形式かは、鍵や証明書に関係なく、そのファイルのエンコーディングを表しています。DER形式のファイルをBase64でエンコードしたものを、PEMファイルと言います。

PEM形式のファイルは先頭と末尾にそれぞれ-----BEGIN ...-----END ...から始まる一文があります。秘密鍵の場合-----BEGIN RSA PRIVATE KEY----------END RSA PRIVATE KEY-----の文字を確認できます。
そのため、PEM形式の鍵をJavaのアプリケーションに取り組む際は前後の文言を取り除き、Base64でデコードする必要があります。

逆に、DER形式の鍵ファイルを取り込む処理を実装する際は前後の文言の除去やBase64でデコードする必要が無く、シンプルな取り込み処理の実装で済みます。
ですが、DER形式の取り込み処理でPEM形式のファイルを取り込もうとしてもエンコーディングが異なるため取り込みに失敗します。逆も同様です。

公開鍵と公開鍵証明書

秘密鍵と対になる公開鍵ですが、鍵ではなく証明書の形をしたものとふたつの形式があります。
公開鍵証明書の方をCRTファイルと言い、.crt拡張子をしています。
証明書になっていない普通の鍵ファイルはPEM形式だと以下の文言が確認できます。

  • 先頭:-----BEGIN PUBLIC KEY-----
  • 末尾:-----END PUBLIC KEY-----

証明書の場合以下の文言になります。

  • 先頭:-----BEGIN CERTIFICATE-----
  • 末尾:-----END CERTIFICATE-----

こちらも、エンコーディングの形式と同様にファイルの取り込み方法が異なるため、実装前に留意しておく必要があります。

STEP3:鍵の生成と取り込みをやってみる

このように基本的な用語も調べながら、改めて鍵の生成と取り込み方法を調査した結果、「JWEとJWSによる暗号化の実装」で紹介した処理を実現することが出来ました。

こちらも色々と調べながら進めましたので、ここでまとめてみます。

鍵ペアの生成

まず、任意のディレクトリに2048bit長のRSA秘密鍵を生成します。

鍵ペアの生成には OpenSSLをインストールし、opensslコマンドを利用しました。
opensslコマンドは、デフォルトでPEM形式のファイルが生成されます。

openssl genrsa 2048 > Private.Key

次に、証明書の発行に必要なCSRファイル(証明書署名要求)を作成します。

今回の開発では、公開鍵証明書として自己署名証明書を採用しました。

openssl req -new -key Private.Key -out server.csr

CSRファイルを作成したら、最後にCRTファイル(公開鍵証明書)を作成します。
有効期限10年の日数(3650日)をここで指定します。

openssl x509 -in server.csr -out Public.crt -req -signkey Private.Key -days 3650

これで秘密鍵と公開鍵の生成が完了しました。

鍵の取り込み

生成した鍵ファイルをアプリケーションに取り込みます。
鍵ファイルは所定のディレクトリに配置し、ファイルパスをそれぞれ引数privateKeyPathcertificatePathに設定して連携しています。

秘密鍵の取り込み

先述したように、PEM形式の鍵はJavaアプリケーションで使用できるように先頭と末尾の文言を削除し、Base64デコードする必要があります。

また、秘密鍵を生成する為に、セキュリティプロバイダを指定する必要があります。
今回はBouncy Castleという暗号化APIのプロバイダを利用しました。

public class EncryptData {
    /**
     * 秘密鍵を取得する。
     */
    public PrivateKey getPrivateKey(String privateKeyPath){

        try (Stream<String> stream = Files.lines(privateKeyPath)) {
            // PEMの先頭と末尾を削除
            String pem = stream.filter(s -> !"-----BEGIN RSA PRIVATE KEY-----".equals(s)
            && !"-----END RSA PRIVATE KEY-----".equals(s)
            && !"\r\n".equals(s)).collect(Collectors.joining());

            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64Util.decode(pem));
            return KeyFactory.getInstance("RSA", new BouncyCastleProvider()).generatePrivate(keySpec);
        }
    }
}

公開鍵の取り込み

今回の実装では署名処理そのものに公開鍵は必要ありませんでしたが、署名した情報を単体レベルで署名検証する為に、公開鍵も取り込む必要がありました。
公開鍵証明書の場合、CertificateFactorygenerateCertificateメソッドを使用することでPEM形式の証明書を取り込むことが可能です。

public class EncryptData {
    /**
     * 公開鍵証明書を取得する。
     */
    public RSAPublicKey getCertificate(String certificatePath){
        try (InputStream inStream = Files.newInputStream(certificatePath)) {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            Certificate cert = cf.generateCertificate(inStream);
            return (RSAPublicKey) cert.getPublicKey();
        }
    }
}

まとめ

鍵の生成と取得方法を実装する上で、調査した内容と苦戦した内容をまとめました。

Webアプリサービスで鍵ファイルを使用する場合、生成して取り込むまでの作業が必要になるため、セットで学ぶ良い経験になりました。
何より、調査を重ねて実施可能な限りの検証を行ったことで、接続テストを円滑に通過できたことが一番の成果でした。

本記事が鍵の生成、取得方法について同様の悩みを抱える方の参考になれば幸いです。


本コンテンツはクリエイティブコモンズ(Creative Commons) 4.0 の「表示—継承」に準拠しています。

このエントリーをはてなブックマークに追加


TIS株式会社
個人情報保護方針 個人情報の取り扱いについて 情報セキュリティ方針 当サイトのご利用にあたって

Copyright 2021 TIS Inc.keyboard_arrow_up