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