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