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