package org.itscool.commons.util;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

/**
 * 暗号化と複合化クラス<br>
 * <br>
 * プログラムで暗号を使いたいとき，自前で実装するのは非常に気を遣います。<br>
 * なぜなら，バグがあったときにデータが復元できなくなるため，慎重に実装
 * したうえで十分にテストしておく必要があるからです。<br>
 * 
 * J2SE1.4ではセキュリティパッケージが多数組み込まれたため，
 * これらのロジックを自前で記述する必要性は大幅に低下しました。<br>
 * そこで，ここで簡単な利用法を紹介します。<br>
 * 
 * 暗号化・復号化は一般にバイト列を暗号化してバイト列にする，というのが 
 * 標準的な使い方です。 <br>
 * 
 * このクラスは「テキスト→暗号バイト列→テキスト」の為のユーティリティクラスです。
 *
 * @author KANO
 * @since jdk1.4.1
 * @version 1.00A 2004/11/1
 */
public class AngouUtil {
    private static final String LINE = System.getProperty("LINE.separator");
    private static String Alogorithm = "Blowfish";
	//private static String Alogorithm = "DESede";

    public static void main(String[] argv) throws Exception{
        String key="kano";
        String message="こんにちわ みなさん";
        
        byte[] angou = AngouUtil.encrypt(key, message);
        String fukugou = AngouUtil.decrypt("kano", angou);
        
        System.out.println(message);
        System.out.println(angou);
        System.out.println(fukugou);
    }
    
    /**
     * 暗号化<br>
     * <hr>
     * 秘密鍵と文字列を受け取って，暗号化を施して，暗号バイト列を返す
     * メソッドです。<br>
     * <br>
     * <br>
     * 暗号関係のクラスは，統一的なインタフェースで暗号アルゴリズム
     * だけを切り替えて使えるようになっています。<br>
     * ここでの暗号化には，Blowfishアルゴリズムを使うことにします。<br>
     * <br>
     * 今回のサンプルは，すべて完全修飾クラス名（FQCN）で記述してあ
     * ります。<br>
     * リストからわかるように，javax.cryptoパッケージと
     * javax.crypto.specパッケージを利用します。<br>
     * 
     * 重要な行は「cipher.init」から2行です。<br>
     * データをdoFinalメソッドに一括して渡しているので，とても簡単になっています。<br>
     * 
     * 秘密鍵SecretKeySpecを作る行とcipherを作る行とで，
     * 暗号化アルゴリズム「Blowfish」を指定していることがわかると思います。<br>
     * これらのアルゴリズム名は大文字・小文字を区別しません。<br>
     *
     * @param key 公開鍵
     * @param text 暗号化するための文字列を指定します。
     * @return byte[] 暗号化されたバイト配列を返します。
     */
    public static byte[] encrypt(String key, String text)
    throws IOException {
        if( key == null || key.length() == 0 ){
            return null;
        }else if( text == null || text.length() == 0 ){
            return null;
        }
        
        try{
//			SecretKeyFactory keyFac = SecretKeyFactory.getInstance(Alogorithm);
//			DESedeKeySpec keySpec = new DESedeKeySpec(key.getBytes());
//			SecretKey sksSpec = keyFac.generateSecret(keySpec);
			
            SecretKeySpec sksSpec = new SecretKeySpec(key.getBytes(), Alogorithm);
            Cipher cipher = Cipher.getInstance(Alogorithm);
            cipher.init(Cipher.ENCRYPT_MODE, sksSpec);
            byte[] encrypted = cipher.doFinal(text.getBytes());
            
            return encrypted;
        }catch( IllegalBlockSizeException ibse ){
            String err = "ILLegalBlockSizeException occured." + LINE;
            err += ibse.getMessage();
            throw new IOException(err);
            
        }catch( InvalidKeyException ike ){
            String err = "ILLegalBlockSizeException occured." + LINE;
            err += ike.getMessage();
            throw new IOException(err);
            
        }catch( NoSuchAlgorithmException nae ){
            String err = "NoSuchAlgorithmException occured." + LINE;
            err += nae.getMessage();
            throw new IOException(err);
            
        }catch( BadPaddingException bpe ){
            String err = "BadPaddingException occured." + LINE;
            err += bpe.getMessage();
            throw new IOException(err);
            
        }catch( NoSuchPaddingException nspe ){
            String err = "NoSuchPaddingException occured." + LINE;
            err += nspe.getMessage();
            throw new IOException(err);
            
        }
        /*
        catch( InvalidKeySpecException ikse ){
			String err = "InvalidKeySpecException occured." + LINE;
			err += ikse.getMessage();
			throw new IOException(err);
        }
        */
    }
    
    /**
     * 復号化<br>
     * <hr>
     * 
     * 同様に，複合化の例はリスト2のようになります<br>
     * <br>
     * この例でも，重要な行は「cipher.init」から2行です。<br>
     * この例では，最終的に複合化したデータは，無条件でStringに変換して返します。<br>
     * <br>
     * 今回は，利用するアルゴリズムとしてBlowfishを利用しました。
     * 前述のようにこれらのAPIはBlowfish以外にも「DES」などいくつかあります。<br>
     * <br>
     * SecretKeySpecに与える暗号化アルゴリズム名に関してSecretKeySpecのJavadoc
     * を読んでいると，「Java暗号化アーキテクチャAPIの仕様およびリファレンスの付
     * 録Aを参照」と書いてあります。<br>
     * しかし，実際には「Java暗号化拡張機能（JCE）リファレンスガイドの付録A」の
     * ほうが適切でしょう。<br>
     * @param key 公開鍵
     * @param encrypted keyで暗号化されたバイト配列を指定します。
     * @return String 複合化された文字列を返します。
     * @throws IllegalBlockSizeException
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws UnsupportedEncodingException
     * @throws BadPaddingException
     * @throws NoSuchPaddingException
     */
    public static String decrypt(String key, byte[] encrypted)
    throws IOException {
        if( key == null || key.length() == 0 ){
            return null;
        }else if( encrypted == null || encrypted.length == 0 ){
            return null;
        }
        
        try{
            SecretKeySpec sksSpec = new SecretKeySpec( key.getBytes(), Alogorithm);
            Cipher cipher = Cipher.getInstance(Alogorithm);
            cipher.init(Cipher.DECRYPT_MODE, sksSpec);
            
            byte[] decrypted = cipher.doFinal(encrypted);
            
            return new String(decrypted);
        }catch( IllegalBlockSizeException ibse ){
            
            //パスワード直打ちの場合はそのまま返す
            return new String(encrypted);
        
        }catch( InvalidKeyException ike ){
            String err = "ILLegalBlockSizeException occured." + LINE;
            err += ike.getMessage();
            throw new IOException(err);
            
        }catch( NoSuchAlgorithmException nae ){
            String err = "NoSuchAlgorithmException occured." + LINE;
            err += nae.getMessage();
            throw new IOException(err);
        
        //公開鍵が不正な場合
        }catch( BadPaddingException bpe ){
            //String err = "BadPaddingException occured." + LINE;
            //err += bpe.getMessage();
            //throw new IOException(err);
            return null;
            
        }catch( NoSuchPaddingException nspe ){
            String err = "NoSuchPaddingException occured." + LINE;
            err += nspe.getMessage();
            throw new IOException(err);
            
        }
    }

}