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