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