001/* 002 * jPOS Project [http://jpos.org] 003 * Copyright (C) 2000-2026 jPOS Software SRL 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jpos.security.jceadapter; 020 021import java.security.InvalidKeyException; 022import java.security.Key; 023import java.security.NoSuchAlgorithmException; 024import java.security.spec.AlgorithmParameterSpec; 025import java.util.Map; 026import java.util.concurrent.ConcurrentHashMap; 027 028import javax.crypto.Cipher; 029import javax.crypto.KeyGenerator; 030import javax.crypto.Mac; 031import javax.crypto.spec.IvParameterSpec; 032import javax.crypto.spec.SecretKeySpec; 033 034import org.jpos.iso.ISOUtil; 035import org.jpos.security.CipherMode; 036import org.jpos.security.SMAdapter; 037import org.jpos.security.Util; 038 039/** 040 * <p> 041 * Provides some higher level methods that are needed by the JCE Security Module, yet they are generic and can be used elsewhere. 042 * </p> 043 * <p> 044 * It depends on the Java<sup>TM</sup> Cryptography Extension (JCE). 045 * </p> 046 * 047 * @author Hani S. Kirollos 048 * @version $Revision$ $Date$ 049 */ 050@SuppressWarnings("unchecked") 051public class JCEHandler { 052 /** Default constructor; no instance state to initialise. */ 053 public JCEHandler() {} 054 static final String ALG_DES = "DES"; 055 static final String ALG_TRIPLE_DES = "DESede"; 056 static final String DES_MODE_ECB = "ECB"; 057 static final String DES_MODE_CBC = "CBC"; 058 static final String DES_NO_PADDING = "NoPadding"; 059 static final Map<MacEngineKey, Mac> macEngines = new ConcurrentHashMap(); 060 /** 061 * The JCE provider 062 */ 063 064 /** 065 * Generates a clear DES (DESede) key 066 * 067 * @param keyLength 068 * the bit length (key size) of the generated key (LENGTH_DES, LENGTH_DES3_2KEY or LENGTH_DES3_3KEY) 069 * @return generated clear DES (or DESede) key 070 * @exception JCEHandlerException if the underlying JCE provider cannot generate the key 071 */ 072 public Key generateDESKey(short keyLength) throws JCEHandlerException { 073 Key generatedClearKey = null; 074 try { 075 KeyGenerator k1; 076 if (keyLength > SMAdapter.LENGTH_DES) { 077 k1 = KeyGenerator.getInstance(ALG_TRIPLE_DES); 078 } else { 079 k1 = KeyGenerator.getInstance(ALG_DES); 080 } 081 generatedClearKey = k1.generateKey(); 082 /* 083 * These 3 steps not only enforce correct parity, but also enforces that when keyLength=128, the third key of the triple 084 * DES key is equal to the first key. This is needed because, JCE doesn't differenciate between Triple DES with 2 keys 085 * and Triple DES with 3 keys 086 */ 087 byte[] clearKeyBytes = extractDESKeyMaterial(keyLength, generatedClearKey); 088 Util.adjustDESParity(clearKeyBytes); 089 generatedClearKey = formDESKey(keyLength, clearKeyBytes); 090 } catch (Exception e) { 091 if (e instanceof JCEHandlerException) 092 throw (JCEHandlerException) e; 093 else 094 throw new JCEHandlerException(e); 095 } 096 return generatedClearKey; 097 } 098 099 /** 100 * Encrypts (wraps) a clear DES Key, it also sets odd parity before encryption 101 * 102 * @param keyLength 103 * bit length (key size) of the clear DES key (LENGTH_DES, LENGTH_DES3_2KEY or LENGTH_DES3_3KEY) 104 * @param clearDESKey 105 * DES/Triple-DES key whose format is "RAW" (for a DESede with 2 Keys, keyLength = 128 bits, while DESede key with 3 106 * keys keyLength = 192 bits) 107 * @param encryptingKey 108 * can be a key of any type (RSA, DES, DESede...) 109 * @return encrypted DES key 110 * @throws JCEHandlerException if key extraction or encryption fails 111 */ 112 public byte[] encryptDESKey(short keyLength, Key clearDESKey, Key encryptingKey) throws JCEHandlerException { 113 byte[] clearKeyBytes = extractDESKeyMaterial(keyLength, clearDESKey); 114 // enforce correct (odd) parity before encrypting the key 115 Util.adjustDESParity(clearKeyBytes); 116 return doCryptStuff(clearKeyBytes, encryptingKey, Cipher.ENCRYPT_MODE); 117 } 118 119 /** 120 * Extracts the DES/DESede key material 121 * 122 * @param keyLength 123 * bit length (key size) of the DES key. (LENGTH_DES, LENGTH_DES3_2KEY or LENGTH_DES3_3KEY) 124 * @param clearDESKey 125 * DES/Triple-DES key whose format is "RAW" 126 * @return encoded key material 127 * @throws JCEHandlerException if the key format/algorithm is unsupported 128 */ 129 protected byte[] extractDESKeyMaterial(short keyLength, Key clearDESKey) throws JCEHandlerException { 130 String keyAlg = clearDESKey.getAlgorithm(); 131 String keyFormat = clearDESKey.getFormat(); 132 if (keyFormat.compareTo("RAW") != 0) { 133 throw new JCEHandlerException("Unsupported DES key encoding format: " + keyFormat); 134 } 135 if (!keyAlg.startsWith(ALG_DES)) { 136 throw new JCEHandlerException("Unsupported key algorithm: " + keyAlg); 137 } 138 byte[] clearKeyBytes = clearDESKey.getEncoded(); 139 clearKeyBytes = ISOUtil.trim(clearKeyBytes, getBytesLength(keyLength)); 140 return clearKeyBytes; 141 } 142 143 /** 144 * Decrypts an encrypted DES/Triple-DES key 145 * 146 * @param keyLength 147 * bit length (key size) of the DES key to be decrypted. (LENGTH_DES, LENGTH_DES3_2KEY or LENGTH_DES3_3KEY) 148 * @param encryptedDESKey 149 * the byte[] representing the encrypted key 150 * @param encryptingKey 151 * can be of any algorithm (RSA, DES, DESede...) 152 * @param checkParity 153 * if true, the parity of the key is checked 154 * @return clear DES (DESede) Key 155 * @throws JCEHandlerException 156 * if checkParity==true and the key does not have correct parity 157 */ 158 public Key decryptDESKey(short keyLength, byte[] encryptedDESKey, Key encryptingKey, boolean checkParity) 159 throws JCEHandlerException { 160 byte[] clearKeyBytes = doCryptStuff(encryptedDESKey, encryptingKey, Cipher.DECRYPT_MODE); 161 if (checkParity && !Util.isDESParityAdjusted(clearKeyBytes)) { 162 throw new JCEHandlerException("Parity not adjusted"); 163 } 164 return formDESKey(keyLength, clearKeyBytes); 165 } 166 167 /** 168 * Forms the clear DES key given its "RAW" encoded bytes Does the inverse of extractDESKeyMaterial 169 * 170 * @param keyLength 171 * bit length (key size) of the DES key. (LENGTH_DES, LENGTH_DES3_2KEY or LENGTH_DES3_3KEY) 172 * @param clearKeyBytes 173 * the RAW DES/Triple-DES key 174 * @return clear key 175 * @throws JCEHandlerException if {@code keyLength} is not a supported DES key size 176 */ 177 protected Key formDESKey(short keyLength, byte[] clearKeyBytes) throws JCEHandlerException { 178 Key key = null; 179 switch (keyLength) { 180 case SMAdapter.LENGTH_DES: { 181 key = new SecretKeySpec(clearKeyBytes, ALG_DES); 182 } 183 break; 184 case SMAdapter.LENGTH_DES3_2KEY: { 185 // make it 3 components to work with JCE 186 clearKeyBytes = ISOUtil.concat(clearKeyBytes, 0, getBytesLength(SMAdapter.LENGTH_DES3_2KEY), clearKeyBytes, 0, 187 getBytesLength(SMAdapter.LENGTH_DES)); 188 } 189 case SMAdapter.LENGTH_DES3_3KEY: { 190 key = new SecretKeySpec(clearKeyBytes, ALG_TRIPLE_DES); 191 } 192 } 193 if (key == null) 194 throw new JCEHandlerException("Unsupported DES key length: " + keyLength + " bits"); 195 return key; 196 } 197 198 /** 199 * Encrypts data using ECB mode. 200 * 201 * @param data plaintext to encrypt 202 * @param key cipher key 203 * @return encrypted data 204 * @exception JCEHandlerException if the JCE cipher operation fails 205 */ 206 public byte[] encryptData(byte[] data, Key key) throws JCEHandlerException { 207 return doCryptStuff(data, key, Cipher.ENCRYPT_MODE); 208 } 209 210 /** 211 * Decrypts data using ECB mode. 212 * 213 * @param encryptedData ciphertext to decrypt 214 * @param key cipher key 215 * @return clear data 216 * @exception JCEHandlerException if the JCE cipher operation fails 217 */ 218 public byte[] decryptData(byte[] encryptedData, Key key) throws JCEHandlerException { 219 return doCryptStuff(encryptedData, key, Cipher.DECRYPT_MODE); 220 } 221 222 /** 223 * Encrypts data using CBC mode with the given IV. 224 * 225 * @param data plaintext to encrypt 226 * @param key cipher key 227 * @param iv 8 bytes initial vector 228 * @return encrypted data 229 * @exception JCEHandlerException if the JCE cipher operation fails 230 */ 231 public byte[] encryptDataCBC(byte[] data, Key key, byte[] iv) throws JCEHandlerException { 232 return doCryptStuff(data, key, Cipher.ENCRYPT_MODE, CipherMode.CBC, iv); 233 } 234 235 /** 236 * Decrypts data using CBC mode with the given IV. 237 * 238 * @param encryptedData ciphertext to decrypt 239 * @param key cipher key 240 * @param iv 8 bytes initial vector 241 * @return clear data 242 * @exception JCEHandlerException if the JCE cipher operation fails 243 */ 244 public byte[] decryptDataCBC(byte[] encryptedData, Key key, byte[] iv) throws JCEHandlerException { 245 return doCryptStuff(encryptedData, key, Cipher.DECRYPT_MODE, CipherMode.CBC, iv); 246 } 247 248 /** 249 * Performs cryptographic DES operations (en/de)cryption) in ECB mode using JCE Cipher. 250 * 251 * @param data input bytes 252 * @param key cipher key 253 * @param direction {@link Cipher#ENCRYPT_MODE} or {@link Cipher#DECRYPT_MODE}. 254 * @return result of the cryptographic operations 255 * @throws JCEHandlerException if the JCE cipher operation fails 256 */ 257 byte[] doCryptStuff(byte[] data, Key key, int direction) throws JCEHandlerException { 258 return doCryptStuff(data, key, direction, CipherMode.ECB, null); 259 } 260 261 /** 262 * Performs cryptographic operations (en/de)cryption using JCE Cipher. 263 * 264 * @param data 265 * @param key 266 * @param direction {@link Cipher#ENCRYPT_MODE} or {@link Cipher#DECRYPT_MODE}. 267 * @param cipherMode values specified by {@link CipherMode}. 268 * @param iv 8 bytes initial vector. After operation will contain new iv value. 269 * @return result of the cryptographic operations. 270 * @throws JCEHandlerException 271 */ 272 byte[] doCryptStuff(byte[] data, Key key, int direction 273 ,CipherMode cipherMode, byte[] iv) throws JCEHandlerException { 274 byte[] result; 275 String transformation = key.getAlgorithm(); 276 if (key.getAlgorithm().startsWith(ALG_DES)) { 277 transformation += "/" + cipherMode.name() + "/" + DES_NO_PADDING; 278 } 279 AlgorithmParameterSpec aps = null; 280 try { 281 Cipher c1 = Cipher.getInstance(transformation); 282 if (cipherMode != CipherMode.ECB) 283 aps = new IvParameterSpec(iv); 284 c1.init(direction, key, aps); 285 result = c1.doFinal(data); 286 if (cipherMode != CipherMode.ECB) 287 System.arraycopy(result, result.length-8, iv, 0, iv.length); 288 } catch (Exception e) { 289 throw new JCEHandlerException(e); 290 } 291 return result; 292 } 293 294 /** 295 * Calculates the length of key in bytes 296 * 297 * @param keyLength 298 * bit length (key size) of the DES key. (LENGTH_DES, LENGTH_DES3_2KEY or LENGTH_DES3_3KEY) 299 * @return keyLength/8 300 * @throws JCEHandlerException 301 * if unknown key length 302 */ 303 int getBytesLength(short keyLength) throws JCEHandlerException { 304 int bytesLength = 0; 305 switch (keyLength) { 306 case SMAdapter.LENGTH_DES: 307 bytesLength = 8; 308 break; 309 case SMAdapter.LENGTH_DES3_2KEY: 310 bytesLength = 16; 311 break; 312 case SMAdapter.LENGTH_DES3_3KEY: 313 bytesLength = 24; 314 break; 315 default: 316 throw new JCEHandlerException("Unsupported key length: " + keyLength + " bits"); 317 } 318 return bytesLength; 319 } 320 321 /** 322 * Helper method used for create or retrieve MAC algorithm from cache 323 * 324 * @param engine 325 * object identyifing MAC algorithm 326 * @return Initialized MAC algotithm 327 * @throws org.jpos.security.jceadapter.JCEHandlerException 328 */ 329 Mac assignMACEngine(MacEngineKey engine) throws JCEHandlerException { 330 if (macEngines.containsKey(engine)) { 331 return macEngines.get(engine); 332 } 333 // Initalize new MAC engine and store them in macEngines cache 334 Mac mac = null; 335 try { 336 mac = Mac.getInstance(engine.getMacAlgorithm()); 337 mac.init(engine.getMacKey()); 338 } catch (NoSuchAlgorithmException e) { 339 throw new JCEHandlerException(e); 340 } catch (InvalidKeyException e) { 341 throw new JCEHandlerException(e); 342 } 343 macEngines.put(engine, mac); 344 return mac; 345 } 346 347 /** 348 * Generates MAC (Message Message Authentication Code) for some data. 349 * 350 * @param data 351 * the data to be MACed 352 * @param kd 353 * the key used for MACing 354 * @param macAlgorithm 355 * MAC algorithm name suitable for {@link Mac#getInstance} 356 * @return the MAC 357 * @throws org.jpos.security.jceadapter.JCEHandlerException if the MAC algorithm cannot be assigned or evaluated 358 */ 359 public byte[] generateMAC(byte[] data, Key kd, String macAlgorithm) throws JCEHandlerException { 360 Mac mac = assignMACEngine(new MacEngineKey(macAlgorithm, kd)); 361 synchronized (mac) { 362 mac.reset(); 363 return mac.doFinal(data); 364 } 365 } 366 367 /** 368 * Class used for indexing MAC algorithms in cache 369 */ 370 protected static class MacEngineKey { 371 private final String macAlgorithm; 372 private final Key macKey; 373 374 /** 375 * Constructs a key for indexing a {@link Mac} engine in the cache. 376 * 377 * @param macAlgorithm MAC algorithm name 378 * @param macKey key used by the MAC engine 379 */ 380 protected MacEngineKey(String macAlgorithm, Key macKey) { 381 this.macAlgorithm = macAlgorithm; 382 this.macKey = macKey; 383 } 384 385 /** 386 * Returns the MAC algorithm name. 387 * 388 * @return MAC algorithm name 389 */ 390 public String getMacAlgorithm() { 391 return macAlgorithm; 392 } 393 394 /** 395 * Returns the MAC key. 396 * 397 * @return MAC key 398 */ 399 public Key getMacKey() { 400 return macKey; 401 } 402 403 @Override 404 public int hashCode() { 405 final int prime = 31; 406 int result = 1; 407 result = prime * result + (macAlgorithm == null ? 0 : macAlgorithm.hashCode()); 408 // Note: class Key does not redefine hashCode - therefore hash on Algorithm only or breaks java equals/hashCode contract 409 return result; 410 } 411 412 @Override 413 public boolean equals(Object obj) { 414 if (this == obj) { 415 return true; 416 } 417 if (obj == null) { 418 return false; 419 } 420 if (getClass() != obj.getClass()) { 421 return false; 422 } 423 MacEngineKey other = (MacEngineKey) obj; 424 if (macAlgorithm == null) { 425 if (other.macAlgorithm != null) 426 return false; 427 } else if (!macAlgorithm.equals(other.macAlgorithm)) { 428 return false; 429 } else if (macKey != other.macKey) { 430 // Note: class Key does not redefine equals or HashCode - therefore instance equality is all we can do 431 return false; 432 } 433 return true; 434 } 435 } 436}