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}