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 * ここでの暗号化は、秘密キー方式でバイト文字列に変換されたものを、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         * 数字から16進文字に変換するテーブルです。
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        // 注意:秘密キーは、8の倍数でないといけない。
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 文字ですが、16進数アスキー文字列に変換
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     復号化を行う暗号化された16進数アスキー文字列
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         * バイト配列を16進数アスキー文字列に変換します。
166         *
167         * バイト配列を、2文字の0~9,a~fのアスキーに変換されます。
168         * これにより、すべての文字を、アスキー化できます。
169         * アスキー化で、上位が0F以下の場合でも、0 を出すことで、固定長に変換します。
170         *
171         * よって、入力バイトの2倍のlength()を持ったStringを作成します。
172         *
173         * @param       input バイト配列
174         *
175         * @return      16進数アスキー文字列
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         * 16進数アスキー文字列をバイト配列に変換します。
195         *
196         * 2文字の0~9,a~fのアスキー文字列を、バイト配列に変換されます。
197         *
198         * よって、入力Stringの1/2倍のlengthを持ったバイト配列を作成します。
199         *
200         * @param       input 16進数アスキー文字列
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で、16Byteのバイトに変換されますが、ここでは、16進数で文字列に変換しています。
224         *
225         * 変換方法は、各バイトの上位/下位を16進文字列に変換後、連結しています。
226         * これは、Tomcat等の digest 認証(MD5使用時)と同じ変換方式です。
227         * 連結後の文字列長は、32バイト(固定)になります。
228         *
229         * @og.rev 5.2.2.0 (2010/11/01) util.StringUtil から移動
230         *
231         * @param       input 変換前の文字列
232         *
233         * @return      MD5でハッシュした文字列。32バイト(固定)
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で、16Byteのバイトに変換されますが、ここでは、16進数で文字列に変換しています。
257         *
258         * 変換方法は、各バイトの上位/下位を16進文字列に変換後、連結しています。
259         * これは、Tomcat等の digest 認証(MD5使用時)と同じ変換方式です。
260         * 連結後の文字列長は、32バイト(固定)になります。
261         *
262         * @og.rev 5.7.2.1 (2014/01/17) Exceptionをまとめます。
263         *
264         * @param       input 変換前のFile
265         *
266         * @return      MD5でハッシュした文字列。32バイト(固定)
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         * 16進数で文字列に変換しています。
304         *
305         * 変換方法は、各バイトの上位/下位を16進文字列に変換後、連結しています。
306         * これは、Tomcat等の digest 認証と同じ変換方式です。
307         *
308         * @og.rev 5.9.27.1 (2010/12/08) 新規作成
309         *
310         * @param       input 変換前の文字列
311         *
312         * @return      SHA1でハッシュした文字列。32バイト(固定)
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}