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