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