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