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