暗号処理

暗号化とは

暗号化は、もとのデータ(文字列など)を、一見してなんだかわからないものに変換する処理です。解読キーを使うと、もとの文字列に復元することができ、これを復号化といいます。

Java言語は、暗号処理のライブラリをjava.cryptパッケージに持っているので、いろいろな暗号化方式を使って、暗号処理が可能です。

暗号処理クラス

AES暗号を使って暗号処理をする簡単なライブラリクラスを作成しました。ソースコードは、著者名の記述を削除しない限り、配布、改良は自由です。jarファイルは、次のリンクからダウンロードできます。

http://k-webs.jp/Java01/download/cryptography.jar

また、ソースコードは、cryptographyプロジェクトをダウンロードしてください。NetBeansでそのまま読み込んで使うことができます。

AES暗号

1990年代後半、それまでアメリカ政府が公用に使っていたDES暗号が、そこらのパソコン(たしか30台ほどを使って)で破られてしまうことが明らかになりました。そこで世界コンペをやって新しい暗号方式を公募することになりました。日本からも楕円暗号などのエントリーがあったのですが、最終的にAES暗号が採用されました。

CBCモード
AES暗号は、全体を小さなブロックに切って、少しずつ暗号処理をします。その際、直前に暗号化したブロックと、次に暗号化するブロックのXORを取り、それを暗号化するという手順を行うのがCBCモードです。直前のブロックの暗号が、次のブロックの暗号化に影響するので、特定の文字列がどのような暗号に符号化されるか、平文と暗号を見比べても、推測できないという、優れた特徴があります。

IV(Initial Vector)

CBCモードでは、先頭のブロックにはXORで重ねるデータブロックがありません。そこで、先頭ブロック用には、人為的に値を作りますが、これがIVです。IVは、Javaの標準クラスを使う場合、128ビットの文字列です。

ランダムな文字列でIVを作成

暗号処理時に、毎回異なるIVを与えると、平文は同じでも毎回違う暗号ができます。そこでIVはランダムな文字列を使います。コモンズのライブラリ(commons-lang3-3.4-bin.zip)にある、RandomStringUtilsを使うと、ランダムな文字列を、長さや使う文字の種類などを指定して簡単に生成できます。

暗号解読キー

暗号化、復号化の両方で使用するキー文字列です。これも128ビット、つまり、16文字ちょうどでなければいけません。内容は自由に作成できますが、解読キーが長すぎれば切り詰め、短すぎれば文字列を補うなどの処理を、システム側でする必要があります。

ソースコード

import java.io.Serializable;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
 * AES暗号/CBCモードによる暗号処理クラス
 * @author 川場隆 kawaba@tk-webs.com 
 */
public class Crypto implements Serializable {
    private Cipher encrypter; // 暗号化用の暗号器
    private Cipher decrypter; // 復号化用の暗号器

    /* コンストラクタ(引数は暗号解読キーとIV)
     *   引数は、どちらもStringからbyte[]に変換したものを指定する
     */
    public Crypto(byte[] secretKey, byte[] ivs) throws Exception{
        IvParameterSpec iv = new IvParameterSpec(ivs);    // 暗号化時のスタートブロック用の初期値を作成
        
        SecretKeySpec key = new SecretKeySpec(secretKey, "AES"); // 暗号方式+解読キーのセットを作成
        encrypter = Cipher.getInstance("AES/CBC/PKCS5Padding");  // 暗号方式と生成方式などを指定して暗号器を作成
        encrypter.init(Cipher.ENCRYPT_MODE, key, iv);            // 暗号器を暗号化モードにセットする

        decrypter = Cipher.getInstance("AES/CBC/PKCS5Padding");  // もうひとつ、暗号器を作成しておく
        decrypter.init(Cipher.DECRYPT_MODE, key, iv);            // 暗号器を復号モードにセットする
    }

    /* 暗号化処理を実行するメソッド */
    public String encrypt(String text) throws Exception {
        byte[] crypto = encrypter.doFinal(text.getBytes()); // 暗号化する
        byte[] str64 = Base64.getEncoder().encode(crypto);  // 表示できるように文字の配列に変換する
        return new String(str64);                           // さらに文字列にしておく
    }
    /* 復号化処理を実行するメソッド */
    public String decrypt(String str64) throws Exception {
        byte[] str = Base64.getDecoder().decode(str64);     // 暗号文字列を元のバイナリに戻す
        byte[] text = decrypter.doFinal(str);               // 復号化する
        return new String(text);                            // 文字列に変換して返す
    }
}

