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 org.javatuples.Pair;
022import org.jpos.core.Configuration;
023import org.jpos.core.ConfigurationException;
024import org.jpos.core.Environment;
025import org.jpos.iso.ISODate;
026import org.jpos.iso.ISOException;
027import org.jpos.iso.ISOUtil;
028import org.jpos.security.*;
029import org.jpos.util.LogEvent;
030import org.jpos.util.Logger;
031import org.jpos.util.SimpleMsg;
032
033import javax.crypto.SecretKey;
034import java.io.BufferedInputStream;
035import java.io.BufferedOutputStream;
036import java.io.File;
037import java.io.FileInputStream;
038import java.io.FileOutputStream;
039import java.io.InputStream;
040import java.io.OutputStream;
041import java.nio.ByteBuffer;
042import java.security.*;
043import java.util.*;
044import java.util.regex.Matcher;
045import java.util.regex.Pattern;
046import javax.crypto.Cipher;
047
048/**
049 * JCESecurityModule is an implementation of a security module in software.
050 * <p>
051 * It doesn't require any hardware device to work.<br>
052 * JCESecurityModule also implements the SMAdapter, so you can view it: either
053 * as a self contained security module adapter that doesn't need a security module
054 * or a security module that plugs directly to jpos, so doesn't need
055 * a separate adapter.<br>
056 * It relies on Java(tm) Cryptography Extension (JCE), hence its name.<br>
057 * JCESecurityModule relies on the JCEHandler class to do the low level JCE work.
058 * <p>
059 * WARNING: This version of JCESecurityModule is meant for testing purposes and
060 * NOT for life operation, since the Local Master Keys are stored in CLEAR on
061 * the system's disk. Comming versions of JCESecurity Module will rely on
062 * java.security.KeyStore for a better protection of the Local Master Keys.
063 *
064 * @author Hani Samuel Kirollos
065 * @author Robert Demski
066 * @version $Revision$ $Date$
067 */
068@SuppressWarnings("unchecked")
069public class JCESecurityModule extends BaseSMAdapter<SecureDESKey> {
070    /**
071     * Pattern representing key type string value.
072     */
073    private static final Pattern KEY_TYPE_PATTERN = Pattern.compile("([^:;]*)([:;])?([^:;])?([^:;])?");
074
075    /**
076     * Pattern for split two clear pins.
077     */
078    private static final Pattern SPLIT_PIN_PATTERN = Pattern.compile("[ :;,/.]");
079
080    /**
081     * NUmber of LMK pairs
082     */
083    private final static int LMK_PAIRS_NO = 0xe;
084
085    /**
086     * LMK variants appled to first byte of LMK pair
087     */
088    private final static int[] variants = {
089        0x00, 0xa6, 0x5a, 0x6a, 0xde, 0x2b, 0x50, 0x74, 0x9c,0xfa
090    };
091
092    /**
093     * LMK scheme variants appiled to first byte of second key in pair
094     */
095    private final static int[] schemeVariants = {
096        0x00, 0xa6, 0x5a, 0x6a, 0xde, 0x2b
097    };
098
099    /**
100     * Index of scheme variant for left LMK key for double length keys
101     */
102    private final static int KEY_U_LEFT      = 1;
103    /**
104     * Index of scheme variant for right LMK key for double length keys
105     */
106    private final static int KEY_U_RIGHT     = 2;
107    /**
108     * Index of scheme variant for left LMK key for triple length keys
109     */
110    private final static int KEY_T_LEFT      = 3;
111    /**
112     * Index of scheme variant for middle LMK key for triple length keys
113     */
114    private final static int KEY_T_MEDIUM    = 4;
115    /**
116     * Index of scheme variant for right LMK key for triple length keys
117     */
118    private final static int KEY_T_RIGHT     = 5;
119
120    private static MessageDigest SHA1_MESSAGE_DIGEST = null;
121
122    private static final byte[] _VARIANT_RIGHT_HALF = new byte[]
123            {
124                    (byte) 0xC0, (byte) 0xC0, (byte) 0xC0, (byte) 0xC0,
125                    (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
126            };
127
128    static {
129      try {
130          SHA1_MESSAGE_DIGEST = MessageDigest.getInstance("SHA-1");
131      } catch (NoSuchAlgorithmException ex) {} //NOPMD: SHA-1 is a standard digest
132    }
133
134    /**
135     * Creates an uninitialized JCE Security Module, you need to setConfiguration to initialize it
136     */
137    public JCESecurityModule () {
138        super();
139    }
140
141    /**
142     * Creates a JCE Security Module initialised with the given LMK file.
143     * @param lmkFile Local Master Keys filename of the JCE Security Module
144     * @throws SMException on initialisation failure
145     */
146    public JCESecurityModule (String lmkFile) throws SMException
147    {
148        Objects.requireNonNull(lmkFile);
149        init(null, lmkFile, false);
150    }
151
152    /**
153     * Constructs a JCE security module backed by the given LMK file and JCE provider.
154     *
155     * @param lmkFile path to the Local Master Keys file
156     * @param jceProviderClassName fully qualified JCE provider class name
157     * @throws SMException if initialization fails
158     */
159    public JCESecurityModule (String lmkFile, String jceProviderClassName) throws SMException
160    {
161        Objects.requireNonNull(lmkFile);
162        init(jceProviderClassName, lmkFile, false);
163    }
164
165    /**
166     * Constructs a JCE security module from a {@link Configuration}, logger, and realm.
167     *
168     * @param cfg configuration to apply (see {@link #setConfiguration(Configuration)})
169     * @param logger logger to bind
170     * @param realm logger realm
171     * @throws ConfigurationException if the configuration is invalid
172     */
173    public JCESecurityModule (Configuration cfg, Logger logger, String realm) throws ConfigurationException
174    {
175        setLogger(logger, realm);
176        setConfiguration(cfg);
177    }
178
179    /**
180     * Configures a JCESecurityModule
181     * @param cfg The following properties are read:<br>
182     *    lmk: Local Master Keys file (The only required parameter)<br>
183     *    jce: JCE Provider Class Name, if not provided, it defaults to: com.sun.crypto.provider.SunJCE<br>
184     *    rebuildlmk: (true/false), rebuilds the Local Master Keys file with new keys (WARNING: old keys will be erased)<br>
185     *    cbc-mac: Cipher Block Chaining MAC algorithm name for given JCE Provider.<br>
186     *             Default is ISO9797ALG3MACWITHISO7816-4PADDING from BouncyCastle provider (known as Retail-MAC)<br>
187     *             that is suitable for most of interfaces with double length MAC key<br>
188     *             ANSI X9.19 aka ISO/IEC 9797-1 MAC algorithm 3 padding method 2 - ISO7816<br>
189     *    ede-mac: Encrypt Decrypt Encrypt MAC algorithm name for given JCE Provider.<br>
190     *             Default is DESEDEMAC from BouncyCastle provider<br>
191     *             that is suitable for BASE24 with double length MAC key<br>
192     *             ANSI X9.19<br>
193     * @throws ConfigurationException if configuration is invalid
194     */
195    @Override
196    public void setConfiguration (Configuration cfg) throws ConfigurationException {
197        super.setConfiguration(cfg);
198        try {
199            init(cfg.get("provider"), cfg.get("lmk", null), cfg.getBoolean("rebuildlmk"));
200        } catch (SMException e) {
201            throw  new ConfigurationException(e);
202        }
203    }
204
205    @Override
206    public SecureDESKey generateKeyImpl (short keyLength, String keyType) throws SMException {
207        Key generatedClearKey = jceHandler.generateDESKey(keyLength);
208        return encryptToLMK(keyLength, keyType, generatedClearKey);
209    }
210
211    @Override
212    public SecureDESKey importKeyImpl (short keyLength, String keyType, byte[] encryptedKey,
213            SecureDESKey kek, boolean checkParity) throws SMException {
214        // decrypt encrypted key
215        Key clearKEY = jceHandler.decryptDESKey(keyLength, encryptedKey, decryptFromLMK(kek),
216                checkParity);
217        // Encrypt Key under LMK
218        return encryptToLMK(keyLength, keyType, clearKEY);
219    }
220
221    @Override
222    public byte[] exportKeyImpl (SecureDESKey key, SecureDESKey kek) throws SMException {
223        // get key in clear
224        Key clearKey = decryptFromLMK(key);
225        // Encrypt key under kek
226        return jceHandler.encryptDESKey(key.getKeyLength(), clearKey, decryptFromLMK(kek));
227    }
228
229    private int getKeyTypeIndex (short keyLength, String keyType) throws SMException {
230        int index;
231        if (keyType==null)
232            return 0;
233        String majorType = getMajorType(keyType);
234        if (!keyTypeToLMKIndex.containsKey(majorType))
235            throw new SMException("Unsupported key type: " + majorType);
236        index = keyTypeToLMKIndex.get(majorType);
237        index |= getVariant(keyType) << 8;
238        return index;
239    }
240
241    private static String getMajorType (String keyType) {
242        Matcher m = KEY_TYPE_PATTERN.matcher(keyType);
243        m.find();
244        if (m.group(1) != null)
245            return m.group(1);
246        throw new IllegalArgumentException("Missing key type");
247    }
248
249    private static int getVariant (String keyType) {
250        int variant = 0;
251        Matcher m = KEY_TYPE_PATTERN.matcher(keyType);
252        m.find();
253        if (m.group(3) != null)
254            try {
255                variant = Integer.valueOf(m.group(3));
256            } catch (NumberFormatException ex){
257                throw new NumberFormatException("Value "+m.group(4)+" is not valid key variant");
258            }
259        return variant;
260    }
261
262    private static KeyScheme getScheme (int keyLength, String keyType) {
263        KeyScheme scheme = KeyScheme.Z;
264        switch (keyLength){
265            case SMAdapter.LENGTH_DES:
266                scheme = KeyScheme.Z; break;
267            case SMAdapter.LENGTH_DES3_2KEY:
268                scheme = KeyScheme.X; break;
269            case SMAdapter.LENGTH_DES3_3KEY:
270                scheme = KeyScheme.Y; break;
271        }
272        if (keyType==null)
273            return scheme;
274        Matcher m = KEY_TYPE_PATTERN.matcher(keyType);
275        m.find();
276        if (m.group(4) != null)
277            try {
278                scheme = KeyScheme.valueOf(m.group(4));
279            } catch (IllegalArgumentException ex){
280                throw new IllegalArgumentException("Value "+m.group(4)+" is not valid key scheme");
281            }
282        return scheme;
283    }
284
285    @Override
286    public EncryptedPIN encryptPINImpl (String pin, String accountNumber) throws SMException {
287        byte[] clearPINBlock = calculatePINBlock(pin, FORMAT00, accountNumber);
288        // Encrypt
289        byte[] translatedPINBlock = jceHandler.encryptData(clearPINBlock, getLMK(PINLMKIndex));
290        return new EncryptedPIN(translatedPINBlock, FORMAT00, accountNumber, false);
291    }
292
293    @Override
294    protected EncryptedPIN encryptPINImpl(String pin, String accountNumber, SecureDESKey pek) throws SMException {
295        byte[] clearPINBlock = calculatePINBlock(pin, FORMAT00, accountNumber);
296        Key clearPEK = decryptFromLMK(pek);
297        byte[] translatedPINBlock = jceHandler.encryptData(clearPINBlock, clearPEK);
298        return new EncryptedPIN(translatedPINBlock, FORMAT00, accountNumber, false);
299    }
300
301    @Override
302    public String decryptPINImpl (EncryptedPIN pinUnderLmk) throws SMException {
303        byte[] clearPINBlock = jceHandler.decryptData(pinUnderLmk.getPINBlock(),
304                getLMK(PINLMKIndex));
305        return calculatePIN(clearPINBlock, pinUnderLmk.getPINBlockFormat(), pinUnderLmk.getAccountNumber());
306    }
307
308    @Override
309    public EncryptedPIN importPINImpl (EncryptedPIN pinUnderKd1, SecureDESKey kd1) throws SMException {
310        // read inputs
311        String accountNumber = pinUnderKd1.getAccountNumber();
312        // Use FORMAT00 for encrypting PIN under LMK
313        byte destinationPINBlockFormat = FORMAT00;
314        // get clear PIN
315        byte[] clearPINBlock = jceHandler.decryptData(pinUnderKd1.getPINBlock(),
316                decryptFromLMK(kd1));
317        // extract clear pin (as entered by card holder)
318        String pin = calculatePIN(clearPINBlock, pinUnderKd1.getPINBlockFormat(),
319                accountNumber);
320        // Format PIN Block using proprietary FORMAT00 to be encrypetd under LMK
321        clearPINBlock = calculatePINBlock(pin, destinationPINBlockFormat, accountNumber);
322        // encrypt PIN
323        byte[] translatedPINBlock = jceHandler.encryptData(clearPINBlock, getLMK(PINLMKIndex));
324        return new EncryptedPIN(translatedPINBlock, destinationPINBlockFormat,
325                accountNumber, false);
326    }
327
328    @Override
329    public EncryptedPIN exportPINImpl (EncryptedPIN pinUnderLmk, SecureDESKey kd2,
330            byte destinationPINBlockFormat) throws SMException {
331        String accountNumber = pinUnderLmk.getAccountNumber();
332        // process
333        // get clear PIN
334        byte[] clearPINBlock = jceHandler.decryptData(pinUnderLmk.getPINBlock(),
335                getLMK(PINLMKIndex));
336        // extract clear pin
337        String pin = calculatePIN(clearPINBlock, pinUnderLmk.getPINBlockFormat(),
338                accountNumber);
339        clearPINBlock = calculatePINBlock(pin, destinationPINBlockFormat, accountNumber);
340        // encrypt PIN
341        byte[] translatedPINBlock = jceHandler.encryptData(clearPINBlock, decryptFromLMK(kd2));
342        return new EncryptedPIN(translatedPINBlock, destinationPINBlockFormat,
343                accountNumber, false);
344    }
345
346    @Override
347    public EncryptedPIN generatePINImpl(String accountNumber, int pinLen, List<String> excludes)
348            throws SMException {
349        if (excludes==null)
350          excludes = Arrays.asList();
351        String pin;
352        {
353          Random rd = new SecureRandom();
354          int max = (int)Math.pow(10, Math.min(pinLen, 9));
355          int max2 = (int)Math.pow(10, Math.max(pinLen - 9,0));
356          do {
357            long pinl = rd.nextInt(max);
358            if (pinLen > 9){
359              pinl *= max2;
360              pinl += rd.nextInt(max2);
361            }
362            pin = ISOUtil.zeropad(pinl, pinLen);
363          } while (excludes.contains(pin));
364        }
365
366        return encryptPINImpl(pin, accountNumber);
367    }
368
369    /**
370     * Visa way to decimalize data block
371     * @param b
372     * @return decimalized data string
373     */
374    private static String decimalizeVisa(byte[] b){
375        char[] bec = ISOUtil.hexString(b).toUpperCase().toCharArray();
376        char[] bhc = new char[bec.length];
377        int k = 0;
378        //Select 0-9 chars
379        for ( char c : bec )
380          if (c<'A')
381            bhc[k++] = c;
382        //Select A-F chars and map them to 0-5
383        char adjust = 'A'-'0';
384        for ( char c : bec )
385          if (c>='A')
386            bhc[k++] = (char) (c-adjust);
387        return new String(bhc);
388    }
389
390    /**
391     * Concatenates two single-length DES keys into a double-length DESede key,
392     * decrypting them from the LMK first. If either non-null input is already
393     * longer than single-length, decrypts and returns that key.
394     *
395     * @param keyA first secured key, may be {@code null}
396     * @param keyB second secured key, may be {@code null}
397     * @return a clear-text DES/DESede key, or {@code null} if both inputs are {@code null}
398     * @throws SMException if key decryption or assembly fails
399     */
400    protected Key concatKeys(SecureDESKey keyA, SecureDESKey keyB)
401            throws SMException {
402        if ( keyA!=null && keyA.getKeyLength()==SMAdapter.LENGTH_DES
403          && keyB!=null && keyB.getKeyLength()==SMAdapter.LENGTH_DES) {
404          Key cvkAclear = decryptFromLMK(keyA);
405          Key cvkBclear = decryptFromLMK(keyB);
406          return jceHandler.formDESKey(SMAdapter.LENGTH_DES3_2KEY
407                  ,ISOUtil.concat(cvkAclear.getEncoded(), cvkBclear.getEncoded()));
408        }
409        if (keyA!=null && keyA.getKeyLength()!=SMAdapter.LENGTH_DES)
410          return decryptFromLMK(keyA);
411        if (keyB!=null && keyB.getKeyLength()!=SMAdapter.LENGTH_DES)
412          return decryptFromLMK(keyB);
413        return null;
414    }
415
416    /**
417     * Calculates a CVV using the supplied clear CVK and an expiry {@link Date}
418     * formatted as {@code yyMM}.
419     *
420     * @param accountNo cardholder primary account number
421     * @param cvk clear CVK
422     * @param expDate expiry date
423     * @param serviceCode 3-digit service code
424     * @return the 3-digit CVV
425     * @throws SMException if the cipher operation fails
426     */
427    protected String calculateCVV(String accountNo, Key cvk, Date expDate,
428                                String serviceCode) throws SMException {
429        String ed = ISODate.formatDate(expDate, "yyMM");
430        return calculateCVD(accountNo, cvk, ed, serviceCode);
431    }
432
433    /**
434     * Calculates a CVD/CVV from the supplied clear CVK using the Visa algorithm.
435     *
436     * @param accountNo cardholder primary account number
437     * @param cvk clear CVK
438     * @param expDate expiry date as {@code yyMM}
439     * @param serviceCode 3-digit service code
440     * @return the 3-digit CVD/CVV
441     * @throws SMException if the cipher operation fails
442     */
443    protected String calculateCVD(String accountNo, Key cvk, String expDate,
444                                String serviceCode) throws SMException {
445        Key udka = jceHandler.formDESKey(SMAdapter.LENGTH_DES
446                ,Arrays.copyOfRange(cvk.getEncoded(), 0, 8));
447
448        byte[] block = ISOUtil.hex2byte(
449                ISOUtil.zeropadRight(accountNo
450                    + expDate
451                    + serviceCode, 32));
452        byte[] ba = Arrays.copyOfRange(block, 0, 8);
453        byte[] bb = Arrays.copyOfRange(block, 8,16);
454
455        //Encrypt ba with udka
456        byte[] bc = jceHandler.encryptData(ba, udka);
457        byte[] bd = ISOUtil.xor(bc, bb);
458        //Encrypt bd Tripple DES
459        byte[] be = jceHandler.encryptData(bd, cvk);
460        return decimalizeVisa(be).substring(0,3);
461    }
462
463    @Override
464    protected String calculateCVVImpl(String accountNo, SecureDESKey cvkA, SecureDESKey cvkB,
465                                   Date expDate, String serviceCode) throws SMException {
466        return calculateCVV(accountNo,concatKeys(cvkA, cvkB),expDate,serviceCode);
467    }
468
469    @Override
470    protected String calculateCVDImpl(String accountNo, SecureDESKey cvkA, SecureDESKey cvkB,
471                                   String expDate, String serviceCode) throws SMException {
472        return calculateCVD(accountNo, concatKeys(cvkA, cvkB), expDate, serviceCode);
473    }
474
475    /**
476     * Validates the lengths of the CAVV inputs, throwing {@link SMException}
477     * with a descriptive message when any value is {@code null} or wrong-sized.
478     *
479     * @param upn 4-digit unpredictable number
480     * @param authrc 1-character authorization result code
481     * @param sfarc 2-character second-factor authorization result code
482     * @throws SMException if any argument is {@code null} or has the wrong length
483     */
484    protected void checkCAVVArgs(String upn, String authrc, String sfarc)
485            throws SMException {
486        if (upn == null)
487            throw new SMException("Unpredictable Number can not be null");
488        if (authrc == null)
489            throw new SMException("Authorization Result Code can not be null");
490        if (sfarc == null)
491            throw new SMException("Secend Factor Authorization Result Code"
492                    + " can not be null");
493        if (upn.length() != 4 )
494            throw new SMException("Length of Unpredictable Number"
495                  + " must be 4 but got "+upn.length());
496        if (authrc.length() != 1 )
497            throw new SMException("Length of Authorization Result Code"
498                  + " must be 1 but got "+authrc.length());
499        if (sfarc.length() != 2 )
500            throw new SMException("Length of Secend Factor Authorization Result"
501                  + " Code must be 2 but got "+sfarc.length());
502    }
503
504    @Override
505    protected String calculateCAVVImpl(String accountNo, SecureDESKey cvk, String upn,
506                                       String authrc, String sfarc) throws SMException {
507        checkCAVVArgs(upn, authrc,sfarc);
508        return calculateCVD(accountNo,concatKeys(cvk, null),upn,authrc+sfarc);
509    }
510
511    /** {@inheritDoc} */
512    @Override
513    protected boolean verifyCVVImpl(String accountNo, SecureDESKey cvkA, SecureDESKey cvkB,
514                     String cvv, Date expDate, String serviceCode) throws SMException {
515        String result = calculateCVV(accountNo, concatKeys(cvkA, cvkB), expDate, serviceCode);
516        return result.equals(cvv);
517    }
518
519    /** {@inheritDoc} */
520    @Override
521    protected boolean verifyCVVImpl(String accountNo, SecureDESKey cvkA, SecureDESKey cvkB,
522                     String cvv, String expDate, String serviceCode) throws SMException {
523        String result = calculateCVD(accountNo, concatKeys(cvkA, cvkB), expDate, serviceCode);
524        return result.equals(cvv);
525    }
526
527    @Override
528    protected boolean verifyCAVVImpl(String accountNo, SecureDESKey cvk, String cavv,
529                     String upn, String authrc, String sfarc) throws SMException {
530        checkCAVVArgs(upn, authrc,sfarc);
531        String result = calculateCVD(accountNo, concatKeys(cvk, null), upn, authrc+sfarc);
532        return result.equals(cavv);
533    }
534
535    /**
536     * Computes a dynamic CVV (dCVV) using ATC and an issuer master key.
537     *
538     * @param accountNo cardholder primary account number
539     * @param imkac issuer master key for AC, encrypted under the LMK
540     * @param expDate expiry date as {@code yyMM}
541     * @param serviceCode 3-digit service code
542     * @param atc application transaction counter
543     * @param mkdm master-key derivation method (defaults to {@link MKDMethod#OPTION_A})
544     * @return the calculated dCVV
545     * @throws SMException if key derivation or computation fails
546     */
547    protected String calculatedCVV(String accountNo, SecureDESKey imkac,
548                     String expDate, String serviceCode, byte[] atc, MKDMethod mkdm)
549                     throws SMException {
550
551        if (mkdm==null)
552          mkdm = MKDMethod.OPTION_A;
553
554        byte[] panpsn = formatPANPSN_dCVD(accountNo, null, mkdm);
555        Key mkac = deriveICCMasterKey(decryptFromLMK(imkac), panpsn);
556
557        String alteredPAN = ISOUtil.hexString(atc) + accountNo.substring(4);
558
559        return calculateCVD(alteredPAN, mkac, expDate, serviceCode);
560    }
561
562    @Override
563    protected boolean verifydCVVImpl(String accountNo, SecureDESKey imkac, String dcvv,
564                     Date expDate, String serviceCode, byte[] atc, MKDMethod mkdm)
565                     throws SMException {
566
567        String ed = ISODate.formatDate(expDate, "yyMM");
568        String res = calculatedCVV(accountNo, imkac, ed,
569                serviceCode, atc, mkdm
570        );
571        return res.equals(dcvv);
572    }
573
574    @Override
575    protected boolean verifydCVVImpl(String accountNo, SecureDESKey imkac, String dcvv,
576                     String expDate, String serviceCode, byte[] atc, MKDMethod mkdm)
577                     throws SMException {
578
579        String res = calculatedCVV(accountNo, imkac, expDate,
580                serviceCode, atc, mkdm
581        );
582        return res.equals(dcvv);
583    }
584
585    /**
586     * Computes the contactless CVC3 from the issuer master key, ATC, UPN, and track data.
587     *
588     * @param imkcvc3 issuer master key for CVC3, encrypted under the LMK
589     * @param accountNo cardholder primary account number
590     * @param acctSeqNo PAN sequence number, or {@code null}
591     * @param atc application transaction counter
592     * @param upn unpredictable number
593     * @param data IVCVC3 seed (or 2 bytes pre-computed IVCVC3)
594     * @param mkdm master-key derivation method (defaults to {@link MKDMethod#OPTION_A})
595     * @return the 5-digit CVC3
596     * @throws SMException if key derivation or computation fails
597     */
598    protected String calculateCVC3(SecureDESKey imkcvc3, String accountNo, String acctSeqNo,
599                     byte[] atc, byte[] upn, byte[] data, MKDMethod mkdm)
600                     throws SMException {
601        if (mkdm==null)
602          mkdm = MKDMethod.OPTION_A;
603        byte[] panpsn = formatPANPSN_dCVD(accountNo, acctSeqNo, mkdm);
604        Key mkcvc3 = deriveICCMasterKey(decryptFromLMK(imkcvc3), panpsn);
605        byte[] ivcvc3 = data;
606        if (ivcvc3.length != 2)
607          //Compute IVCVC3
608          ivcvc3 = calculateIVCVC3(mkcvc3, data);
609        //Concatenate IVCVC3, UPN and ATC
610        byte[] b = ISOUtil.concat(ivcvc3, upn);
611        b = ISOUtil.concat(b, atc);
612        //Encrypt prepared blok
613        b = jceHandler.encryptData(b, mkcvc3);
614        //Format last two bytes to integer
615        int cvc3l = (b[6] & 0xff) << 8;
616        cvc3l |= b[7] & 0xff;
617        //Convert to string representation
618        return String.format("%05d",cvc3l);
619    }
620
621    @Override
622    protected boolean verifyCVC3Impl(SecureDESKey imkcvc3, String accountNo, String acctSeqNo,
623                     byte[] atc, byte[] upn, byte[] data, MKDMethod mkdm, String cvc3)
624                     throws SMException {
625
626        String calcCVC3 = calculateCVC3(imkcvc3, accountNo, acctSeqNo,
627                atc, upn, data, mkdm
628        );
629        cvc3 = cvc3==null?"":cvc3;
630        //get some last digits
631        calcCVC3 = calcCVC3.substring(5-cvc3.length());
632        return calcCVC3.equals(cvc3);
633    }
634
635    private byte[] calculateIVCVC3(Key mkcvc3, byte[] data)
636            throws JCEHandlerException {
637        byte[] paddedData = paddingISO9797Method2(data);
638        byte[] mac = calculateMACISO9797Alg3(mkcvc3, paddedData);
639        return Arrays.copyOfRange(mac,6,8);
640    }
641
642    /**
643     * ISO/IEC 9797-1 padding method 2
644     * @param d da to be padded
645     * @return padded data
646     */
647    private byte[] paddingISO9797Method2(byte[] d) {
648        //Padding - first byte 0x80 rest 0x00
649        byte[] t = new byte[d.length - d.length%8 + 8];
650        System.arraycopy(d, 0, t, 0, d.length);
651        for (int i=d.length;i<t.length;i++)
652          t[i] = (byte)(i==d.length?0x80:0x00);
653        d = t;
654        return d;
655    }
656
657    /**
658     * Calculate MAC according to ISO/IEC 9797-1 Alg 3
659     * @param key DES double length key
660     * @param d data to calculate MAC on it
661     * @return 8 byte of mac value
662     * @throws JCEHandlerException
663     */
664    private byte[] calculateMACISO9797Alg3(Key key, byte[] d) throws JCEHandlerException {
665        Key kl = jceHandler.formDESKey(SMAdapter.LENGTH_DES
666                            ,Arrays.copyOfRange(key.getEncoded(), 0, 8));
667        Key kr = jceHandler.formDESKey(SMAdapter.LENGTH_DES
668                            ,Arrays.copyOfRange(key.getEncoded(), 8, 16));
669        if (d.length%8 != 0) {
670            //Padding with 0x00 bytes
671            byte[] t = new byte[d.length - d.length%8 + 8];
672            System.arraycopy(d, 0, t, 0, d.length);
673            d = t;
674        }
675        //MAC_CBC alg 3
676        byte[] y_i = ISOUtil.hex2byte("0000000000000000");
677        byte[] yi  = new byte[8];
678        for ( int i=0;i<d.length;i+=8){
679            System.arraycopy(d, i, yi, 0, yi.length);
680            y_i = jceHandler.encryptData(ISOUtil.xor(yi, y_i), kl);
681        }
682        y_i = jceHandler.decryptData(y_i, kr);
683        y_i = jceHandler.encryptData(y_i, kl);
684        return y_i;
685    }
686
687    /**
688     * Prepare 8-bytes data from PAN and PAN Sequence Number.
689     * <p>
690     * Used at EMV operations: {@link #verifyARQCImpl ARQC verification},
691     * {@link #generateARPCImpl ARPC generation},
692     * {@link #generateSM_MACImpl secure message MAC generation}
693     * and {@link #translatePINGenerateSM_MACImpl secure message PIN translation}
694     */
695    private static byte[] formatPANPSN(String pan, String psn, MKDMethod mkdm)
696            throws SMException {
697        switch (mkdm){
698            case OPTION_A:
699                return formatPANPSNOptionA(pan, psn);
700            case OPTION_B:
701                if ( pan.length() <= 16)
702                    //use OPTION_A
703                    return formatPANPSNOptionA(pan, psn);
704                return formatPANPSNOptionB(pan, psn);
705            default:
706                throw new SMException("Unsupported ICC Master Key derivation method");
707        }
708    }
709
710    /**
711     * Prepare 8-bytes data from PAN and PAN Sequence Number.
712     * <p>
713     * Used at dCVV verification {@link #verifydCVVImpl verifydCVV}
714     * and CVC3 {@link #verifyCVC3Impl verifyCVC3}
715     */
716    private static byte[] formatPANPSN_dCVD(String pan, String psn, MKDMethod mkdm)
717            throws SMException {
718        switch (mkdm){
719            case OPTION_A:
720                return formatPANPSNOptionA(pan, psn);
721            case OPTION_B:
722                return formatPANPSNOptionB(pan, psn);
723            default:
724                throw new SMException("Unsupported ICC Master Key derivation method");
725        }
726    }
727
728    /**
729     * Format bytes representing Application PAN and
730     * PAN Sequence Number in BCD format.
731     * <p>
732     * Concatenate from left to right decimal digits of PAN and
733     * PAN Sequence Number digits. If {@code psn} is not present, it is
734     * replaced by a "00" digits. If the result is less than 16 digits long,
735     * pad it to the left with hexadecimal zeros in order to obtain an
736     * 16-digit number. If the Application PAN has an odd number of decimal
737     * digits then concatenate a "0" padding digit to the left thereby
738     * ensuring that the result is an even number of digits.
739     *
740     * @param pan application primary account number
741     * @param psn PAN Sequence Number
742     * @return up to 11 bytes representing Application PAN
743     */
744    private static byte[] preparePANPSN(String pan, String psn){
745        if (psn == null || psn.isEmpty())
746            psn = "00";
747        String ret = pan + psn;
748        //convert digits to bytes and padd with "0"
749        //to left for ensure even number of digits
750        return ISOUtil.hex2byte(ret);
751    }
752
753    /**
754     * Prepare 8-bytes data from PAN and PAN Sequence Number (Option A)
755     * <ul>
756     * <li> Prepare Application PAN and PAN Sequence Number by {@see #preparePANPSN}
757     * <li> Select first 16 digits
758     * </ul>
759     * @param pan application primary account number
760     * @param psn PAN Sequence Number
761     * @return 8-bytes representing rightmost 16 digits
762     */
763    private static byte[] formatPANPSNOptionA(String pan, String psn){
764        if ( pan.length() < 14 )
765            try {
766                pan = ISOUtil.zeropad(pan, 14);
767            } catch( ISOException ex ) {} //NOPMD: ISOException condition is checked before.
768        byte[] b = preparePANPSN(pan, psn);
769        return Arrays.copyOfRange(b, b.length-8, b.length);
770    }
771
772    /**
773     * Prepare bytes data from PAN and PAN Sequence Number (Option B)
774     *
775     * <h5>Do the following:</h5>
776     * <ul>
777     * <li> Prepare Application PAN and PAN Sequence Number by {@see #preparePANPSN}
778     * <li> Hash the prepared result using the SHA-1 hashing algorithm
779     *      to obtain the 20-byte hash result
780     * <li> Decimalize result of hasing by {@see #decimalizeVisa }
781     * <li> Select first 16 decimal digits
782     * </ul>
783     * @param pan application primary account number
784     * @param psn PAN Sequence Number
785     * @return 8-bytes representing first 16 decimalised digits
786     */
787    private static byte[] formatPANPSNOptionB(String pan, String psn){
788        byte[] b = preparePANPSN(pan, psn);
789        //20-bytes sha-1 digest
790        byte[] r = SHA1_MESSAGE_DIGEST.digest(b);
791        //decimalized HEX string of digest
792        String rs = decimalizeVisa(r);
793        //return 16-bytes decimalizd digest
794        return ISOUtil.hex2byte(rs.substring(0,16));
795    }
796
797    /**
798     * Derive ICC Master Key from Issuer Master Key and preformated PAN/PANSeqNo
799     *
800     * Compute two 8-byte numbers:
801     * <ul>
802     * <li>left part is a result of Triple-DES encryption of {@code panpsn} with {@code imk} as the key</li>
803     * <li>right part is a result of Triple-DES encryption of binary-inverted {@code panpsn} with {@code imk} as the key</li>
804     * <li>concatenate left and right parts</li>
805     * </ul>
806     * <br>
807     * Described in EMV v4.2 Book 2, Annex A1.4.1 Master Key Derivation point 2
808     *
809     * @param imk 16-bytes Issuer Master Key
810     * @param panpsn preformated PAN and PAN Sequence Number
811     * @return derived 16-bytes ICC Master Key with adjusted DES parity
812     * @throws JCEHandlerException on cryptographic failure
813     */
814    protected Key deriveICCMasterKey(Key imk, byte[] panpsn)
815            throws JCEHandlerException {
816        byte[] l = Arrays.copyOfRange(panpsn, 0, 8);
817        //left part of derived key
818        l = jceHandler.encryptData(l, imk);
819        byte[] r = Arrays.copyOfRange(panpsn, 0, 8);
820        //inverse clear right part of key
821        r = ISOUtil.xor(r, JCESecurityModule.fPaddingBlock);
822        //right part of derived key
823        r = jceHandler.encryptData(r,imk);
824        //derived key
825        byte[] mk = ISOUtil.concat(l,r);
826        //fix DES parity of key
827        Util.adjustDESParity(mk);
828        //form JCE Tripple-DES Key
829        return jceHandler.formDESKey(SMAdapter.LENGTH_DES3_2KEY, mk);
830    }
831
832    /**
833     * Calculates a 4-digit PVV using the IBM 3624 algorithm.
834     *
835     * @param pinUnderLmk PIN block encrypted under the LMK
836     * @param key clear PVV key
837     * @param keyIdx PVV key index (only the low decimal digit is used)
838     * @param excludes optional list of PINs to reject before computing the PVV
839     * @return the 4-digit PVV
840     * @throws SMException if the PIN is on the excludes list or computation fails
841     */
842    protected String calculatePVV(EncryptedPIN pinUnderLmk, Key key, int keyIdx
843                               ,List<String> excludes) throws SMException {
844        String pin = decryptPINImpl(pinUnderLmk);
845        if (excludes!=null && excludes.contains(pin))
846          throw new WeakPINException("Given PIN is on excludes list");
847        String block = pinUnderLmk.getAccountNumber().substring(1);
848        block += Integer.toString(keyIdx%10);
849        block += pin.substring(0, 4);
850        byte[] b = ISOUtil.hex2byte(block);
851        b = jceHandler.encryptData(b, key);
852
853        return decimalizeVisa(b).substring(0,4);
854    }
855
856    @Override
857    protected String calculatePVVImpl(EncryptedPIN pinUnderLmk, SecureDESKey pvkA,
858                               SecureDESKey pvkB, int pvkIdx, List<String> excludes)
859            throws SMException {
860        Key key = concatKeys(pvkA, pvkB);
861        return calculatePVV(pinUnderLmk, key, pvkIdx, excludes);
862    }
863
864    @Override
865    protected String calculatePVVImpl(EncryptedPIN pinUnderKd1, SecureDESKey kd1,
866                       SecureDESKey pvkA, SecureDESKey pvkB, int pvkIdx,
867                       List<String> excludes) throws SMException {
868        Key key = concatKeys(pvkA, pvkB);
869        EncryptedPIN pinUnderLmk = importPINImpl(pinUnderKd1, kd1);
870        return calculatePVV(pinUnderLmk, key, pvkIdx, excludes);
871    }
872
873    @Override
874    public boolean verifyPVVImpl(EncryptedPIN pinUnderKd1, SecureDESKey kd1, SecureDESKey pvkA,
875                             SecureDESKey pvkB, int pvki, String pvv) throws SMException {
876        Key key = concatKeys(pvkA, pvkB);
877        EncryptedPIN pinUnderLmk = importPINImpl(pinUnderKd1, kd1);
878        String result = calculatePVV(pinUnderLmk, key, pvki, null);
879        return result.equals(pvv);
880    }
881
882    @Override
883    public EncryptedPIN translatePINImpl (EncryptedPIN pinUnderKd1, SecureDESKey kd1,
884            SecureDESKey kd2, byte destinationPINBlockFormat) throws SMException {
885        EncryptedPIN translatedPIN;
886        Key clearKd1 = decryptFromLMK(kd1);
887        Key clearKd2 = decryptFromLMK(kd2);
888        translatedPIN = translatePINExt(null, pinUnderKd1, clearKd1, clearKd2
889                        ,destinationPINBlockFormat, null, PaddingMethod.MCHIP);
890        return translatedPIN;
891    }
892
893    private EncryptedPIN translatePINExt (EncryptedPIN oldPinUnderKd1, EncryptedPIN pinUnderKd1, Key kd1,
894            Key kd2, byte destinationPINBlockFormat, Key udk, PaddingMethod padm) throws SMException {
895        String accountNumber = pinUnderKd1.getAccountNumber();
896        // get clear PIN
897        byte[] clearPINBlock = jceHandler.decryptData(pinUnderKd1.getPINBlock(), kd1);
898        String pin = calculatePIN(clearPINBlock, pinUnderKd1.getPINBlockFormat(),
899                accountNumber);
900        // Reformat PIN Block
901        byte[] translatedPINBlock;
902        if (isVSDCPinBlockFormat(destinationPINBlockFormat)) {
903          String udka = ISOUtil.hexString(Arrays.copyOfRange(udk.getEncoded(), 0, 8));
904          if (destinationPINBlockFormat == SMAdapter.FORMAT42 ) {
905              byte[] oldClearPINBlock = jceHandler.decryptData(oldPinUnderKd1.getPINBlock(), kd1);
906              String oldPIN = calculatePIN(oldClearPINBlock, oldPinUnderKd1.getPINBlockFormat(),
907                      accountNumber);
908              clearPINBlock = calculatePINBlock(pin+":"+oldPIN, destinationPINBlockFormat, udka);
909          } else
910              clearPINBlock = calculatePINBlock(pin, destinationPINBlockFormat, udka);
911
912          accountNumber = udka.substring(4);
913        } else {
914          clearPINBlock = calculatePINBlock(pin, destinationPINBlockFormat, accountNumber);
915        }
916
917        switch(padm){
918          case VSDC:
919              //Add VSDC pin block padding
920              clearPINBlock = ISOUtil.concat(new byte[]{0x08}, clearPINBlock);
921              clearPINBlock = paddingISO9797Method2(clearPINBlock);
922              break;
923          case CCD:
924              //Add CCD pin block padding
925              clearPINBlock = paddingISO9797Method2(clearPINBlock);
926              break;
927          default:
928        }
929
930        // encrypt PIN
931        if (padm == PaddingMethod.CCD)
932          translatedPINBlock = jceHandler.encryptDataCBC(clearPINBlock, kd2
933                  ,Arrays.copyOf(zeroBlock, zeroBlock.length));
934        else
935          translatedPINBlock = jceHandler.encryptData(clearPINBlock, kd2);
936        return new EncryptedPIN(translatedPINBlock
937                        ,destinationPINBlockFormat, accountNumber, false);
938    }
939
940    /**
941     * Derive the session key used for Application Cryptogram verification
942     * or for secure messaging, the diversification value is the ATC
943     * @param mkac unique ICC Master Key for Application Cryptogams or Secure Messaging
944     * @param atc ICC generated Application Transaction Counter as diversification value
945     * @return derived 16-bytes Session Key with adjusted DES parity
946     * @throws JCEHandlerException
947     */
948    private Key deriveSK_VISA(Key mkac, byte[] atc) throws JCEHandlerException {
949
950        byte[] skl = new byte[8];
951        System.arraycopy(atc, atc.length-2, skl, 6, 2);
952        skl = ISOUtil.xor(skl, Arrays.copyOfRange(mkac.getEncoded(),0 ,8));
953
954        byte[] skr = new byte[8];
955        System.arraycopy(atc, atc.length-2, skr, 6, 2);
956        skr = ISOUtil.xor(skr, ISOUtil.hex2byte("000000000000FFFF"));
957        skr = ISOUtil.xor(skr, Arrays.copyOfRange(mkac.getEncoded(),8 ,16));
958        Util.adjustDESParity(skl);
959        Util.adjustDESParity(skr);
960        return jceHandler.formDESKey(SMAdapter.LENGTH_DES3_2KEY, ISOUtil.concat(skl,skr));
961    }
962
963    /**
964     * Common Session Key Derivation Method for secure messaging.
965     * <p>
966     * The diversification value is the <em>RAND</em>, which is ARQC
967     * incremeted by 1 (with overflow) after each script command
968     * for that same ATC value.
969     * Described in EMV v4.2 Book 2, Annex A1.3.1 Common Session Key
970     * Derivation Option for secure messaging.
971     *
972     * @param mksm unique ICC Master Key for Secure Messaging
973     * @param rand Application Cryptogram as diversification value
974     * @return derived 16-bytes Session Key with adjusted DES parity
975     * @throws JCEHandlerException
976     */
977    private Key deriveCommonSK_SM(Key mksm, byte[] rand) throws JCEHandlerException {
978      byte[] rl = Arrays.copyOf(rand,8);
979      rl[2] = (byte)0xf0;
980      byte[] skl = jceHandler.encryptData(rl, mksm);
981      byte[] rr = Arrays.copyOf(rand,8);
982      rr[2] = (byte)0x0f;
983      byte[] skr = jceHandler.encryptData(rr, mksm);
984      Util.adjustDESParity(skl);
985      Util.adjustDESParity(skr);
986      return jceHandler.formDESKey(SMAdapter.LENGTH_DES3_2KEY, ISOUtil.concat(skl,skr));
987    }
988
989    /**
990     * Common Session Key Derivation Method for Application Cryptograms.
991     * <p>
992     * Described in EMV v4.2 Book 2, Annex A1.3.1 Common Session Key Derivation
993     * Option for Application Cryptograms.
994     *
995     * @param mkac unique ICC Master Key for Application Cryptogams.
996     * @param atc ICC generated Application Transaction Counter as diversification value.
997     * @return derived 16-bytes Session Key with adjusted DES parity.
998     * @throws JCEHandlerException
999     */
1000    private Key deriveCommonSK_AC(Key mkac, byte[] atc) throws JCEHandlerException {
1001
1002        byte[] r = new byte[8];
1003        System.arraycopy(atc, atc.length-2, r, 0, 2);
1004
1005        return deriveCommonSK_SM(mkac, r);
1006    }
1007
1008    /**
1009     * MasterCard Proprietary Session Key Derivation (SKD) method.
1010     * <p>
1011     * Described in M/Chip 4 version 1.1 Security & Key Management manual
1012     * paragraph 7 ICC Session Key Derivation.
1013     *
1014     * @param mkac unique ICC Master Key for Application Cryptogams
1015     * @param atc ICC generated Application Transaction Counter as diversification value
1016     * @param upn terminal generated random as diversification value
1017     * @return derived 16-bytes Session Key with adjusted DES parity
1018     * @throws JCEHandlerException
1019     */
1020    private Key deriveSK_MK(Key mkac, byte[] atc, byte[] upn) throws JCEHandlerException {
1021
1022        byte[] r = new byte[8];
1023        System.arraycopy(atc, atc.length-2, r, 0, 2);
1024        System.arraycopy(upn, upn.length-4, r, 4, 4);
1025
1026        return deriveCommonSK_SM(mkac, r);
1027    }
1028
1029    private void constraintMKDM(MKDMethod mkdm, SKDMethod skdm) throws SMException {
1030        if (mkdm == MKDMethod.OPTION_B)
1031            throw new SMException("Master Key Derivation Option B"
1032                    + " is not used in practice with scheme "+skdm);
1033    }
1034
1035    private void constraintARPCM(SKDMethod skdm, ARPCMethod arpcMethod) throws SMException {
1036        if (arpcMethod == ARPCMethod.METHOD_2 )
1037            throw new SMException("ARPC generation method 2"
1038                    + " is not used in practice with scheme "+skdm);
1039    }
1040
1041    /**
1042     * Calculate ARQC (Application Request Cryptogram).
1043     * <p>
1044     * Entry point e.g. for simulator systems.
1045     * @param mkdm Master Key Derivation Method
1046     * @param skdm Session Key Derivation Method
1047     * @param imkac Issuer Master Key for Application Cryptogram
1048     * @param accountNo account number
1049     * @param accntSeqNo account sequence number
1050     * @param atc Application Transaction Counter
1051     * @param upn Unpredictable Number
1052     * @param transData transaction data
1053     * @return computed ARQC bytes
1054     * @throws SMException on cryptographic failure
1055     */
1056    protected byte[] calculateARQC(MKDMethod mkdm, SKDMethod skdm
1057            ,SecureDESKey imkac, String accountNo, String accntSeqNo, byte[] atc
1058            ,byte[] upn, byte[] transData) throws SMException {
1059        if (mkdm==null)
1060            mkdm = MKDMethod.OPTION_A;
1061
1062        byte[] panpsn = formatPANPSN(accountNo, accntSeqNo, mkdm);
1063        Key mkac = deriveICCMasterKey(decryptFromLMK(imkac), panpsn);
1064        Key skac = mkac;
1065        switch(skdm){
1066            case VSDC:
1067                constraintMKDM(mkdm, skdm);
1068                break;
1069            case MCHIP:
1070                constraintMKDM(mkdm, skdm);
1071                skac = deriveSK_MK(mkac,atc,upn);
1072                break;
1073            case EMV_CSKD:
1074                skac = deriveCommonSK_AC(mkac, atc);
1075                break;
1076            default:
1077                throw new SMException("Session Key Derivation "+skdm+" not supported");
1078        }
1079
1080        return calculateMACISO9797Alg3(skac, transData);
1081    }
1082
1083    @Override
1084    protected boolean verifyARQCImpl(MKDMethod mkdm, SKDMethod skdm, SecureDESKey imkac
1085            ,String accountNo, String accntSeqNo, byte[] arqc, byte[] atc
1086            ,byte[] upn, byte[] transData) throws SMException {
1087
1088        byte[] res = calculateARQC(mkdm, skdm, imkac, accountNo, accntSeqNo
1089                ,atc, upn, transData);
1090        return Arrays.equals(arqc, res);
1091    }
1092
1093    @Override
1094    public byte[] generateARPCImpl(MKDMethod mkdm, SKDMethod skdm, SecureDESKey imkac
1095            ,String accountNo, String accntSeqNo, byte[] arqc, byte[] atc, byte[] upn
1096            ,ARPCMethod arpcMethod, byte[] arc, byte[] propAuthData)
1097            throws SMException {
1098        if (mkdm==null)
1099            mkdm = MKDMethod.OPTION_A;
1100
1101        byte[] panpsn = formatPANPSN(accountNo, accntSeqNo, mkdm);
1102        Key mkac = deriveICCMasterKey(decryptFromLMK(imkac), panpsn);
1103        Key skarpc = mkac;
1104        switch(skdm){
1105            case VSDC:
1106                constraintMKDM(mkdm, skdm);
1107                constraintARPCM(skdm, arpcMethod);
1108                break;
1109            case MCHIP:
1110                constraintMKDM(mkdm, skdm);
1111                constraintARPCM(skdm, arpcMethod);
1112                break;
1113            case EMV_CSKD:
1114                skarpc = deriveCommonSK_AC(mkac, atc);
1115                break;
1116            default:
1117                throw new SMException("Session Key Derivation "+skdm+" not supported");
1118        }
1119
1120        return calculateARPC(skarpc, arqc, arpcMethod, arc, propAuthData);
1121    }
1122
1123    @Override
1124    public byte[] verifyARQCGenerateARPCImpl(MKDMethod mkdm, SKDMethod skdm, SecureDESKey imkac
1125            ,String accountNo, String accntSeqNo, byte[] arqc, byte[] atc, byte[] upn 
1126            ,byte[] transData, ARPCMethod arpcMethod, byte[] arc, byte[] propAuthData)
1127            throws SMException {
1128        if (mkdm==null)
1129            mkdm = MKDMethod.OPTION_A;
1130
1131        byte[] panpsn = formatPANPSN(accountNo, accntSeqNo, mkdm);
1132        Key mkac = deriveICCMasterKey(decryptFromLMK(imkac), panpsn);
1133        Key skac = mkac;
1134        Key skarpc = mkac;
1135        switch(skdm){
1136            case VSDC:
1137                constraintMKDM(mkdm, skdm);
1138                constraintARPCM(skdm, arpcMethod);
1139                break;
1140            case MCHIP:
1141                constraintMKDM(mkdm, skdm);
1142                constraintARPCM(skdm, arpcMethod);
1143                skac = deriveSK_MK(mkac,atc,upn);
1144                break;
1145            case EMV_CSKD:
1146                skac = deriveSK_MK(mkac, atc, new byte[4]);
1147                skarpc = skac;
1148                break;
1149            default:
1150                throw new SMException("Session Key Derivation "+skdm+" not supported");
1151        }
1152
1153        if (!Arrays.equals(arqc, calculateMACISO9797Alg3(skac, transData)))
1154            return null;
1155
1156        return calculateARPC(skarpc, arqc, arpcMethod, arc, propAuthData);
1157    }
1158
1159    /**
1160     * Calculate ARPC (Application Response Cryptogram).
1161     * <p>
1162     * Entry point e.g. for simulator systems.
1163     * @param skarpc Session Key for ARPC computation
1164     * @param arqc Application Request Cryptogram
1165     * @param arpcMethod ARPC generation method
1166     * @param arc Authorization Response Code
1167     * @param propAuthData proprietary authentication data (may be {@code null})
1168     * @return computed ARPC bytes
1169     * @throws SMException on cryptographic failure
1170     */
1171    protected byte[] calculateARPC(Key skarpc, byte[] arqc, ARPCMethod arpcMethod
1172             ,byte[] arc, byte[] propAuthData) throws SMException {
1173        if (arpcMethod == null)
1174            arpcMethod = ARPCMethod.METHOD_1;
1175
1176        byte[] b = new byte[8];
1177        switch(arpcMethod) {
1178            case METHOD_1:
1179                System.arraycopy(arc, arc.length-2, b, 0, 2);
1180                b = ISOUtil.xor(arqc, b);
1181                return jceHandler.encryptData(b, skarpc);
1182            case METHOD_2:
1183                b = ISOUtil.concat(arqc, arc);
1184                if (propAuthData != null)
1185                    b = ISOUtil.concat(b, propAuthData);
1186                b = paddingISO9797Method2(b);
1187                b = calculateMACISO9797Alg3(skarpc, b);
1188                return Arrays.copyOf(b, 4);
1189            default:
1190                throw new SMException("ARPC Method "+arpcMethod+" not supported");
1191        }
1192    }
1193
1194    @Override
1195    protected byte[] generateSM_MACImpl(MKDMethod mkdm, SKDMethod skdm
1196            ,SecureDESKey imksmi, String accountNo, String accntSeqNo
1197            ,byte[] atc, byte[] arqc, byte[] data) throws SMException {
1198        if (mkdm==null)
1199          mkdm = MKDMethod.OPTION_A;
1200        byte[] panpsn = formatPANPSN(accountNo, accntSeqNo, mkdm);
1201        Key mksmi = deriveICCMasterKey(decryptFromLMK(imksmi), panpsn);
1202        Key smi;
1203        switch(skdm){
1204          case VSDC:
1205            smi = deriveSK_VISA(mksmi, atc);
1206            data = paddingISO9797Method2(data);
1207            break;
1208          case MCHIP:
1209          case EMV_CSKD:
1210            smi = deriveCommonSK_SM(mksmi,arqc);
1211            data = paddingISO9797Method2(data);
1212            break;
1213          default:
1214            throw new SMException("Session Key Derivation "+skdm+" not supported");
1215        }
1216        return calculateMACISO9797Alg3(smi, data);
1217    }
1218
1219    @Override
1220    protected Pair<EncryptedPIN,byte[]> translatePINGenerateSM_MACImpl(
1221            MKDMethod mkdm, SKDMethod skdm, PaddingMethod padm, SecureDESKey imksmi
1222           ,String accountNo, String accntSeqNo, byte[] atc, byte[] arqc
1223           ,byte[] data, EncryptedPIN currentPIN, EncryptedPIN newPIN
1224           ,SecureDESKey kd1, SecureDESKey imksmc, SecureDESKey imkac
1225           ,byte destinationPINBlockFormat) throws SMException {
1226        if (mkdm==null)
1227          mkdm = MKDMethod.OPTION_A;
1228        byte[] panpsn = formatPANPSN(accountNo, accntSeqNo, mkdm);
1229        Key mksmc = deriveICCMasterKey(decryptFromLMK(imksmc), panpsn);
1230        Key smc;
1231        PaddingMethod derivedPADM;
1232        switch(skdm){
1233          case VSDC:
1234            smc = deriveSK_VISA(mksmc, atc);
1235            derivedPADM = PaddingMethod.VSDC;
1236            break;
1237          case MCHIP:
1238            smc = deriveCommonSK_SM(mksmc,arqc);
1239            derivedPADM = PaddingMethod.MCHIP;
1240            break;
1241          case EMV_CSKD:
1242            smc = deriveCommonSK_SM(mksmc,arqc);
1243            derivedPADM = PaddingMethod.CCD;
1244            break;
1245          default:
1246            throw new SMException("Session Key Derivation "+skdm+" not supported");
1247        }
1248
1249        //Use derived Padding Method if not specified
1250        if ( padm == null )
1251          padm = derivedPADM;
1252
1253        Key udk = null;
1254        if (isVSDCPinBlockFormat(destinationPINBlockFormat))
1255          udk = deriveICCMasterKey(decryptFromLMK(imkac), panpsn);
1256
1257        EncryptedPIN pin =  translatePINExt(currentPIN, newPIN, decryptFromLMK(kd1)
1258                            ,smc, destinationPINBlockFormat, udk, padm);
1259        data = ISOUtil.concat(data, pin.getPINBlock());
1260        byte[] mac = generateSM_MACImpl(mkdm, skdm, imksmi, accountNo
1261                               ,accntSeqNo, atc, arqc, data);
1262        return new Pair(pin, mac);
1263    }
1264
1265    private boolean isVSDCPinBlockFormat(byte pinBlockFormat) {
1266        return pinBlockFormat==SMAdapter.FORMAT41 || pinBlockFormat==SMAdapter.FORMAT42;
1267    }
1268
1269    @Override
1270    public byte[] encryptDataImpl(CipherMode cipherMode, SecureDESKey kd
1271            ,byte[] data, byte[] iv) throws SMException {
1272        Key dek = decryptFromLMK(kd);
1273        return jceHandler.doCryptStuff(data, dek, Cipher.ENCRYPT_MODE, cipherMode, iv);
1274    }
1275
1276    @Override
1277    public byte[] decryptDataImpl(CipherMode cipherMode, SecureDESKey kd
1278            ,byte[] data, byte[] iv) throws SMException {
1279        Key dek = decryptFromLMK(kd);
1280        return jceHandler.doCryptStuff(data, dek, Cipher.DECRYPT_MODE, cipherMode, iv);
1281    }
1282
1283    /**
1284     * Generates CBC-MAC (Cipher Block Chaining Message Authentication Code)
1285     * for some data.
1286     *
1287     * @param data the data to be MACed
1288     * @param kd the key used for MACing
1289     * @return generated CBC-MAC bytes
1290     * @throws SMException on MAC computation failure
1291     */
1292    @Override
1293    protected byte[] generateCBC_MACImpl (byte[] data, SecureDESKey kd) throws SMException {
1294        LogEvent evt = new LogEvent(this, "jce-provider-cbc-mac");
1295        try {
1296          return generateMACImpl(data,kd,cfg.get("cbc-mac","ISO9797ALG3MACWITHISO7816-4PADDING"),evt);
1297        } catch (Exception e) {
1298          Logger.log(evt);
1299          throw  e instanceof SMException ? (SMException)e : new SMException(e);
1300        }
1301    }
1302
1303    /**
1304     * Generates EDE-MAC (Encrypt Decrypt Encrypt Message Authentication Code)
1305     * for some data.
1306     *
1307     * @param data the data to be MACed
1308     * @param kd the key used for MACing
1309     * @return generated EDE-MAC bytes
1310     * @throws SMException on MAC computation failure
1311     */
1312    @Override
1313    protected byte[] generateEDE_MACImpl (byte[] data, SecureDESKey kd) throws SMException {
1314        LogEvent evt = new LogEvent(this, "jce-provider-ede-mac");
1315        try {
1316          return generateMACImpl(data,kd,cfg.get("ede-mac","DESEDEMAC"),evt);
1317        } catch (Exception e) {
1318          Logger.log(evt);
1319          throw  e instanceof SMException ? (SMException)e : new SMException(e);
1320        }
1321    }
1322
1323    private byte[] generateMACImpl (byte[] data, SecureDESKey kd,
1324        String macAlgorithm, LogEvent evt) throws SMException {
1325        try {
1326          return jceHandler.generateMAC(data,decryptFromLMK(kd),macAlgorithm);
1327        } catch (JCEHandlerException e){
1328          evt.addMessage(e);
1329          if (e.getCause() instanceof InvalidKeyException)
1330            throw new SMException(e);
1331          else
1332            throw new SMException("Unable to load MAC algorithm whose name is: " + macAlgorithm +
1333              ". Check that is used correct JCE provider and/or it is proper configured for this module.",e);
1334        }
1335    }
1336
1337    /**
1338     * Generates a random clear key component.
1339     * @param keyLength the desired key length (e.g. LENGTH_DES, LENGTH_DES3_2KEY)
1340     * @return clear key component as a hex string
1341     * @throws SMException on key generation failure
1342     */
1343    public String generateClearKeyComponent (short keyLength) throws SMException {
1344        String clearKeyComponenetHexString;
1345        SimpleMsg[] cmdParameters =  {
1346            new SimpleMsg("parameter", "Key Length", keyLength)
1347        };
1348        LogEvent evt = new LogEvent(this, "s-m-operation");
1349        evt.addMessage(new SimpleMsg("command", "Generate Clear Key Component", cmdParameters));
1350        
1351        try {
1352            Key clearKey = jceHandler.generateDESKey(keyLength);
1353            byte[] clearKeyData = jceHandler.extractDESKeyMaterial(keyLength, clearKey);
1354            clearKeyComponenetHexString = ISOUtil.hexString(clearKeyData);
1355            evt.addMessage(new SimpleMsg("result", "Generated Clear Key Componenet", clearKeyComponenetHexString));
1356        } catch (JCEHandlerException e) {
1357            evt.addMessage(e);
1358            throw  e;
1359        } finally {
1360            Logger.log(evt);
1361        }
1362        return  clearKeyComponenetHexString;
1363    }
1364
1365    /**
1366     * Generates key check value.<br>
1367     * @param secureDESKey SecureDESKey with untrusted or fake Key Check Value
1368     * @return generated Key Check Value
1369     * @throws SMException on cryptographic failure
1370     */
1371    @Override
1372    protected byte[] generateKeyCheckValueImpl (SecureDESKey secureDESKey) throws SMException {
1373        return  calculateKeyCheckValue(decryptFromLMK(secureDESKey));
1374    }
1375
1376    @Override
1377    public SecureDESKey translateKeySchemeImpl (SecureDESKey key, KeyScheme keyScheme)
1378            throws SMException {
1379
1380        if (key == null)
1381            throw new SMException("Missing key to change");
1382
1383        if (keyScheme == null)
1384            throw new SMException("Missing desired key schame");
1385
1386        if (keyScheme == key.getScheme())
1387            return key;
1388
1389        switch (key.getScheme()) {
1390            case Z:
1391                throw new SMException("Single length DES key using the variant method"
1392                        + " can not be converted to any other key");
1393            case U:
1394            case T:
1395                throw new SMException("DES key using the variant method can not "
1396                        + "be converted to less secure key using X9.17 method");
1397            case X:
1398                if (keyScheme != KeyScheme.U)
1399                    throw new SMException("Double length key using X9.17 method may be  "
1400                            + "converted only to double length key using variant method");
1401                break;
1402            case Y:
1403                if (keyScheme != KeyScheme.T)
1404                    throw new SMException("Triple length key using X9.17 method may be  "
1405                            + "converted only to triple length key using variant method");
1406                break;
1407            default:
1408                throw new SMException("Change key scheme allowed only for keys"
1409                        + "using variant method");
1410        }
1411
1412        // get key in clear
1413        Key clearKey = decryptFromLMK(key);
1414        // Encrypt key under LMK
1415        String keyType = getMajorType(key.getKeyType()) + ":" + key.getVariant() + keyScheme;
1416        return encryptToLMK(key.getKeyLength(), keyType, clearKey);
1417    }
1418
1419    /**
1420     * Forms a key from 3 clear components and returns it encrypted under its corresponding LMK
1421     * The corresponding LMK is determined from the keyType
1422     * @param keyLength e.g. LENGTH_DES, LENGTH_DES3_2, LENGTH_DES3_3, ..
1423     * @param keyType possible values are those defined in the SecurityModule interface, e.g. ZMK, TMK
1424     * @param clearComponent1HexString HexString containing the first component
1425     * @param clearComponent2HexString HexString containing the second component
1426     * @param clearComponent3HexString HexString containing the third component
1427     * @return SecureDESKey formed from the three clear components
1428     * @throws SMException on key formation failure
1429     */
1430    public SecureDESKey formKEYfromThreeClearComponents(
1431            short keyLength,
1432            String keyType,
1433            String clearComponent1HexString,
1434            String clearComponent2HexString,
1435            String clearComponent3HexString) throws SMException {
1436        SecureDESKey secureDESKey;
1437        LogEvent evt = new LogEvent(this, "s-m-operation");
1438
1439        try {
1440            byte[] clearComponent1 = ISOUtil.hex2byte(clearComponent1HexString);
1441            byte[] clearComponent2 = clearComponent2HexString != null ? ISOUtil.hex2byte(clearComponent2HexString) : new byte[keyLength >> 3];
1442            byte[] clearComponent3 = clearComponent3HexString != null ? ISOUtil.hex2byte(clearComponent3HexString) : new byte[keyLength >> 3];
1443            byte[] clearKeyBytes = ISOUtil.xor(ISOUtil.xor(clearComponent1, clearComponent2),
1444                    clearComponent3);
1445            Key clearKey = jceHandler.formDESKey(keyLength, clearKeyBytes);
1446            secureDESKey = encryptToLMK(keyLength, keyType, clearKey);
1447            SimpleMsg[] cmdParameters =  {
1448                new SimpleMsg("parameter", "Key Length", keyLength),
1449                new SimpleMsg("parameter", "Key Type", keyType),
1450                new SimpleMsg("parameter", "Component 1 Check Value", calculateKeyCheckValue(jceHandler.formDESKey(keyLength, clearComponent1))),
1451                new SimpleMsg("parameter", "Component 2 Check Value", calculateKeyCheckValue(jceHandler.formDESKey(keyLength, clearComponent2))),
1452                new SimpleMsg("parameter", "Component 3 Check Value", calculateKeyCheckValue(jceHandler.formDESKey(keyLength, clearComponent3)))
1453            };                        
1454            evt.addMessage(new SimpleMsg("command", "Form Key from Three Clear Components", cmdParameters));            
1455            evt.addMessage(new SimpleMsg("result", "Formed Key", secureDESKey));
1456        } catch (JCEHandlerException e) {
1457            evt.addMessage(e);
1458            throw  e;
1459        } finally {
1460            Logger.log(evt);
1461        }
1462        return  secureDESKey;
1463    }
1464
1465    @Override
1466    public SecureDESKey formKEYfromClearComponents (short keyLength, String keyType, String... components)
1467      throws SMException
1468    {
1469        if (components.length < 1 || components.length > 3)
1470            throw new SMException("Invalid number of clear key components");
1471        String[] s = new String[3];
1472        int i=0;
1473        for (String component : components)
1474            s[i++] = component;
1475
1476        return formKEYfromThreeClearComponents(keyLength, keyType, s[0], s[1], s[2]);
1477    }
1478
1479
1480    /**
1481     * Calculates a key check value over a clear key.
1482     * @param key the clear key
1483     * @return the key check value
1484     * @exception SMException on error
1485     */
1486    protected byte[] calculateKeyCheckValue (Key key) throws SMException {
1487        byte[] encryptedZeroBlock = jceHandler.encryptData(zeroBlock, key);
1488        return ISOUtil.trim(encryptedZeroBlock, 3);
1489    }
1490
1491    /**
1492     * Encrypts a clear DES Key under LMK to form a SecureKey.
1493     * @param keyLength the key length (e.g. LENGTH_DES, LENGTH_DES3_2KEY)
1494     * @param keyType the key type as defined in SMAdapter (e.g. ZMK, TMK)
1495     * @param clearDESKey the clear DES key to encrypt
1496     * @return secureDESKey the key encrypted under LMK
1497     * @throws SMException on encryption failure
1498     */
1499    protected SecureDESKey encryptToLMK (short keyLength, String keyType, Key clearDESKey) throws SMException {
1500        Key novar, left, medium, right;
1501        byte[] clearKeyBytes = jceHandler.extractDESKeyMaterial(keyLength, clearDESKey);
1502        byte[] bl = new byte[SMAdapter.LENGTH_DES>>3];
1503        byte[] bm = new byte[SMAdapter.LENGTH_DES>>3];
1504        byte[] br = new byte[SMAdapter.LENGTH_DES>>3];
1505        byte[] encrypted = null;
1506        // enforce correct (odd) parity before encrypting the key
1507        Util.adjustDESParity(clearKeyBytes);
1508        int lmkIndex = getKeyTypeIndex(keyLength, keyType);
1509        switch ( getScheme(keyLength, keyType) ){
1510            case Z:
1511            case X:
1512            case Y:
1513                novar = getLMK( lmkIndex );
1514                encrypted = jceHandler.encryptData(clearKeyBytes, novar);
1515                break;
1516            case U:
1517                left  = getLMK( KEY_U_LEFT  << 12 | lmkIndex & 0xfff );
1518                right = getLMK( KEY_U_RIGHT << 12 | lmkIndex & 0xfff );
1519                System.arraycopy(clearKeyBytes, 0, bl, 0, bl.length);
1520                System.arraycopy(clearKeyBytes, bl.length, br, 0, br.length);
1521                bl = jceHandler.encryptData(bl, left);
1522                br = jceHandler.encryptData(br, right);
1523                encrypted = ISOUtil.concat(bl, br);
1524                break;
1525            case T:
1526                left  = getLMK( KEY_T_LEFT   << 12 | lmkIndex & 0xfff );
1527                medium= getLMK( KEY_T_MEDIUM << 12 | lmkIndex & 0xfff );
1528                right = getLMK( KEY_T_RIGHT  << 12 | lmkIndex & 0xfff );
1529                System.arraycopy(clearKeyBytes, 0, bl, 0, bl.length);
1530                System.arraycopy(clearKeyBytes, bl.length, bm, 0, bm.length);
1531                System.arraycopy(clearKeyBytes, bl.length+bm.length, br, 0, br.length);
1532                bl = jceHandler.encryptData(bl, left);
1533                bm = jceHandler.encryptData(bm, medium);
1534                br = jceHandler.encryptData(br, right);
1535                encrypted = ISOUtil.concat(bl, bm);
1536                encrypted = ISOUtil.concat(encrypted, br);
1537                break;
1538        }
1539        SecureDESKey secureDESKey = new SecureDESKey(keyLength, keyType, encrypted,
1540                calculateKeyCheckValue(clearDESKey));
1541        return  secureDESKey;
1542    }
1543
1544    /**
1545     * Decrypts a secure DES key from encryption under LMK.
1546     * @param secureDESKey key encrypted under LMK
1547     * @return clear key
1548     * @throws SMException on decryption failure
1549     */
1550    protected Key decryptFromLMK (SecureDESKey secureDESKey) throws SMException {
1551        Key left, medium, right;
1552        byte[] keyBytes = secureDESKey.getKeyBytes();
1553        byte[] bl = new byte[SMAdapter.LENGTH_DES>>3];
1554        byte[] bm = new byte[SMAdapter.LENGTH_DES>>3];
1555        byte[] br = new byte[SMAdapter.LENGTH_DES>>3];
1556        byte[] clearKeyBytes = null;
1557        Integer lmkIndex = getKeyTypeIndex(secureDESKey.getKeyLength(), secureDESKey.getKeyType());
1558        if (lmkIndex==null)
1559           throw new SMException("Unsupported key type: " + secureDESKey.getKeyType());
1560        lmkIndex |= secureDESKey.getVariant()<<8;
1561        switch ( secureDESKey.getScheme() ) {
1562            case Z:
1563            case X:
1564            case Y:
1565                clearKeyBytes = jceHandler.decryptData(keyBytes, getLMK( lmkIndex ));
1566                break;
1567            case U:
1568                left  = getLMK( KEY_U_LEFT  << 12 | lmkIndex & 0xfff );
1569                right = getLMK( KEY_U_RIGHT << 12 | lmkIndex & 0xfff );
1570                System.arraycopy(keyBytes, 0, bl, 0, bl.length);
1571                System.arraycopy(keyBytes, bl.length, br, 0, br.length);
1572                bl = jceHandler.decryptData(bl, left);
1573                br = jceHandler.decryptData(br, right);
1574                clearKeyBytes = ISOUtil.concat(bl, br);
1575                clearKeyBytes = ISOUtil.concat(clearKeyBytes, 0, clearKeyBytes.length, clearKeyBytes, 0, br.length );
1576                break;
1577            case T:
1578                left  = getLMK( KEY_T_LEFT   << 12 | lmkIndex & 0xfff );
1579                medium= getLMK( KEY_T_MEDIUM << 12 | lmkIndex & 0xfff );
1580                right = getLMK( KEY_T_RIGHT  << 12 | lmkIndex & 0xfff );
1581                System.arraycopy(keyBytes, 0, bl, 0, bl.length);
1582                System.arraycopy(keyBytes, bl.length, bm, 0, bm.length);
1583                System.arraycopy(keyBytes, bl.length+bm.length, br, 0, br.length);
1584                bl = jceHandler.decryptData(bl, left);
1585                bm = jceHandler.decryptData(bm, medium);
1586                br = jceHandler.decryptData(br, right);
1587                clearKeyBytes = ISOUtil.concat(bl, bm);
1588                clearKeyBytes = ISOUtil.concat(clearKeyBytes, br);
1589                break;
1590        }
1591        if (!Util.isDESParityAdjusted(clearKeyBytes))
1592            throw new JCEHandlerException("Parity not adjusted");
1593        return jceHandler.formDESKey(secureDESKey.getKeyLength(), clearKeyBytes);
1594    }
1595
1596    private char[] formatPINBlock(String pin, int checkDigit){
1597      char[] block = ISOUtil.hexString(fPaddingBlock).toCharArray();
1598      char[] pinLenHex = String.format("%02X", pin.length()).toCharArray();
1599      pinLenHex[0] = (char)('0' + checkDigit);
1600
1601      // pin length then pad with 'F'
1602      System.arraycopy(pinLenHex, 0, block, 0, pinLenHex.length);
1603      System.arraycopy(pin.toCharArray(), 0
1604                      ,block, pinLenHex.length, pin.length());
1605      return block;
1606    }
1607
1608    private String[] splitPins(String pins) {
1609      String[] pin = new String[2];
1610      String[] p = SPLIT_PIN_PATTERN.split(pins);
1611      pin[0] = p[0];
1612      if (p.length >= 2)
1613          pin[1] = p[1];
1614      return pin;
1615    }
1616
1617    /**
1618     * Calculates the clear PIN Block
1619     * @param pin as entered by the card holder on the PIN entry device
1620     * @param pinBlockFormat the PIN block format identifier
1621     * @param accountNumber (the 12 right-most digits of the account number excluding the check digit)
1622     * @return The clear PIN Block
1623     * @throws SMException on invalid PIN or format error
1624     */
1625    protected byte[] calculatePINBlock (String pin, byte pinBlockFormat, String accountNumber) throws SMException {
1626        byte[] pinBlock = null;
1627        String oldPin = null;
1628        if (pinBlockFormat==SMAdapter.FORMAT42){
1629          String[] p = splitPins(pin);
1630          pin = p[0];
1631          oldPin = p[1];
1632          if (oldPin.length() < MIN_PIN_LENGTH || oldPin.length() > MAX_PIN_LENGTH)
1633              throw  new SMException("Invalid OLD PIN length: " + oldPin.length());
1634          if (!ISOUtil.isNumeric(oldPin, 10))
1635              throw  new SMException("Invalid OLD PIN decimal digits: " + oldPin);
1636        }
1637        if (pin.length() < MIN_PIN_LENGTH || pin.length() > MAX_PIN_LENGTH)
1638            throw  new SMException("Invalid PIN length: " + pin.length());
1639        if (!ISOUtil.isNumeric(pin, 10))
1640            throw  new SMException("Invalid PIN decimal digits: " + pin);
1641        if (isVSDCPinBlockFormat(pinBlockFormat)) {
1642          if (accountNumber.length() != 16 )
1643            throw  new SMException("Invalid UDK-A: " + accountNumber
1644                    + ". The length of the UDK-A must be 16 hexadecimal digits");
1645        } else if (accountNumber.length() != 12)
1646            throw  new SMException("Invalid Account Number: " + accountNumber + ". The length of the account number must be 12 (the 12 right-most digits of the account number excluding the check digit)");
1647        switch (pinBlockFormat) {
1648            case FORMAT00: // same as FORMAT01
1649            case FORMAT01:
1650                {
1651                    // Block 1
1652                    byte[] block1 = ISOUtil.hex2byte(new String(formatPINBlock(pin,0x0)));
1653
1654                    // Block 2
1655                    byte[] block2 = ISOUtil.hex2byte("0000" + accountNumber);
1656                    // pinBlock
1657                    pinBlock = ISOUtil.xor(block1, block2);
1658                }
1659                break;
1660            case FORMAT03:
1661                {
1662                    char[] block = ISOUtil.hexString(fPaddingBlock).toCharArray();
1663                    System.arraycopy(pin.toCharArray(), 0
1664                                    ,block, 0, pin.length());
1665                    pinBlock = ISOUtil.hex2byte (new String(block));
1666                }
1667                break;
1668            case FORMAT05:
1669                {
1670                    // Block 1
1671                    char[] block1 = formatPINBlock(pin, 0x1);
1672
1673                    // Block rnd
1674                    byte[] rnd = new byte[8];
1675                    Random rd = new SecureRandom();
1676                    rd.nextBytes(rnd);
1677
1678                    // Block 2
1679                    char[] block2 = ISOUtil.hexString(rnd).toCharArray();
1680
1681                    // merge blocks
1682                    System.arraycopy(block1, 0
1683                                    ,block2, 0, pin.length() + 2
1684                    );
1685                    // pinBlock
1686                    pinBlock = ISOUtil.hex2byte(new String(block2));
1687                }
1688                break;
1689            case FORMAT34:
1690                {
1691                    pinBlock = ISOUtil.hex2byte (new String(formatPINBlock(pin,0x2)));
1692                }
1693                break;
1694            case FORMAT35:
1695                {
1696                    // Block 1
1697                    byte[] block1 = ISOUtil.hex2byte(new String(formatPINBlock(pin,0x2)));
1698
1699                    // Block 2
1700                    byte[] block2 = ISOUtil.hex2byte("0000" + accountNumber);
1701                    // pinBlock
1702                    pinBlock = ISOUtil.xor(block1, block2);
1703                }
1704                break;
1705            case FORMAT41:
1706                {
1707                    // Block 1
1708                    byte[] block1 = ISOUtil.hex2byte(new String(formatPINBlock(pin,0x0)));
1709
1710                    // Block 2 - account number should contain Unique DEA Key A (UDK-A)
1711                    byte[] block2 = ISOUtil.hex2byte("00000000"
1712                                + accountNumber.substring(accountNumber.length()-8) );
1713                    // pinBlock
1714                    pinBlock = ISOUtil.xor(block1, block2);
1715                }
1716                break;
1717            case FORMAT42:
1718                {
1719                    // Block 1
1720                    byte[] block1 = ISOUtil.hex2byte(new String(formatPINBlock(pin,0x0)));
1721
1722                    // Block 2 - account number should contain Unique DEA Key A (UDK-A)
1723                    byte[] block2 = ISOUtil.hex2byte("00000000"
1724                                + accountNumber.substring(accountNumber.length()-8) );
1725                    // Block 3 - old pin
1726                    byte[] block3 = ISOUtil.hex2byte(ISOUtil.zeropadRight(oldPin, 16));
1727                    // pinBlock
1728                    pinBlock = ISOUtil.xor(block1, block2);
1729                    pinBlock = ISOUtil.xor(pinBlock, block3);
1730                }
1731                break;
1732            default:
1733                throw  new SMException("Unsupported PIN format: " + pinBlockFormat);
1734        }
1735        return  pinBlock;
1736    }
1737
1738    private void validatePinBlock(char[] pblock, int checkDigit, int padidx, int offset)
1739            throws SMException {
1740      validatePinBlock(pblock, checkDigit, padidx, offset, 'F');
1741    }
1742
1743    private void validatePinBlock(char[] pblock, int checkDigit
1744                             ,int padidx, int offset, char padDigit)
1745            throws SMException {
1746      // test pin block check digit
1747      if (checkDigit >= 0 && pblock[0] - '0' != checkDigit)
1748          throw new SMException("PIN Block Error - invalid check digit");
1749      // test pin block pdding
1750      int i = pblock.length - 1;
1751      while (i >= padidx)
1752          if (pblock[i--] != padDigit && padDigit > 0)
1753              throw new SMException("PIN Block Error - invalid padding");
1754      // test pin block digits
1755      while (i >= offset)
1756          if (pblock[i--] >= 'A')
1757              throw new SMException("PIN Block Error - illegal pin digit");
1758      // test pin length
1759      int pinLength = padidx - offset;
1760      if (pinLength < MIN_PIN_LENGTH || pinLength > MAX_PIN_LENGTH)
1761          throw new SMException("PIN Block Error - invalid pin length: " + pinLength);
1762    }
1763
1764    /**
1765     * Calculates the clear pin (as entered by card holder on the pin entry device)
1766     * givin the clear PIN block
1767     * @param pinBlock clear PIN Block
1768     * @param pinBlockFormat the PIN block format identifier
1769     * @param accountNumber the 12 right-most digits of the account number
1770     * @return the pin
1771     * @throws SMException on invalid PIN block or format error
1772     */
1773    protected String calculatePIN (byte[] pinBlock, byte pinBlockFormat, String accountNumber) throws SMException {
1774        String pin = null;
1775        if (isVSDCPinBlockFormat(pinBlockFormat)) {
1776          if (accountNumber.length() != 16 )
1777            throw  new SMException("Invalid UDK-A: " + accountNumber
1778                    + ". The length of the UDK-A must be 16 hexadecimal digits");
1779        } else if (accountNumber.length() != 12)
1780            throw  new SMException("Invalid Account Number: " + accountNumber + ". The length of the account number must be 12 (the 12 right-most digits of the account number excluding the check digit)");
1781        switch (pinBlockFormat) {
1782            case FORMAT00: // same as format 01
1783            case FORMAT01:
1784                {
1785                    // Block 2
1786                    byte[] bl2 = ISOUtil.hex2byte("0000" + accountNumber);
1787                    // get Block1
1788                    byte[] bl1 = ISOUtil.xor(pinBlock, bl2);
1789                    int pinLength = bl1[0] & 0x0f;
1790                    char[] block1 = ISOUtil.hexString(bl1).toCharArray();
1791                    int offset = 2;
1792                    int checkDigit = 0x0;
1793                    int padidx = pinLength + offset;
1794                    // test pin block
1795                    validatePinBlock(block1,checkDigit,padidx,offset);
1796                    // get pin
1797                    pin = new String(Arrays.copyOfRange(block1, offset, padidx));
1798                }
1799                break;
1800            case FORMAT03: 
1801                {
1802                    String bl1 = ISOUtil.hexString(pinBlock);
1803                    int padidx = bl1.indexOf('F');
1804                    if ( padidx < 0) padidx = 12;
1805                    char[] block1 = bl1.toCharArray();
1806                    int checkDigit = -0x1;
1807                    int offset = 0;
1808
1809                    // test pin block
1810                    validatePinBlock(block1,checkDigit,padidx,offset);
1811                    // get pin
1812                    pin = new String(Arrays.copyOfRange(block1, offset, padidx));
1813                }
1814                break;
1815            case FORMAT05:
1816                {
1817                    // get Block1
1818                    byte[] bl1 = pinBlock;
1819                    int pinLength = bl1[0] & 0x0f;
1820                    char[] block1 = ISOUtil.hexString(bl1).toCharArray();
1821                    int offset = 2;
1822                    int checkDigit = 0x01;
1823                    int padidx = pinLength + offset;
1824                    // test pin block
1825                    validatePinBlock(block1, checkDigit, padidx, offset, (char) 0);
1826                    // get pin
1827                    pin = new String(Arrays.copyOfRange(block1, offset, padidx));
1828                }
1829                break;
1830            case FORMAT34:
1831                {
1832                    int pinLength = pinBlock[0] & 0x0f;
1833                    char[] block1 = ISOUtil.hexString(pinBlock).toCharArray();
1834                    int offset = 2;
1835                    int checkDigit = 0x2;
1836                    int padidx = pinLength + offset;
1837                    // test pin block
1838                    validatePinBlock(block1,checkDigit,padidx,offset);
1839                    // get pin
1840                    pin = new String(Arrays.copyOfRange(block1, offset, padidx));
1841                }
1842                break;
1843            case FORMAT35:
1844                {
1845                    // Block 2
1846                    byte[] bl2 = ISOUtil.hex2byte("0000" + accountNumber);
1847                    // get Block1
1848                    byte[] bl1 = ISOUtil.xor(pinBlock, bl2);
1849                    int pinLength = bl1[0] & 0x0f;
1850                    char[] block1 = ISOUtil.hexString(bl1).toCharArray();
1851                    int offset = 2;
1852                    int checkDigit = 0x2;
1853                    int padidx = pinLength + offset;
1854                    // test pin block
1855                    validatePinBlock(block1,checkDigit,padidx,offset);
1856                    // get pin
1857                    pin = new String(Arrays.copyOfRange(block1, offset, padidx));
1858                }
1859                break;
1860            case FORMAT41:
1861                {
1862                    // Block 2 - account number should contain Unique DEA Key A (UDK-A)
1863                    byte[] bl2 = ISOUtil.hex2byte("00000000"
1864                                + accountNumber.substring(accountNumber.length()-8) );
1865                    // get Block1
1866                    byte[] bl1 = ISOUtil.xor(pinBlock, bl2);
1867                    int pinLength = bl1[0] & 0x0f;
1868                    char[] block1 = ISOUtil.hexString(bl1).toCharArray();
1869                    int offset = 2;
1870                    int checkDigit = 0x0;
1871                    int padidx = pinLength + offset;
1872                    // test pin block
1873                    validatePinBlock(block1,checkDigit,padidx,offset);
1874                    // get pin
1875                    pin = new String(Arrays.copyOfRange(block1, offset, padidx));
1876                }
1877                break;
1878            case FORMAT42:
1879                {
1880                    // Block 2 - account number should contain Unique DEA Key A (UDK-A)
1881                    byte[] bl2 = ISOUtil.hex2byte("00000000"
1882                                + accountNumber.substring(accountNumber.length()-8) );
1883                    // get Block1
1884                    byte[] bl1 = ISOUtil.xor(pinBlock, bl2);
1885                    int pinLength = bl1[0] & 0x0f;
1886                    char[] block1 = ISOUtil.hexString(bl1).toCharArray();
1887                    int offset = 2;
1888                    int checkDigit = 0x0;
1889                    int padidx = pinLength + offset;
1890                    // test pin block
1891                    validatePinBlock(block1,checkDigit,padidx,offset,'0');
1892                    // get pin
1893                    pin = new String(Arrays.copyOfRange(block1, offset, padidx));
1894                }
1895                break;
1896            default:
1897                throw  new SMException("Unsupported PIN Block format: " + pinBlockFormat);
1898        }
1899        return  pin;
1900    }
1901
1902    /**
1903     * Initializes the JCE Security Module
1904     * @param jceProviderClassName
1905     * @param lmkFile Local Master Keys File used by JCE Security Module to store the LMKs
1906     * @param lmkRebuild if set to true, the lmkFile gets overwritten with newly generated keys (WARNING: this would render all your previously stored SecureKeys unusable)
1907     * @throws SMException
1908     */
1909    private void init (String jceProviderClassName, String lmkFile, boolean lmkRebuild) throws SMException {
1910        File lmk = lmkFile != null ? new File(lmkFile) : null;
1911        if (lmk == null && !lmkRebuild)
1912            throw new SMException ("null lmkFile - needs rebuild");
1913        try {
1914            keyTypeToLMKIndex = new TreeMap<>();
1915            keyTypeToLMKIndex.put(SMAdapter.TYPE_ZMK, 0x000);
1916            keyTypeToLMKIndex.put(SMAdapter.TYPE_ZPK, 0x001);
1917            keyTypeToLMKIndex.put(SMAdapter.TYPE_PVK, 0x002);
1918            keyTypeToLMKIndex.put(SMAdapter.TYPE_TPK, 0x002);
1919            keyTypeToLMKIndex.put(SMAdapter.TYPE_TMK, 0x002);
1920            keyTypeToLMKIndex.put(SMAdapter.TYPE_TAK, 0x003);
1921//            keyTypeToLMKIndex.put(PINLMKIndex,        0x004);
1922            keyTypeToLMKIndex.put(SMAdapter.TYPE_CVK, 0x402);
1923            keyTypeToLMKIndex.put(SMAdapter.TYPE_ZAK, 0x008);
1924            keyTypeToLMKIndex.put(SMAdapter.TYPE_BDK, 0x009);
1925            keyTypeToLMKIndex.put(SMAdapter.TYPE_MK_AC, 0x109);
1926            keyTypeToLMKIndex.put(SMAdapter.TYPE_MK_SMI,  0x209);
1927            keyTypeToLMKIndex.put(SMAdapter.TYPE_MK_SMC,  0x309);
1928            keyTypeToLMKIndex.put(SMAdapter.TYPE_MK_DAC,  0x409);
1929            keyTypeToLMKIndex.put(SMAdapter.TYPE_MK_DN,   0x509);
1930            keyTypeToLMKIndex.put(SMAdapter.TYPE_MK_CVC3, 0x709);
1931            keyTypeToLMKIndex.put(SMAdapter.TYPE_ZEK, 0x00A);
1932            keyTypeToLMKIndex.put(SMAdapter.TYPE_DEK, 0x00B);
1933            keyTypeToLMKIndex.put(SMAdapter.TYPE_RSA_SK, 0x00C);
1934            keyTypeToLMKIndex.put(SMAdapter.TYPE_HMAC,   0x10C);
1935            keyTypeToLMKIndex.put(SMAdapter.TYPE_RSA_PK, 0x00D);
1936            Provider provider = null;
1937            LogEvent evt = new LogEvent(this, "jce-provider");
1938            try {
1939                if (jceProviderClassName != null && !jceProviderClassName.isEmpty()) {
1940                    provider = (Provider) Class.forName(jceProviderClassName).getDeclaredConstructor().newInstance();
1941                    Security.addProvider(provider);
1942                    evt.addMessage("name", provider.getName());
1943                }
1944            } catch (Exception e) {
1945                evt.addMessage(e);
1946                throw  new SMException("Unable to load jce provider whose class name is: "
1947                        + jceProviderClassName);
1948            } finally {
1949                Logger.log(evt);
1950            }
1951            jceHandler = new JCEHandler();
1952            if (lmkRebuild) {
1953                // Creat new LMK file
1954                evt = new LogEvent(this, "local-master-keys");
1955                if (lmk != null)
1956                    evt.addMessage("Rebuilding new Local Master Keys in file: \"" + lmk.getCanonicalPath() + "\".");
1957                Logger.log(evt);
1958                // Generate New random Local Master Keys
1959                generateLMK();
1960                // Write the new Local Master Keys to file
1961                evt = new LogEvent(this, "local-master-keys");
1962                if (lmk != null) {
1963                    writeLMK(lmk);
1964                    evt.addMessage("Local Master Keys built successfully in file: \""
1965                      + lmk.getCanonicalPath() + "\".");
1966                } else {
1967                    evt.addMessage("Local Master Keys built successfully");
1968                }
1969                Logger.log(evt);
1970            }
1971            if (lmk != null) {
1972                if (!lmk.exists()) {
1973                    // LMK File does not exist
1974                    throw  new SMException("Error loading Local Master Keys, file: \""
1975                      + lmk.getCanonicalPath() + "\" does not exist."
1976                      + " Please specify a valid LMK file, or rebuild a new one.");
1977                }
1978                else {
1979                    // Read LMK from file
1980                    readLMK(lmk);
1981                    evt = new LogEvent(this, "local-master-keys");
1982                    evt.addMessage("Loaded successfully from file: \"" + lmk.getCanonicalPath()
1983                      + "\"");
1984                    Logger.log(evt);
1985                }
1986            }
1987        } catch (Exception e) {
1988            if (e instanceof SMException) {
1989                throw  (SMException)e;
1990            }
1991            else {
1992                throw  new SMException(e);
1993            }
1994        }
1995    }
1996
1997    private byte[] applySchemeVariant(byte[] lmkdata, int variant){
1998        byte[] vardata = new byte[lmkdata.length];
1999        System.arraycopy(lmkdata, 0, vardata, 0, lmkdata.length);
2000        //XOR first byte of second key with selected variant byte
2001        vardata[8] ^= variant;
2002        return vardata;
2003    }
2004
2005    private byte[] applyVariant(byte[] lmkdata, int variant){
2006        byte[] vardata = new byte[lmkdata.length];
2007        System.arraycopy(lmkdata, 0, vardata, 0, lmkdata.length);
2008        //XOR first byte of first key with selected variant byte
2009        vardata[0] ^= variant;
2010        return vardata;
2011    }
2012
2013    private void spreadLMKVariants(byte[] lmkData, int idx) throws SMException {
2014        int i = 0;
2015        for (int v :variants){
2016            int k = 0;
2017            byte[] variantData = applyVariant(lmkData,v);
2018            for (int sv :schemeVariants){
2019                byte[] svData = applySchemeVariant(variantData,sv);
2020//                System.out.println(String.format("LMK0x%1$d:%2$d:%3$02x=%4$s", k, i, idx
2021//                        ,ISOUtil.hexString(svData)));
2022                // make it 3 components to work with sun JCE
2023                svData = ISOUtil.concat(
2024                    svData, 0, jceHandler.getBytesLength(SMAdapter.LENGTH_DES3_2KEY),
2025                    svData, 0, jceHandler.getBytesLength(SMAdapter.LENGTH_DES)
2026                    );
2027                int key = idx;
2028                key += 0x100*i;
2029                key += 0x1000*k++;
2030                lmks.put(key, (SecretKey)jceHandler.formDESKey(SMAdapter.LENGTH_DES3_2KEY, svData));
2031            }
2032            i++;
2033        }
2034    }
2035
2036    /**
2037     * Generates new LMK keys
2038     * @exception SMException on error
2039     */
2040    private void generateLMK () throws SMException {
2041        lmks.clear();
2042        try {
2043            for (int i = 0; i <= LMK_PAIRS_NO; i++){
2044              SecretKey lmkKey = (SecretKey)jceHandler.generateDESKey(LMK_KEY_LENGTH);
2045              spreadLMKVariants(lmkKey.getEncoded(), i);
2046            }
2047        } catch (JCEHandlerException e) {
2048            throw  new SMException("Can't generate Local Master Keys", e);
2049        }
2050    }
2051
2052    /**
2053     * reads (loads) LMK's from lmkFile
2054     * @param lmkFile
2055     * @exception SMException on error
2056     */
2057    private void readLMK (File lmkFile) throws SMException {
2058        lmks.clear();
2059        try {
2060            Properties lmkProps = new Properties();
2061            InputStream in = new BufferedInputStream(new FileInputStream(lmkFile));
2062            try {
2063                lmkProps.load(in);
2064            } finally {
2065                in.close();
2066            }
2067            Enumeration<?> e = lmkProps.propertyNames();
2068            while (e.hasMoreElements()) {
2069                String propName = (String) e.nextElement();
2070                lmkProps.put(propName, Environment.get(lmkProps.getProperty(propName)));
2071            }
2072            byte[] lmkData;
2073            for (int i = 0; i <= LMK_PAIRS_NO; i++) {
2074                lmkData = ISOUtil.hex2byte(lmkProps.getProperty(
2075                        String.format("LMK0x%1$02x", i)).substring(0, SMAdapter.LENGTH_DES3_2KEY/4));
2076                spreadLMKVariants(lmkData, i);
2077            }
2078        } catch (Exception e) {
2079            throw  new SMException("Can't read Local Master Keys from file: " +
2080                    lmkFile, e);
2081        }
2082    }
2083
2084    /**
2085     * Writes a newly generated LMK's to lmkFile
2086     * @param lmkFile
2087     * @exception SMException on error
2088     */
2089    private void writeLMK (File lmkFile) throws SMException {
2090        Properties lmkProps = new Properties();
2091        try {
2092            for (int i = 0; i <= LMK_PAIRS_NO; i++) {
2093                lmkProps.setProperty(String.format("LMK0x%1$02x", i),
2094                        ISOUtil.hexString(lmks.get(i).getEncoded()));
2095            }
2096            OutputStream out = new BufferedOutputStream(new FileOutputStream(lmkFile));
2097            try {
2098                lmkProps.store(out, "Local Master Keys");
2099            } finally {
2100                out.close();
2101            }
2102        } catch (Exception e) {
2103            throw  new SMException("Can't write Local Master Keys to file: " + lmkFile,
2104                    e);
2105        }
2106    }
2107
2108    /**
2109     * gets the suitable LMK variant for the key index
2110     * @param lmkIndex
2111     * @return the lmks secret key for the givin key index
2112     * @throws SMException
2113     */
2114    private SecretKey getLMK (Integer lmkIndex) throws SMException {
2115        SecretKey lmk = lmks.get(lmkIndex);
2116        if (lmk==null)
2117            throw  new SMException(String.format("Invalid key code: LMK0x%1$04x", lmkIndex));
2118        return  lmk;
2119    }
2120    /**
2121     * maps a key type to an LMK Index
2122     */
2123    private Map<String,Integer> keyTypeToLMKIndex;
2124    /**
2125     * The clear Local Master Keys
2126     */
2127    private final Map<Integer,SecretKey> lmks = new TreeMap<>();
2128    /**
2129     * A index for the LMK used to encrypt the PINs
2130     */
2131    private static final Integer PINLMKIndex = 0x004;
2132    /**
2133     * The key length (in bits) of the Local Master Keys.
2134     * JCESecurityModule uses Triple DES Local Master Keys
2135     */
2136    private static final short LMK_KEY_LENGTH = LENGTH_DES3_2KEY;
2137    /**
2138     * The minimum length of the PIN
2139     */
2140    private static final short MIN_PIN_LENGTH = 4;
2141    /**
2142     * The maximum length of the PIN
2143     */
2144    private static final short MAX_PIN_LENGTH = 12;
2145
2146    /**
2147     * a 64-bit block of ones used when calculating pin blocks
2148     */
2149    private static final byte[] fPaddingBlock = ISOUtil.hex2byte("FFFFFFFFFFFFFFFF");
2150
2151    /**
2152     * a dummy 64-bit block of zeros used when calculating the check value
2153     */
2154    private static final byte[] zeroBlock = ISOUtil.hex2byte("0000000000000000");
2155
2156    /** Helper used for raw JCE cipher and key operations. */
2157    protected JCEHandler jceHandler;
2158
2159    //--------------------------------------------------------------------------------------------------
2160    // DUKPT
2161    //--------------------------------------------------------------------------------------------------
2162
2163    private byte[] encrypt64(byte[] data, byte[] key)
2164            throws JCEHandlerException
2165    {
2166        return jceHandler.encryptData(
2167                data,
2168                jceHandler.formDESKey((short) (key.length << 3), key)
2169        );
2170    }
2171
2172    private byte[] decrypt64(byte[] data, byte[] key)
2173            throws JCEHandlerException
2174    {
2175        return jceHandler.decryptData(
2176                data,
2177                jceHandler.formDESKey((short) (key.length << 3), key)
2178        );
2179    }
2180
2181    /**
2182     * DUKPT helper that applies a single- or triple-DES "X9.24" style transform
2183     * over an 8-byte block. For an 8-byte key the operation is
2184     * {@code data = (data XOR key) | encrypt | XOR key}; for a 16-byte key it
2185     * is the standard EDE3 transform.
2186     *
2187     * @param data 8-byte input block
2188     * @param key single- or double-length DES key
2189     * @return the transformed 8-byte block
2190     * @throws JCEHandlerException on cipher failure
2191     */
2192    protected byte[] specialEncrypt(byte[] data, byte[] key)
2193            throws JCEHandlerException
2194    {
2195        if (key.length == 8)
2196        {
2197            data = ISOUtil.xor(data, key);
2198            data = encrypt64(data, key);
2199            return ISOUtil.xor(data, key);
2200        }
2201        byte[] keyL = new byte[8];
2202        byte[] keyR = new byte[8];
2203        System.arraycopy(key, 0, keyL, 0, 8);
2204        System.arraycopy(key, 8, keyR, 0, 8);
2205        data = encrypt64(data, keyL);
2206        data = decrypt64(data, keyR);
2207        data = encrypt64(data, keyL);
2208        return data;
2209    }
2210
2211    /**
2212     * Inverse of {@link #specialEncrypt(byte[], byte[])}.
2213     *
2214     * @param data 8-byte input block
2215     * @param key single- or double-length DES key
2216     * @return the transformed 8-byte block
2217     * @throws JCEHandlerException on cipher failure
2218     */
2219    protected byte[] specialDecrypt(byte[] data, byte[] key)
2220            throws JCEHandlerException
2221    {
2222        if (key.length == 8)
2223        {
2224            data = ISOUtil.xor(data, key);
2225            data = decrypt64(data, key);
2226            return ISOUtil.xor(data, key);
2227        }
2228        byte[] keyL = new byte[8];
2229        byte[] keyR = new byte[8];
2230        System.arraycopy(key, 0, keyL, 0, 8);
2231        System.arraycopy(key, 8, keyR, 0, 8);
2232        data = decrypt64(data, keyL);
2233        data = encrypt64(data, keyR);
2234        data = decrypt64(data, keyL);
2235        return data;
2236    }
2237
2238    private void shr(byte[] b)
2239    {
2240        boolean carry = false;
2241        for (int i = 0; i < b.length; i++)
2242        {
2243            byte c = b[i];
2244            b[i] = (byte) (c >>> 1 & 0x7F);
2245            if (carry)
2246            {
2247                b[i] |= 0x80;
2248            }
2249            carry = (c & 0x01) == 1;
2250        }
2251    }
2252
2253    private void or(byte[] b, byte[] mask, int offset)
2254    {
2255        int len = Math.min(b.length - offset, mask.length);
2256        byte[] d = new byte[len];
2257
2258        for (int i = 0; i < len; i++)
2259        {
2260            b[offset++] |= mask[i];
2261        }
2262    }
2263
2264    private byte[] and(byte[] b, byte[] mask, int offset)
2265    {
2266        int len = Math.min(b.length - offset, mask.length);
2267        byte[] d = new byte[b.length];
2268        System.arraycopy(b, 0, d, 0, b.length);
2269
2270        for (int i = 0; i < len; i++, offset++)
2271        {
2272            d[offset] = (byte) (b[offset] & mask[i]);
2273        }
2274        return d;
2275    }
2276
2277    private byte[] and(byte[] b, byte[] mask)
2278    {
2279        return and(b, mask, 0);
2280    }
2281
2282    private boolean notZero(byte[] b)
2283    {
2284        int l = b.length;
2285        for (int i = 0; i < l; i++)
2286        {
2287            if (b[i] != 0)
2288            {
2289                return true;
2290            }
2291        }
2292        return false;
2293    }
2294
2295    private byte[] calculateInitialKey(KeySerialNumber sn, SecureDESKey bdk, boolean tdes)
2296            throws SMException
2297    {
2298        byte[] kl = new byte[8];
2299        byte[] kr = new byte[8];
2300        byte[] kk = decryptFromLMK(bdk).getEncoded();
2301        byte[] ksn = new byte[8];
2302
2303        System.arraycopy(kk, 0, kl, 0, 8);
2304        System.arraycopy(kk, 8, kr, 0, 8);
2305        System.arraycopy (sn.getBytes(), 0, ksn, 0, ksn.length);
2306        ksn[7] &= 0xE0;
2307        
2308        byte[] data = encrypt64(ksn, kl);
2309        data = decrypt64(data, kr);
2310        data = encrypt64(data, kl);
2311
2312        // ANS X9.24:2009 3DES keys (@DFLC/@apr 2011) A.6 steps 5 & 6 (p69)
2313        if (tdes)
2314        {
2315            byte[] kl2 = ISOUtil.xor(kl, _VARIANT_RIGHT_HALF);
2316            byte[] kr2 = ISOUtil.xor(kr, _VARIANT_RIGHT_HALF);
2317            byte[] data2 = encrypt64(ksn, kl2);
2318            data2 = decrypt64(data2, kr2);
2319            data2 = encrypt64(data2, kl2);
2320
2321            byte[] d = new byte[16];
2322            System.arraycopy(data, 0, d, 0, 8);
2323            System.arraycopy(data2, 0, d, 8, 8);
2324            data = d;
2325        }
2326        return data;
2327    }
2328
2329
2330    @Override
2331    public byte[] dataEncrypt (SecureDESKey bdk, byte[] clearText) throws SMException {
2332        try {
2333            byte[] ksnB = jceHandler.generateDESKey ((short) 128).getEncoded();
2334            KeySerialNumber ksn = getKSN (ksnB);
2335            byte[] derivedKey = calculateDerivedKey (ksn, bdk, true, true);
2336            Key dk = jceHandler.formDESKey ((short) 128, derivedKey);
2337            byte[] cypherText = jceHandler.encryptData (lpack(clearText), dk);
2338
2339            ByteBuffer bb = ByteBuffer.allocate (cypherText.length + 32);
2340            bb.put (ksnB);
2341            bb.put (cypherText);
2342            bb.put (hash8 (new byte[][]{ ksnB, cypherText, derivedKey }));
2343            return bb.array();
2344        } catch (JCEHandlerException e) {
2345            throw new SMException (e);
2346        }
2347    }
2348
2349    @Override
2350    public byte[] dataDecrypt (SecureDESKey bdk, byte[] cypherText) throws SMException {
2351        try {
2352            if (cypherText.length < 32) {
2353                throw new SMException (
2354                  "Invalid key block '" + ISOUtil.hexString (cypherText) + "'"
2355                );
2356            }
2357            byte[] ksnB          = new byte[24];
2358            byte[] encryptedData = new byte[cypherText.length - 32];
2359            byte[] mac           = new byte[8];
2360
2361            System.arraycopy (cypherText,  0, ksnB, 0, 24);
2362            System.arraycopy (cypherText, 24, encryptedData, 0, encryptedData.length);
2363            System.arraycopy (cypherText, cypherText.length-8, mac, 0, 8);
2364
2365            KeySerialNumber ksn = getKSN (ksnB);
2366
2367            byte[] derivedKey = calculateDerivedKey (ksn, bdk, true, true);
2368            Key dk = jceHandler.formDESKey ((short) 128, derivedKey);
2369            byte[] clearText = jceHandler.decryptData (encryptedData, dk);
2370            byte[] generatedMac = hash8 (
2371              new byte[][] { ksnB, encryptedData, derivedKey }
2372            );
2373            if (!Arrays.equals (mac, generatedMac))
2374                throw new SMException ("Invalid cyphertext.");
2375            return lunpack (clearText);
2376        } catch (JCEHandlerException e) {
2377            throw new SMException (e);
2378        }
2379   }
2380
2381    /**
2382     * Computes the DUKPT-derived working key for the given KSN and BDK.
2383     *
2384     * @param ksn key serial number
2385     * @param bdk base derivation key, encrypted under the LMK
2386     * @param tdes if {@code true}, use the triple-DES variant; otherwise single-DES
2387     * @param dataEncryption {@code true} when deriving a data-encryption variant key
2388     * @return the derived working key bytes
2389     * @throws SMException if derivation fails
2390     */
2391    protected byte[] calculateDerivedKey(KeySerialNumber ksn, SecureDESKey bdk, boolean tdes, boolean dataEncryption)
2392            throws SMException
2393    {
2394        return tdes?calculateDerivedKeyTDES(ksn,bdk, dataEncryption):calculateDerivedKeySDES(ksn,bdk);
2395    }
2396
2397    private byte[] calculateDerivedKeySDES(KeySerialNumber ksn, SecureDESKey bdk)
2398            throws SMException
2399    {
2400        final byte[] _1FFFFF =
2401                new byte[]{(byte) 0x1F, (byte) 0xFF, (byte) 0xFF};
2402        final byte[] _100000 =
2403                new byte[]{(byte) 0x10, (byte) 0x00, (byte) 0x00};
2404        final byte[] _E00000 =
2405                new byte[]{(byte) 0xE0, (byte) 0x00, (byte) 0x00};
2406
2407        byte[] curkey = calculateInitialKey(ksn, bdk, false);
2408        byte[] smidr = new byte[8];
2409        System.arraycopy (ksn.getBytes(), 2, smidr, 0, smidr.length);
2410
2411        byte[] reg3 = ksn.getTransactionCounterBytes();
2412        reg3 = and(reg3, _1FFFFF);
2413        byte[] shiftr = _100000;
2414        byte[] temp;
2415        byte[] tksnr;
2416        smidr = and(smidr, _E00000, 5);
2417        do
2418        {
2419            temp = and(shiftr, reg3);
2420            if (notZero(temp))
2421            {
2422                or(smidr, shiftr, 5);
2423                tksnr = ISOUtil.xor(smidr, curkey);
2424                tksnr = encrypt64(tksnr, curkey);
2425                curkey = ISOUtil.xor(tksnr, curkey);
2426            }
2427            shr(shiftr);
2428        }
2429        while (notZero(shiftr));
2430        curkey[7] ^= 0xFF;
2431        return curkey;
2432    }
2433    private byte[] calculateDerivedKeyTDES(KeySerialNumber ksn, SecureDESKey bdk, boolean dataEncryption)
2434            throws SMException
2435    {
2436        final byte[] _1FFFFF =
2437                new byte[]{(byte) 0x1F, (byte) 0xFF, (byte) 0xFF};
2438        final byte[] _100000 =
2439                new byte[]{(byte) 0x10, (byte) 0x00, (byte) 0x00};
2440        final byte[] _E00000 =
2441                new byte[]{(byte) 0xE0, (byte) 0x00, (byte) 0x00};
2442
2443        byte[] curkey = calculateInitialKey(ksn, bdk, true);
2444
2445        byte[] smidr = new byte[8];
2446        System.arraycopy (ksn.getBytes(), 2, smidr, 0, smidr.length);
2447
2448        byte[] ksnImage = ksn.getBytes();
2449        byte[] reg3;
2450        if (dataEncryption && ksnImage[0] != (byte) 0xFF && ksnImage[1] != (byte) 0xFF) {
2451            // jPOS 2.x compatibility mode -  
2452             reg3 = new byte[5];
2453             System.arraycopy (ksnImage, 5, reg3, 0, reg3.length);
2454        } else {
2455            reg3 = ksn.getTransactionCounterBytes();
2456        }
2457        reg3 = and(reg3, _1FFFFF);
2458        byte[] shiftr = _100000;
2459        byte[] temp;
2460        byte[] tksnr;
2461        byte[] r8a;
2462        byte[] r8b;
2463        byte[] curkeyL = new byte[8];
2464        byte[] curkeyR = new byte[8];
2465        smidr = and(smidr, _E00000, 5);
2466
2467        do
2468        {
2469            temp = and(shiftr, reg3);
2470            if (notZero(temp))
2471            {
2472                System.arraycopy(curkey, 0, curkeyL, 0, 8);
2473                System.arraycopy(curkey, 8, curkeyR, 0, 8);
2474
2475                or(smidr, shiftr, 5);
2476                // smidr == R8
2477
2478                tksnr = ISOUtil.xor(smidr, curkeyR);
2479                tksnr = encrypt64(tksnr, curkeyL);
2480                tksnr = ISOUtil.xor(tksnr, curkeyR);
2481                // tksnr == R8A
2482                curkeyL = ISOUtil.xor(curkeyL, _VARIANT_RIGHT_HALF);
2483                curkeyR = ISOUtil.xor(curkeyR, _VARIANT_RIGHT_HALF);
2484
2485                r8b = ISOUtil.xor(smidr, curkeyR);
2486                r8b = encrypt64(r8b, curkeyL);
2487                r8b = ISOUtil.xor(r8b, curkeyR);
2488
2489                System.arraycopy(r8b, 0, curkey, 0, 8);
2490                System.arraycopy(tksnr, 0, curkey, 8, 8);
2491            }
2492            shr(shiftr);
2493        }
2494        while (notZero(shiftr));
2495
2496        if (dataEncryption) {
2497            curkey[5] ^= 0xFF;
2498            curkey[13] ^= 0xFF;
2499            System.arraycopy(curkey, 0, curkeyL, 0, 8);
2500            System.arraycopy(curkey, 8, curkeyR, 0, 8);
2501            byte[] L = encrypt64(curkeyL, curkeyL);
2502            L = decrypt64(L, curkeyR);
2503            L = encrypt64(L, curkeyL);
2504
2505            byte[] R = encrypt64(curkeyR, curkeyL); // this is the right implementation
2506            R = decrypt64(R, curkeyR);
2507            R = encrypt64(R, curkeyL);
2508            System.arraycopy (L, 0, curkey, 0, 8);
2509            System.arraycopy (R, 0, curkey, 8, 8);
2510        } else {
2511            curkey[7] ^= 0xFF;
2512            curkey[15] ^= 0xFF;
2513        }
2514        return curkey;
2515    }
2516
2517    /**
2518     * Imports a 128-bit BDK assembled from three clear hex-encoded components.
2519     *
2520     * @param clearComponent1HexString first hex component
2521     * @param clearComponent2HexString second hex component
2522     * @param clearComponent3HexString third hex component
2523     * @return the secured BDK encrypted under the LMK
2524     * @throws SMException if any component is malformed or assembly fails
2525     */
2526    public SecureDESKey importBDK(String clearComponent1HexString,
2527                                  String clearComponent2HexString,
2528                                  String clearComponent3HexString) throws SMException
2529    {
2530        return formKEYfromThreeClearComponents((short) 128, "BDK",
2531                clearComponent1HexString,
2532                clearComponent2HexString,
2533                clearComponent3HexString);
2534    }
2535
2536    private KeySerialNumber getKSN(byte[] b) {
2537        ByteBuffer buf = ByteBuffer.allocate(10);
2538        buf.put(b, 0, 10);
2539        return new KeySerialNumber (buf.array());
2540    }
2541
2542    /**
2543     * Translates a DUKPT-encrypted PIN block to a target encryption key,
2544     * optionally re-formatting the PIN block.
2545     *
2546     * @param pinUnderDuk PIN block encrypted under a derived DUKPT key
2547     * @param ksn key serial number used to derive the source key
2548     * @param bdk base derivation key, encrypted under the LMK
2549     * @param kd2 destination key, encrypted under the LMK
2550     * @param destinationPINBlockFormat target PIN block format
2551     * @param tdes if {@code true}, derive using triple-DES
2552     * @return the PIN re-encrypted under {@code kd2}
2553     * @throws SMException if derivation, decryption, or re-encryption fails
2554     */
2555    protected EncryptedPIN translatePINImpl
2556            (EncryptedPIN pinUnderDuk, KeySerialNumber ksn,
2557             SecureDESKey bdk, SecureDESKey kd2, byte destinationPINBlockFormat,boolean tdes)
2558            throws SMException
2559    {
2560        byte[] derivedKey = calculateDerivedKey(ksn, bdk, tdes, false);
2561        byte[] clearPinblk = specialDecrypt(
2562                pinUnderDuk.getPINBlock(), derivedKey
2563        );
2564
2565        String pan = pinUnderDuk.getAccountNumber();
2566        String pin = calculatePIN(
2567                clearPinblk, pinUnderDuk.getPINBlockFormat(), pan
2568        );
2569        byte[] translatedPinblk = jceHandler.encryptData(
2570                calculatePINBlock(pin, destinationPINBlockFormat, pan),
2571                decryptFromLMK(kd2)
2572        );
2573        return new EncryptedPIN(translatedPinblk, destinationPINBlockFormat, pan,false);
2574    }
2575    protected EncryptedPIN importPINImpl
2576            (EncryptedPIN pinUnderDuk, KeySerialNumber ksn, SecureDESKey bdk,boolean tdes)
2577            throws SMException
2578    {
2579        byte[] derivedKey = calculateDerivedKey(ksn, bdk,tdes, false);
2580        byte[] clearPinblk = specialDecrypt(
2581                pinUnderDuk.getPINBlock(), derivedKey
2582        );
2583        String pan = pinUnderDuk.getAccountNumber();
2584        String pin = calculatePIN(
2585                clearPinblk, pinUnderDuk.getPINBlockFormat(), pan
2586        );
2587
2588        byte[] pinUnderLmk = jceHandler.encryptData(
2589                calculatePINBlock(pin, SMAdapter.FORMAT00, pan),
2590                getLMK(PINLMKIndex)
2591        );
2592        return new EncryptedPIN(pinUnderLmk, SMAdapter.FORMAT00, pan,false);
2593    }
2594
2595    /**
2596     * Exports PIN to DUKPT Encryption.
2597     *
2598     * @param pinUnderLmk PIN block encrypted under the LMK
2599     * @param ksn key serial number used to derive the working key
2600     * @param bdk base derivation key, encrypted under the LMK
2601     * @param tdes if {@code true}, derive using triple-DES
2602     * @param destinationPINBlockFormat target PIN block format
2603     * @return The encrypted pin
2604     * @throws SMException if derivation, decryption, or re-encryption fails
2605     */
2606    public EncryptedPIN exportPIN
2607            (EncryptedPIN pinUnderLmk, KeySerialNumber ksn, SecureDESKey bdk, boolean tdes,
2608             byte destinationPINBlockFormat)
2609            throws SMException
2610    {
2611        String accountNumber = pinUnderLmk.getAccountNumber();
2612        // process
2613        // get clear PIN
2614        byte[] clearPINBlock = jceHandler.decryptData(pinUnderLmk.getPINBlock(),
2615                                                      getLMK(PINLMKIndex));
2616        // extract clear pin
2617        String pin = calculatePIN(clearPINBlock, pinUnderLmk.getPINBlockFormat(),
2618                                  accountNumber);
2619
2620        clearPINBlock = calculatePINBlock(pin, destinationPINBlockFormat, accountNumber);
2621
2622        // encrypt PIN
2623        byte[] derivedKey = calculateDerivedKey(ksn, bdk, tdes, false);
2624        byte[] translatedPINBlock = specialEncrypt(clearPINBlock, derivedKey);
2625
2626        return new EncryptedPIN(translatedPINBlock, destinationPINBlockFormat,
2627                                accountNumber, false);
2628    }
2629
2630    /**
2631     * places a length indicator in a byte array.
2632     * @param b the byte array
2633     * @return a byte array with a two-byte length indicator
2634     */
2635    private byte[] lpack (byte[] b) {
2636        int l = b.length;
2637        int adjustedLen = ((l+9) >> 3) << 3;
2638        byte[] d = new byte[adjustedLen];
2639        System.arraycopy (b, 0, d, 2, l);
2640        d[0] = (byte) ((l >> 8) & 0xFF);
2641        d[1] = (byte) (l & 0xFF);
2642        return d;
2643    }
2644    /**
2645     * Unpacks a byte array packed by lPack
2646     * into the former byte[]
2647     * @param b packed byte array
2648     * @return original (unpacked) byte array
2649     */
2650    private byte[] lunpack (byte[] b) {
2651        int l = ((((int)b[0])&0xFF) << 8) | (((int)b[1])&0xFF);
2652        byte[] d = new byte[l];
2653        System.arraycopy (b,2,d,0,l);
2654        return d;
2655    }
2656
2657    private byte[] hash8 (byte[][] bb) throws SMException {
2658        try {
2659            MessageDigest md = MessageDigest.getInstance("SHA");
2660            for (byte[] b : bb) {
2661                md.update(b);
2662            }
2663            return Arrays.copyOf(md.digest(), 8);
2664        } catch (NoSuchAlgorithmException e) {
2665            throw new SMException (e);
2666        }
2667    }
2668}