/* * Copyright (C) 2017 Schürmann & Breitmoser GbR * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ package org.sufficientlysecure.keychain.securitytoken; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.AlgorithmParameters; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PublicKey; import java.security.SecureRandom; import java.security.cert.CertPathBuilder; import java.security.cert.CertPathBuilderException; import java.security.cert.CertStore; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.CollectionCertStoreParameters; import java.security.cert.PKIXBuilderParameters; import java.security.cert.PKIXCertPathBuilderResult; import java.security.cert.X509CertSelector; import java.security.cert.X509Certificate; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; import java.security.spec.ECParameterSpec; import java.security.spec.EllipticCurve; import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidParameterSpecException; import java.util.ArrayList; import android.content.Context; import androidx.annotation.CheckResult; import androidx.annotation.NonNull; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.KeyAgreement; import javax.crypto.Mac; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.bouncycastle.asn1.nist.NISTNamedCurves; import org.bouncycastle.asn1.x9.ECNamedCurveTable; import org.bouncycastle.asn1.x9.X9ECParameters; import org.bouncycastle.math.ec.ECCurve; import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.util.Arrays; import org.sufficientlysecure.keychain.ui.SettingsSmartPGPAuthoritiesActivity; import org.sufficientlysecure.keychain.util.Preferences; class SCP11bSecureMessaging implements SecureMessaging { private static final byte OPENPGP_SECURE_MESSAGING_CLA_MASK = (byte)0x04; private static final byte OPENPGP_SECURE_MESSAGING_KEY_ATTRIBUTES_TAG = (byte)0xD4; private static final int AES_BLOCK_SIZE = 128 / 8; private static final int SCP11_MAC_LENGTH = AES_BLOCK_SIZE / 2; private static final String SCP11_SYMMETRIC_ALGO = "AES"; private static final String SCP11_CIPHER_ALGO = "AES/CBC/NoPadding"; private static final String SCP11_MAC_ALGO = "AESCMAC"; private static final String SCP11B_KEY_AGREEMENT_ALGO = "ECDH"; private static final String SCP11B_KEY_AGREEMENT_KEY_TYPE = "ECDH"; private static final String SCP11B_KEY_AGREEMENT_KEY_ALGO = "EC"; private static final String SCP11B_KEY_DERIVATION_ALGO = "SHA256"; private static final String CERTIFICATE_FORMAT = "X.509"; private static final String PROVIDER = "BC"; private static SecureRandom srand; private static KeyFactory ecdhFactory; private static CertificateFactory certFactory; private SecretKey mSEnc; private SecretKey mSMac; private SecretKey mSRMac; private short mEncryptionCounter; private byte[] mMacChaining; private SCP11bSecureMessaging() { } private void setKeys(@NonNull final byte[] sEnc, @NonNull final byte[] sMac, @NonNull final byte[] sRmac, @NonNull final byte[] receipt) throws SecureMessagingException { if ((sEnc.length != sMac.length) || (sEnc.length != sRmac.length) || (receipt.length != AES_BLOCK_SIZE)) { throw new SecureMessagingException("incoherent SCP11b key set"); } mSEnc = new SecretKeySpec(sEnc, SCP11_SYMMETRIC_ALGO); mSMac = new SecretKeySpec(sMac, SCP11_SYMMETRIC_ALGO); mSRMac = new SecretKeySpec(sRmac, SCP11_SYMMETRIC_ALGO); mEncryptionCounter = 0; mMacChaining = receipt; } @Override public void clearSession() { mSEnc = null; mSMac = null; mSRMac = null; mEncryptionCounter = 0; mMacChaining = null; } @Override public boolean isEstablished() { return (mSEnc != null) && (mSMac != null) && (mSRMac != null) && (mMacChaining != null); } private static ECParameterSpec getAlgorithmParameterSpec(final EcKeyFormat kf) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidParameterSpecException { final AlgorithmParameters algoParams = AlgorithmParameters.getInstance(SCP11B_KEY_AGREEMENT_KEY_ALGO, PROVIDER); algoParams.init(new ECGenParameterSpec(ECNamedCurveTable.getName(kf.curveOid()))); return algoParams.getParameterSpec(ECParameterSpec.class); } private static ECPublicKey newECDHPublicKey(final EcKeyFormat kf, byte[] data) throws InvalidKeySpecException, NoSuchAlgorithmException, InvalidParameterSpecException, NoSuchProviderException { if (ecdhFactory == null) { ecdhFactory = KeyFactory.getInstance(SCP11B_KEY_AGREEMENT_KEY_TYPE, PROVIDER); } final X9ECParameters params = NISTNamedCurves.getByOID(kf.curveOid()); if (params == null) { throw new InvalidParameterSpecException("unsupported curve"); } final ECCurve curve = params.getCurve(); final ECPoint p = curve.decodePoint(data); if (!p.isValid()) { throw new InvalidKeySpecException("invalid EC point"); } final java.security.spec.ECPublicKeySpec pk = new java.security.spec.ECPublicKeySpec( new java.security.spec.ECPoint(p.getAffineXCoord().toBigInteger(), p.getAffineYCoord().toBigInteger()), getAlgorithmParameterSpec(kf)); return (ECPublicKey)(ecdhFactory.generatePublic(pk)); } private static KeyPair generateECDHKeyPair(final EcKeyFormat kf) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidParameterSpecException, InvalidAlgorithmParameterException { final KeyPairGenerator gen = KeyPairGenerator.getInstance(SCP11B_KEY_AGREEMENT_KEY_ALGO, PROVIDER); if (srand == null) { srand = new SecureRandom(); } gen.initialize(getAlgorithmParameterSpec(kf), srand); return gen.generateKeyPair(); } private static ECPublicKey verifyCertificate(final Context ctx, final EcKeyFormat kf, final byte[] data) throws IOException { try { if (certFactory == null) { certFactory = CertificateFactory.getInstance(CERTIFICATE_FORMAT, PROVIDER); } final ECParameterSpec kfParams = getAlgorithmParameterSpec(kf); final Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(data)); if (!(cert instanceof X509Certificate)) { throw new IOException("invalid card certificate"); } final X509Certificate cardCert = (X509Certificate) cert; final PublicKey pk = cardCert.getPublicKey(); if (!(pk instanceof ECPublicKey)) { throw new IOException("invalid card public key"); } final ECPublicKey cardPk = (ECPublicKey) pk; final ECParameterSpec cardPkParams = cardPk.getParams(); if (!kfParams.getCurve().equals(cardPkParams.getCurve())) { throw new IOException("incoherent card certificate/public key format"); } final KeyStore ks = SettingsSmartPGPAuthoritiesActivity.readKeystore(ctx); if (ks == null) { throw new KeyStoreException("no keystore found"); } final X509CertSelector targetConstraints = new X509CertSelector(); targetConstraints.setCertificate(cardCert); final ArrayList al = new ArrayList(); al.add(cardCert); final CollectionCertStoreParameters certStoreParams = new CollectionCertStoreParameters(al); final CertStore certStore = CertStore.getInstance("Collection", certStoreParams, PROVIDER); final PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(ks, targetConstraints); pkixParams.setRevocationEnabled(false); pkixParams.addCertStore(certStore); final CertPathBuilder builder = CertPathBuilder.getInstance(CertPathBuilder.getDefaultType(), PROVIDER); final PKIXCertPathBuilderResult result = (PKIXCertPathBuilderResult) builder.build(pkixParams); return cardPk; } catch (CertificateException e) { throw new IOException("invalid card certificate (" + e.getMessage() + ")"); } catch (NoSuchAlgorithmException e) { throw new IOException("unknown algorithm (" + e.getMessage() + ")"); } catch (InvalidParameterSpecException e) { throw new IOException("invalid card key parameters (" + e.getMessage() + ")"); } catch (IllegalArgumentException e) { e.printStackTrace(); throw new IOException("illegal argument (" + e.getMessage() + ")"); } catch (NoSuchProviderException e) { throw new IOException("unavailable crypto (" + e.getMessage() + ")"); } catch (KeyStoreException e) { throw new IOException("failed to build keystore (" + e.getMessage() + ")"); } catch (InvalidAlgorithmParameterException e) { throw new IOException("invalid algorithm parameter (" + e.getMessage() + ")"); } catch (CertPathBuilderException e) { throw new IOException("invalid certificate path (" + e.getMessage() + ")"); } } @CheckResult static SecureMessaging establish(final SecurityTokenConnection t, final Context ctx, OpenPgpCommandApduFactory commandFactory) throws SecureMessagingException, IOException { CommandApdu cmd; ResponseApdu resp; Iso7816TLV[] tlvs; t.clearSecureMessaging(); // retrieving key algorithm cmd = commandFactory.createGetDataCommand(0x00, OPENPGP_SECURE_MESSAGING_KEY_ATTRIBUTES_TAG); resp = t.communicate(cmd); if (!resp.isSuccess()) { throw new SecureMessagingException("failed to retrieve secure messaging key attributes"); } tlvs = Iso7816TLV.readList(resp.getData(), true); if ((tlvs == null) || (tlvs.length != 1) || ((byte)tlvs[0].mT != OPENPGP_SECURE_MESSAGING_KEY_ATTRIBUTES_TAG)) { throw new SecureMessagingException("invalid format of secure messaging key attributes"); } final KeyFormat kf = KeyFormat.fromBytes(tlvs[0].mV); if (!(kf instanceof EcKeyFormat)) { throw new SecureMessagingException("invalid format of secure messaging key"); } final EcKeyFormat eckf = (EcKeyFormat)kf; if (eckf.curveOid() == null) { throw new SecureMessagingException("unsupported curve"); } try { ECPublicKey pkcard = null; final Preferences prefs = Preferences.getPreferences(ctx); if (prefs != null && prefs.getExperimentalSmartPGPAuthoritiesEnable()) { // retrieving certificate cmd = commandFactory.createSelectSecureMessagingCertificateCommand(); resp = t.communicate(cmd); if (!resp.isSuccess()) { throw new SecureMessagingException("failed to select secure messaging certificate"); } cmd = commandFactory.createGetDataCardHolderCertCommand(); resp = t.communicate(cmd); if (!resp.isSuccess()) { throw new SecureMessagingException("failed to retrieve secure messaging certificate"); } pkcard = verifyCertificate(ctx, eckf, resp.getData()); } else { cmd = commandFactory.createRetrieveSecureMessagingPublicKeyCommand(); resp = t.communicate(cmd); if (!resp.isSuccess()) { throw new SecureMessagingException("failed to retrieve secure messaging public key"); } tlvs = Iso7816TLV.readList(resp.getData(), true); if ((tlvs == null) || (tlvs.length != 1) || ((short)tlvs[0].mT != (short)0x7f49)) { throw new SecureMessagingException("invalid format of secure messaging key"); } tlvs = Iso7816TLV.readList(tlvs[0].mV, true); if ((tlvs == null) || (tlvs.length != 1) || ((byte)tlvs[0].mT != (byte)0x86)) { throw new SecureMessagingException("invalid format of secure messaging key"); } pkcard = newECDHPublicKey(eckf, tlvs[0].mV); } if (pkcard == null) { throw new SecureMessagingException("No key in token for secure messaging"); } final EllipticCurve curve = pkcard.getParams().getCurve(); final int fieldSize = curve.getField().getFieldSize(); int keySize; if(fieldSize < 512) { keySize = 16; } else { keySize = 32; } final KeyPair ekoce = generateECDHKeyPair(eckf); final ECPublicKey epkoce = (ECPublicKey)ekoce.getPublic(); final ECPrivateKey eskoce = (ECPrivateKey)ekoce.getPrivate(); final byte[] crt_template = new byte[] { (byte)0xA6, (byte)0x0D, (byte)0x90, (byte)0x02, (byte)0x11, (byte)0x00, (byte)0x95, (byte)0x01, (byte)0x3C, (byte)0x80, (byte)0x01, (byte)0x88, (byte)0x81, (byte)0x01, (byte)keySize, (byte)0x5F, (byte)0x49 }; int csize = (int)Math.ceil(epkoce.getParams().getCurve().getField().getFieldSize() / 8.0); ByteArrayOutputStream pkout = new ByteArrayOutputStream(), bout = new ByteArrayOutputStream(); pkout.write((byte)0x04); SecurityTokenUtils.writeBits(pkout, epkoce.getW().getAffineX(), csize); SecurityTokenUtils.writeBits(pkout, epkoce.getW().getAffineY(), csize); bout.write(crt_template); bout.write(SecurityTokenUtils.encodeLength(pkout.size())); pkout.writeTo(bout); pkout = bout; cmd = commandFactory.createInternalAuthForSecureMessagingCommand(pkout.toByteArray()); resp = t.communicate(cmd); if (!resp.isSuccess()) { throw new SecureMessagingException("failed to initiate internal authenticate"); } tlvs = Iso7816TLV.readList(resp.getData(), true); if ((tlvs == null) || (tlvs.length != 2) || (tlvs[0].mT == tlvs[1].mT)) { throw new SecureMessagingException("invalid internal authenticate response"); } byte[] receipt = null; ECPublicKey epkcard = null; for (int i = 0; i < tlvs.length; ++i) { switch (tlvs[i].mT) { case 0x86: if (tlvs[i].mL != AES_BLOCK_SIZE) { throw new SecureMessagingException("invalid size for receipt"); } receipt = tlvs[i].mV; break; case 0x5F49: epkcard = newECDHPublicKey(eckf, tlvs[i].mV); break; default: throw new SecureMessagingException("unexpected data in internal authenticate response"); } } final KeyAgreement ecdhKa = KeyAgreement.getInstance(SCP11B_KEY_AGREEMENT_ALGO, PROVIDER); bout = new ByteArrayOutputStream(); //compute ShSe ecdhKa.init(eskoce); ecdhKa.doPhase(epkcard, true); bout.write(ecdhKa.generateSecret()); //compute ShSs ecdhKa.init(eskoce); ecdhKa.doPhase(pkcard, true); bout.write(ecdhKa.generateSecret()); csize = bout.size() + 3; bout.write(new byte[] { (byte)0, (byte)0, (byte)0, (byte)0, crt_template[8], crt_template[11], (byte)keySize }); byte[] shs = bout.toByteArray(); //key derivation final MessageDigest h = MessageDigest.getInstance(SCP11B_KEY_DERIVATION_ALGO, PROVIDER); bout = new ByteArrayOutputStream(); while (bout.size() < 4 * keySize) { ++shs[csize]; bout.write(h.digest(shs)); } shs = bout.toByteArray(); final byte[] rkey = Arrays.copyOfRange(shs, 0, keySize); final byte[] sEnc = Arrays.copyOfRange(shs, keySize, 2 * keySize); final byte[] sMac = Arrays.copyOfRange(shs, 2 * keySize, 3 * keySize); final byte[] sRmac = Arrays.copyOfRange(shs, 3 * keySize, 4 * keySize); //receipt computation final Mac mac = Mac.getInstance(SCP11_MAC_ALGO, PROVIDER); mac.init(new SecretKeySpec(rkey, SCP11_SYMMETRIC_ALGO)); shs = resp.getData(); mac.update(pkout.toByteArray()); mac.update(shs, 0, shs.length - 2 - AES_BLOCK_SIZE); shs = mac.doFinal(); for(int i = 0; i < AES_BLOCK_SIZE; ++i) { if (shs[i] != receipt[i]) { throw new SecureMessagingException("corrupted receipt!"); } } final SCP11bSecureMessaging sm = new SCP11bSecureMessaging(); sm.setKeys(sEnc, sMac, sRmac, receipt); return sm; } catch (InvalidKeySpecException e) { throw new SecureMessagingException("invalid key specification : " + e.getMessage()); } catch (NoSuchAlgorithmException e) { throw new SecureMessagingException("unknown EC key algorithm : " + e.getMessage()); } catch (InvalidParameterSpecException e) { throw new SecureMessagingException("invalid ECDH parameters : " + e.getMessage()); } catch (NoSuchProviderException e) { throw new SecureMessagingException("unknown provider " + PROVIDER); } catch (InvalidAlgorithmParameterException e) { throw new SecureMessagingException("invalid algorithm parameters : " + e.getMessage()); } catch (InvalidKeyException e) { throw new SecureMessagingException("invalid key : " + e.getMessage()); } catch (IllegalArgumentException e) { throw new SecureMessagingException("illegal argument (" + e.getMessage() + ")"); } } @Override public CommandApdu encryptAndSign(CommandApdu apdu) throws SecureMessagingException { if (!isEstablished()) { throw new SecureMessagingException("not established"); } ++mEncryptionCounter; if(mEncryptionCounter <= 0) { throw new SecureMessagingException("exhausted encryption counter"); } try { byte[] data = apdu.getData(); if (data.length > 0) { final Cipher cipher = Cipher.getInstance(SCP11_CIPHER_ALGO); byte[] iv = new byte[AES_BLOCK_SIZE]; Arrays.fill(iv, (byte)0); cipher.init(Cipher.ENCRYPT_MODE, mSEnc, new IvParameterSpec(iv)); iv[AES_BLOCK_SIZE - 2] = (byte)((mEncryptionCounter >> 8) & 0xff); iv[AES_BLOCK_SIZE - 1] = (byte)(mEncryptionCounter & 0xff); iv = cipher.doFinal(iv); cipher.init(Cipher.ENCRYPT_MODE, mSEnc, new IvParameterSpec(iv)); final byte[] pdata = new byte[data.length + AES_BLOCK_SIZE - (data.length % AES_BLOCK_SIZE)]; System.arraycopy(data, 0, pdata, 0, data.length); pdata[data.length] = (byte)0x80; Arrays.fill(data, (byte)0); data = cipher.doFinal(pdata); Arrays.fill(pdata, (byte)0); Arrays.fill(iv, (byte)0); } final int lcc = data.length + SCP11_MAC_LENGTH; final byte[] odata = new byte[4 + 3 + lcc + 3]; int ooff = 0; odata[ooff++] = (byte) (((byte) apdu.getCLA()) | OPENPGP_SECURE_MESSAGING_CLA_MASK); odata[ooff++] = (byte) apdu.getINS(); odata[ooff++] = (byte) apdu.getP1(); odata[ooff++] = (byte) apdu.getP2(); if (lcc > 0xff) { odata[ooff++] = (byte) 0; odata[ooff++] = (byte) ((lcc >> 8) & 0xff); } odata[ooff++] = (byte) (lcc & 0xff); System.arraycopy(data, 0, odata, ooff, data.length); ooff += data.length; Arrays.fill(data, (byte)0); final Mac mac = Mac.getInstance(SCP11_MAC_ALGO, PROVIDER); mac.init(mSMac); mac.update(mMacChaining); mac.update(odata, 0, ooff); mMacChaining = mac.doFinal(); System.arraycopy(mMacChaining, 0, odata, ooff, SCP11_MAC_LENGTH); ooff += SCP11_MAC_LENGTH; if (lcc > 0xff) { odata[ooff++] = (byte) 0; } odata[ooff++] = (byte) 0; apdu = CommandApdu.fromBytes(odata, 0, ooff); Arrays.fill(odata, (byte)0); return apdu; } catch (NoSuchAlgorithmException e) { throw new SecureMessagingException("unavailable algorithm : " + e.getMessage()); } catch (NoSuchProviderException e) { throw new SecureMessagingException("unavailable provider : " + e.getMessage()); } catch (NoSuchPaddingException e) { throw new SecureMessagingException("unavailable padding algorithm : " + e.getMessage()); } catch (InvalidKeyException e) { throw new SecureMessagingException("invalid key : " + e.getMessage()); } catch (InvalidAlgorithmParameterException e) { throw new SecureMessagingException("invalid IV : " + e.getMessage()); } catch (BadPaddingException e) { throw new SecureMessagingException("invalid IV : " + e.getMessage()); } catch (IllegalBlockSizeException e) { throw new SecureMessagingException("invalid block size : " + e.getMessage()); } } @Override public ResponseApdu verifyAndDecrypt(ResponseApdu apdu) throws SecureMessagingException { if (!isEstablished()) { throw new SecureMessagingException("not established"); } byte[] data = apdu.getData(); if ((data.length == 0) && !apdu.isSuccess() && (apdu.getSw1() != 0x62) && (apdu.getSw1() != 0x63)) { return apdu; } if (data.length < SCP11_MAC_LENGTH) { throw new SecureMessagingException("missing or incomplete MAC in response"); } try { final Mac mac = Mac.getInstance(SCP11_MAC_ALGO, PROVIDER); mac.init(mSRMac); mac.update(mMacChaining); if ((data.length - SCP11_MAC_LENGTH) > 0) { mac.update(data, 0, data.length - SCP11_MAC_LENGTH); } mac.update((byte) apdu.getSw1()); mac.update((byte) apdu.getSw2()); final byte[] sig = mac.doFinal(); for (int i = 0; i < SCP11_MAC_LENGTH; ++i) { if ((i >= sig.length) || (sig[i] != data[data.length - SCP11_MAC_LENGTH + i])) { throw new SecureMessagingException("corrupted integrity"); } } if (((data.length - SCP11_MAC_LENGTH) % AES_BLOCK_SIZE) != 0) { throw new SecureMessagingException("invalid encrypted data size"); } if (data.length > SCP11_MAC_LENGTH) { final Cipher cipher = Cipher.getInstance(SCP11_CIPHER_ALGO); byte[] iv = new byte[AES_BLOCK_SIZE]; Arrays.fill(iv,(byte)0); cipher.init(Cipher.ENCRYPT_MODE, mSEnc, new IvParameterSpec(iv)); iv[0] = (byte) 0x80; iv[AES_BLOCK_SIZE - 2] = (byte) ((mEncryptionCounter >> 8) & 0xff); iv[AES_BLOCK_SIZE - 1] = (byte) (mEncryptionCounter & 0xff); iv = cipher.doFinal(iv); cipher.init(Cipher.DECRYPT_MODE, mSEnc, new IvParameterSpec(iv)); data = cipher.doFinal(data, 0, data.length - SCP11_MAC_LENGTH); int i = data.length - 1; while ((0 < i) && (data[i] == (byte) 0)) --i; if ((i <= 0) || (data[i] != (byte) 0x80)) { throw new SecureMessagingException("invalid data padding after decryption"); } final byte[] datasw = new byte[i + 2]; System.arraycopy(data, 0, datasw, 0, i); datasw[datasw.length - 2] = (byte) apdu.getSw1(); datasw[datasw.length - 1] = (byte) apdu.getSw2(); Arrays.fill(data, (byte) 0); data = datasw; } else { data = new byte[2]; data[0] = (byte) apdu.getSw1(); data[1] = (byte) apdu.getSw2(); } apdu = ResponseApdu.fromBytes(data); return apdu; } catch (NoSuchAlgorithmException e) { throw new SecureMessagingException("unavailable algorithm : " + e.getMessage()); } catch (NoSuchProviderException e) { throw new SecureMessagingException("unknown provider : " + e.getMessage()); } catch (NoSuchPaddingException e) { throw new SecureMessagingException("unavailable padding algorithm : " + e.getMessage()); } catch (InvalidKeyException e) { throw new SecureMessagingException("invalid key : " + e.getMessage()); } catch (BadPaddingException e) { throw new SecureMessagingException("invalid IV : " + e.getMessage()); } catch (InvalidAlgorithmParameterException e) { throw new SecureMessagingException("invalid IV : " + e.getMessage()); } catch (IllegalBlockSizeException e) { throw new SecureMessagingException("invalid block size : " + e.getMessage()); } } }