    001/*
    002 * Copyright (c) 2009 The openGion Project.
    003 *
    004 * Licensed under the Apache License, Version 2.0 (the "License");
    005 * you may not use this file except in compliance with the License.
    006 * You may obtain a copy of the License at
    007 *
    008 *     http://www.apache.org/licenses/LICENSE-2.0
    009 *
    010 * Unless required by applicable law or agreed to in writing, software
    011 * distributed under the License is distributed on an "AS IS" BASIS,
    012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
    013 * either express or implied. See the License for the specific language
    014 * governing permissions and limitations under the License.
    015 */
    016package org.opengion.fukurou.security;
    017
    018import javax.crypto.spec.SecretKeySpec;
    019import javax.crypto.Cipher;
    020
    021import java.security.MessageDigest;
    022import java.security.NoSuchAlgorithmException;
    023import java.security.GeneralSecurityException;  // 5.7.2.1 (2014/01/17)
    024
    025import java.nio.charset.Charset;                                // 5.5.2.6 (2012/05/25)
    026import java.nio.channels.FileChannel;                   // 5.7.2.1 (2014/01/17)
    027import java.nio.ByteBuffer;                                             // 5.5.2.6 (2012/05/25)
    028
    029import java.io.File;
    030import java.io.FileInputStream;
    031import java.io.IOException;
    032
    033import org.opengion.fukurou.util.Closer;                // 5.5.2.6 (2012/05/25)
    034
    035/**
    036 * HybsCryptography は、セキュリティ強化の為の Hybs独自の暗号化クラスです。
    037 *
    038 * このクラスは、暗号化キーを受け取り、それに基づいて暗号化/復号化を行います。
    039 * ここでの暗号化は、秘密キー方式でバイト文字列に変換されたものを、１６進アスキー文字に
    040 * 直して、扱っています。よって、暗号化/復号化共に、文字列として扱うことが可能です。
    041 *
    042 * @og.rev 4.0.0.0 (2005/08/31) 新規追加
    043 * @og.group ライセンス管理
    044 *
    045 * @version  4.0
    046 * @author   Kazuhiko Hasegawa
    047 * @since    JDK5.0,
    048 */
    049public final class HybsCryptography {
    050        private final SecretKeySpec sksSpec ;
    051        private static final String CIPHER_TYPE = "Blowfish" ;
    052
    053        /**
    054         * 数字から１６進文字に変換するテーブルです。
    055         */
    056        private static final char[] HEXA_DECIMAL =
    057                { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    058                  'a', 'b', 'c', 'd', 'e', 'f' };
    059
    060        /**
    061         * プラットフォーム依存のデフォルトの Charset です。
    062         * プラットフォーム依存性を考慮する場合、エンコード指定で作成しておく事をお勧めします。
    063         *
    064         * @og.rev 5.5.2.6 (2012/05/25) findbugs対応
    065         */
    066        private static final Charset DEFAULT_CHARSET = Charset.defaultCharset() ;
    067
    068        // 注意：秘密キーは、８の倍数でないといけない。
    069        private static final String HYBS_CRYPT_KEY = "2a5a88891d37ae59" ;
    070
    071        /**
    072         * 内部設定の秘密鍵を使用して，暗号化を行うオブジェクトを構築します。
    073         * ここでの暗号化は、Java標準のセキュリティパッケージを使用しています。
    074         */
    075        public HybsCryptography() {
    076                sksSpec = new SecretKeySpec( HYBS_CRYPT_KEY.getBytes( DEFAULT_CHARSET ), CIPHER_TYPE );         // 5.5.2.6 (2012/05/25) findbugs対応
    077        }
    078
    079        /**
    080         * 秘密鍵の文字列を受け取って，暗号化を行うオブジェクトを構築します。
    081         * ここでの暗号化は、Java標準のセキュリティパッケージを使用しています。
    082         * 秘密鍵のサイズを、8 の倍数 (32 以上 448 以下) にする必要があります。
    083         *
    084         * @param       cryptKey        暗号化を行う秘密鍵
    085         */
    086        public HybsCryptography( final String cryptKey ) {
    087                sksSpec = new SecretKeySpec( cryptKey.getBytes( DEFAULT_CHARSET ), CIPHER_TYPE );               // 5.5.2.6 (2012/05/25) findbugs対応
    088        }
    089
    090        /**
    091         * セキュリティカラムのDBTyepに対してHybs独自の暗号化を行います。
    092         * 暗号化されたデータは、通常 byte 文字ですが、１６進数アスキー文字列に変換
    093         * したものを返します。
    094         * この暗号化では、引数が null の場合は、ゼロ文字列を返します。
    095         *
    096         * @og.rev 5.7.2.1 (2014/01/17) Exceptionをまとめます。
    097         *
    098         * @param       org     暗号化を行う元の文字列
    099         *
    100         * @return      暗号化された文字列(HEXADECIMAL化)
    101         */
    102        public String encrypt( final String org ) {
    103                if( org == null || org.length() == 0 ) { return ""; }
    104
    105                try {
    106                        Cipher cipher = Cipher.getInstance( CIPHER_TYPE );
    107                        cipher.init( Cipher.ENCRYPT_MODE, sksSpec );
    108                        byte[] encrypted = cipher.doFinal( org.getBytes( DEFAULT_CHARSET ) );           // 5.5.2.6 (2012/05/25) findbugs対応
    109
    110                        return byte2hexa( encrypted );
    111                }
    112                // 5.7.2.1 (2014/01/17) Exceptionをまとめます。
    113                catch( GeneralSecurityException ex ) {
    114                        String errMsg = "暗号化処理に失敗しました。[" + org + "]"
    115                                                        + ex.getMessage() ;
    116                        throw new RuntimeException( errMsg,ex );
    117                }
    118        }
    119
    120        /**
    121         * セキュリティカラムのDBTyepに対してHybs独自の復号化を行います。
    122         * ここでの復号化は、encrypt で暗号化された文字を戻す場合に使用します。
    123         * この復号化では、null は復号化できないため、ゼロ文字列を返します。
    124         *
    125         * @og.rev 5.7.2.1 (2014/01/17) Exceptionをまとめます。
    126         *
    127         * @param       hex     復号化を行う暗号化された１６進数アスキー文字列
    128         *
    129         * @return      復号化された元の文字列
    130         */
    131        public String decrypt( final String hex ) {
    132                if( hex == null || hex.length() == 0 ) { return ""; }
    133
    134                try {
    135                        Cipher cipher = Cipher.getInstance( CIPHER_TYPE );
    136                        cipher.init( Cipher.DECRYPT_MODE, sksSpec );
    137                        byte[] encrypted = hexa2byte( hex );
    138                        byte[] decrypted = cipher.doFinal( encrypted );
    139                        return new String( decrypted,DEFAULT_CHARSET );         // 5.5.2.6 (2012/05/25) findbugs対応
    140                }
    141                // 5.7.2.1 (2014/01/17) Exceptionをまとめます。
    142                catch( GeneralSecurityException ex ) {
    143                        String errMsg = "復号化処理に失敗しました。[" + hex + "]"
    144                                                        + ex.getMessage() ;
    145                        throw new RuntimeException( errMsg,ex );
    146                }
    147        }
    148
    149        /**
    150         * バイト配列を１６進数アスキー文字列に変換します。
    151         *
    152         * バイト配列を、２文字の0～9,a～ｆのアスキーに変換されます。
    153         * これにより、すべての文字を、アスキー化できます。
    154         * アスキー化で、上位が0F以下の場合でも、0 を出すことで、固定長に変換します。
    155         *
    156         * よって、入力バイトの２倍のlength()を持ったStringを作成します。
    157         *
    158         * @param       input バイト配列
    159         *
    160         * @return      １６進数アスキー文字列
    161         */
    162        public static String byte2hexa( final byte[] input ) {
    163                String rtn = null;
    164                if( input != null ) {
    165                        int len = input.length ;
    166                        char[] ch = new char[len*2];
    167                        for( int i=0; i<len; i++ ) {
    168                                int high = (input[i] & 0xf0) >> 4 ;
    169                                int low  = input[i] & 0x0f ;
    170                                ch[i*2]   = HEXA_DECIMAL[high];
    171                                ch[i*2+1] = HEXA_DECIMAL[low];
    172                        }
    173                        rtn =  new String(ch);
    174                }
    175                return rtn;
    176        }
    177
    178        /**
    179         * １６進数アスキー文字列をバイト配列に変換します。
    180         *
    181         * ２文字の0～9,a～ｆのアスキー文字列を、バイト配列に変換されます。
    182         *
    183         * よって、入力Stringの１/２倍のlengthを持ったバイト配列を作成します。
    184         *
    185         * @param       input １６進数アスキー文字列
    186         *
    187         * @return      バイト配列
    188         */
    189        public static byte[] hexa2byte( final String input ) {
    190                byte[] rtn = null;
    191                if( input != null ) {
    192                        int len = input.length() ;
    193                        rtn = new byte[len/2];
    194                        for( int i=0; i<len/2; i++ ) {
    195                                char ch = input.charAt( i*2 );
    196                                int high = ( ch < 'a' ) ? ch-'0' : ch-'a'+10 ;
    197                                ch = input.charAt( i*2+1 );
    198                                int low  = ( ch < 'a' ) ? ch-'0' : ch-'a'+10 ;
    199                                rtn[i] = (byte)(high << 4 | low);
    200                        }
    201                }
    202                return rtn;
    203        }
    204
    205        /**
    206         * MessageDigestにより、MD5 でハッシュした文字に変換します。
    207         *
    208         * MD5で、１６Byteのバイトに変換されますが、ここでは、１６進数で文字列に変換しています。
    209         *
    210         * 変換方法は、各バイトの上位/下位を１６進文字列に変換後、連結しています。
    211         * これは、Tomcat等の digest 認証(MD5使用時)と同じ変換方式です。
    212         * 連結後の文字列長は、３２バイト(固定)になります。
    213         *
    214         * @og.rev 5.2.2.0 (2010/11/01) util.StringUtil から移動
    215         *
    216         * @param       input 変換前の文字列
    217         *
    218         * @return      MD5でハッシュした文字列。３２バイト(固定)
    219         */
    220        public static String getMD5( final String input ) {
    221                String rtn = null;
    222                if( input != null ) {
    223                        try {
    224                                MessageDigest md5 = MessageDigest.getInstance( "MD5" );
    225                                md5.update( input.getBytes( DEFAULT_CHARSET ) );        // 5.5.2.6 (2012/05/25) findbugs対応
    226                                byte[] out = md5.digest();
    227                                rtn = byte2hexa( out );
    228                        }
    229                        catch( NoSuchAlgorithmException ex ) {
    230                                String errMsg = "MessageDigestで失敗しました。[" + input + "]"
    231                                                        + ex.getMessage() ;
    232                                throw new RuntimeException( errMsg,ex );
    233                        }
    234                }
    235                return rtn;
    236        }
    237
    238        /**
    239         * MessageDigestにより、MD5 でハッシュした文字に変換します。
    240         *
    241         * MD5で、１６Byteのバイトに変換されますが、ここでは、１６進数で文字列に変換しています。
    242         *
    243         * 変換方法は、各バイトの上位/下位を１６進文字列に変換後、連結しています。
    244         * これは、Tomcat等の digest 認証(MD5使用時)と同じ変換方式です。
    245         * 連結後の文字列長は、３２バイト(固定)になります。
    246         *
    247         * @og.rev 5.7.2.1 (2014/01/17) Exceptionをまとめます。
    248         *
    249         * @param       input 変換前のFile
    250         *
    251         * @return      MD5でハッシュした文字列。３２バイト(固定)
    252         */
    253        public static String getMD5( final File input ) {
    254                String rtn = null;
    255                if( input != null ) {
    256                        FileInputStream fis     = null;
    257                        FileChannel             fc      = null;
    258                        try {
    259                                MessageDigest md5 = MessageDigest.getInstance( "MD5" );
    260                                fis = new FileInputStream( input );
    261                                fc  =fis.getChannel();
    262                                ByteBuffer bb = fc.map( FileChannel.MapMode.READ_ONLY , 0L , fc.size() );
    263                                md5.update( bb );
    264                                byte[] out = md5.digest();
    265                                rtn = byte2hexa( out );
    266                        }
    267                        catch( NoSuchAlgorithmException ex ) {
    268                                String errMsg = "MessageDigestで MD5 インスタンスの作成に失敗しました。[" + input + "]"
    269                                                        + ex.getMessage() ;
    270                                throw new RuntimeException( errMsg,ex );
    271                        }
    272                        catch( IOException ex ) {
    273                                String errMsg = "ファイルの読み取りを失敗しました。[" + input + "]"
    274                                                        + ex.getMessage() ;
    275                                throw new RuntimeException( errMsg,ex );
    276                        }
    277                        finally {
    278                                Closer.ioClose( fc );
    279                                Closer.ioClose( fis );
    280                        }
    281                }
    282                return rtn;
    283        }
    284
    285        /**
    286         * 暗号化のテストを行う為のメインメソッド
    287         *
    288         * java HybsCryptography KEY TEXT で起動します。
    289         *   KEY  : 秘密鍵(8 の倍数 (32 以上 448 以下)文字)
    290         *   TEXT : 変換する文字列
    291         *
    292         * @og.rev 5.2.2.0 (2010/11/01) 循環参照の解消(LogWriter 削除)
    293         *
    294         * @param       args    引数配列
    295         */
    296        public static void main( final String[] args ) {
    297                if( args.length != 2 ) {
    298                        System.out.println( "java HybsCryptography KEY TEXT" );
    299                        System.out.println( "  KEY  : 秘密鍵(8 の倍数 (32 以上 448 以下)文字)" );
    300                        System.out.println( "  TEXT : 変換する文字列" );
    301                        return;
    302                }
    303
    304                HybsCryptography cript = new HybsCryptography( args[0] );
    305
    306                System.out.println( "IN   TEXT : " + args[1] );
    307
    308                String hexa = cript.encrypt( args[1] );
    309                System.out.println( "HEXA TEXT : " + hexa );
    310
    311                String data = cript.decrypt( hexa );
    312                System.out.println( "OUT  DATA : " + data );
    313        }
    314}