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