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