Cryptoクラスの使い方

暗号を使うのはどういう時でしょう。パスワードなどを暗号化しますね。そして、それを復号化するのは、例えばログイン処理などで照合する時です。明らかに、暗号化と復号化は、実行のタイミングが違うわけです。

ですから、暗号解読キーとIVは、システム内でファイルやデータベースに(さらに暗号化して)記録して保管しておかねばなりません。ここでは、そのさらなる暗号化の処理は省略しますが、いづれにせよ、暗号化、復号化のタイミングで、暗号解読キーと復号キーを書きだしたり、読みだしたりしなければなりません。

この使用例では、読み出しと書きこみには、byte配列をそのままオブジェクトとしてファイルに保管するユーティリティ(FileUtilクラス)を使っています。FileUtilクラスは、ダウンロードしたプロジェクトファイルの中にソースコードがありますので参照してください。

使用例のコード

import org.apache.commons.lang3.RandomStringUtils;
import util.FileUtil;

public class CryptoExample {
    public static void main(String args[]) {
        /*******************************************************************************
         * 暗号化と復号化は、別々に行われるのが普通であるので、暗号解読キーと、IVは、ファイル
         * に保存しておき、実行時に読み込んで利用する
         *******************************************************************************/

        /*------------------------------------------------------------------------------
           データの準備 
        --------------------------------------------------------------------------------*/
        byte[] iv  = RandomStringUtils.randomAlphanumeric(16).getBytes();// IV(暗号化時のスタートブロック用の初期値 128ビット固定長)
        byte[] key = "kawaba_2015_key_".getBytes();  // 暗号解読キー(128ビット固定長)
        
        /*------------------------------------------------------------------------------
           IVと暗号解読キーはファイルに保存しておく 
        --------------------------------------------------------------------------------*/
        FileUtil.writeBytes(iv,  "iv");       
        FileUtil.writeBytes(key, "secret");

        /*------------------------------------------------------------------------------
           データの表示 
        --------------------------------------------------------------------------------*/
        System.out.println("IV(スタートブロック用の初期値)="+new String(iv)+ "("+iv.length + "byte)");
        System.out.println("暗号解読キー=" + new String(key) + "(16byte)");        
        
        /*------------------------------------------------------------------------------
           暗号化の処理 
        --------------------------------------------------------------------------------*/
        String source = "なんたらかんたら"; // 暗号化する文字列
        String result = "";                // 暗号化した結果の文字列  
        try {                                                                              // Cryptoオブジェクトを生成する
            Crypto c = new Crypto(FileUtil.readBytes("secret"), FileUtil.readBytes("iv")); // 解読キーとIVをファイルから読み込む
            result = c.encrypt(source);                                                    // 暗号化した文字列を得る
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("暗号=" + result);  // 暗号文字列を表示する
        
        /*------------------------------------------------------------------------------
           復号化の処理 
        --------------------------------------------------------------------------------*/
        try {                                                                              // Cryptoオブジェクトを生成する
            Crypto c2 =new Crypto(FileUtil.readBytes("secret"), FileUtil.readBytes("iv")); // 解読キーとIVをファイルから読み込む
            result = c2.decrypt(result);                                                   // 復号化した文字列を得る
        } catch (Exception e) {
            e.printStackTrace();
        }        
        System.out.println("復号=" + result);  // 元の文字列を表示する
    }
}

このコードを実行すると、次のように表示されます。

何度か実行してみて、同じ文字列でも、毎回、違う暗号に変換されることを確認してください。

IV(スタートブロック用の初期値)=u7aTAfOMMKJvD02S(16byte)
暗号解読キー=kawaba_2015_key_(16byte)
暗号=piiKP/GX3HcykHbXAHjptPDtEORdx6oASa2luFtAbZA=
復号=なんたらかんたら


読者になる

コメントをどうぞ

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

%d人のブロガーが「いいね」をつけました。