/*
 * Copyright 2004 - 2013 Wayne Grant
 *           2013 - 2025 Kai Kramer
 *
 * This file is part of KeyStore Explorer.
 *
 * KeyStore Explorer 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.
 *
 * KeyStore Explorer 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 KeyStore Explorer.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.kse.crypto.privatekey;

import static javax.crypto.Cipher.DECRYPT_MODE;
import static javax.crypto.Cipher.ENCRYPT_MODE;
import static org.kse.crypto.privatekey.EncryptionType.ENCRYPTED;
import static org.kse.crypto.privatekey.EncryptionType.UNENCRYPTED;

import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.interfaces.DSAParams;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.text.MessageFormat;
import java.util.ResourceBundle;
import java.util.Set;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Integer;
import org.bouncycastle.asn1.ASN1OctetString;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.kse.KSE;
import org.kse.crypto.CryptoException;
import org.kse.crypto.digest.DigestType;
import org.kse.crypto.digest.DigestUtil;
import org.kse.crypto.ecc.EccUtil;
import org.kse.crypto.keypair.KeyPairType;
import org.kse.crypto.keypair.KeyPairUtil;
import org.kse.gui.passwordmanager.Password;
import org.kse.utilities.PRNG;
import org.kse.utilities.pem.PemAttribute;
import org.kse.utilities.pem.PemAttributes;
import org.kse.utilities.pem.PemInfo;
import org.kse.utilities.pem.PemUtil;

/**
 * Provides utility methods relating to OpenSSL (= PKCS#1 for RSA) encoded private keys.
 *
 * <pre>
 * RSA PKCS#1 RSAPrivateKey
 * ------------------------
 *
 * RSAPrivateKey ::= SEQUENCE {
 *   version          INTEGER Version(0),
 *   modulus          INTEGER, -- n
 *   publicExponent   INTEGER, -- e
 *   privateExponent  INTEGER, -- d
 *   ...
 * }
 *
 * ------BEGIN RSA PRIVATE KEY------
 * ------END RSA PRIVATE KEY------
 * </pre>
 *
 * <pre>
 * EC PrivateKey
 * -------------
 * (RFC 5915 or SEC 1 section C.4)
 *
 * ECPrivateKey ::= SEQUENCE {
 *   version        INTEGER { ecPrivkeyVer1(1) },
 *   privateKey     OCTET STRING,
 *   parameters [0] ECParameters {{ NamedCurve }} OPTIONAL, // RFC5480
 *   publicKey [1]  BIT STRING OPTIONAL
 * }
 *
 * -----BEGIN EC PRIVATE KEY-----
 * -----END EC PRIVATE KEY-----
 *
 * -----BEGIN EC PRIVATE KEY-----
 * Proc-Type: 4,ENCRYPTED
 * DEK-Info: DES-EDE3-CBC,258248872DB25390
 * -----END EC PRIVATE KEY-----
 *
 * </pre>
 */
public class OpenSslPvkUtil {
    private static ResourceBundle res = ResourceBundle.getBundle("org/kse/crypto/privatekey/resources");

    // Begin OpenSSL RSA private key PEM
    private static final String OPENSSL_RSA_PVK_PEM_TYPE = "RSA PRIVATE KEY";

    // Begin OpenSSL DSA private key PEM
    private static final String OPENSSL_DSA_PVK_PEM_TYPE = "DSA PRIVATE KEY";

    // Begin OpenSSL EC private key PEM
    private static final String OPENSSL_EC_PVK_PEM_TYPE = "EC PRIVATE KEY";

    // OpenSSL version
    private static final BigInteger VERSION = BigInteger.ZERO;

    // EC private key ASN.1 structure has version 1
    private static final BigInteger VERSION_EC = BigInteger.ONE;

    // Proc-Type PEM header attribute name
    private static final String PROC_TYPE_ATTR_NAME = "Proc-Type";

    // Proc-Type PEM header attribute value
    private static final String PROC_TYPE_ATTR_VALUE = "4,ENCRYPTED";

    // DEK-Info PEM header attribute name
    private static final String DEK_INFO_ATTR_NAME = "DEK-Info";

    // DEK-Info PEM headere attribute value template (pbe algorithm,salt)
    private static final String DEK_INFO_ATTR_VALUE_TEMPLATE = "{0},{1}";

    private static final Set<KeyPairType> OPEN_SSL_SUPPORTED_KEYPAIR = Set.of(
            KeyPairType.RSA, KeyPairType.DSA, KeyPairType.EC, KeyPairType.ECDSA);

    private OpenSslPvkUtil() {
    }

    /**
     * OpenSSL encode a private key.
     *
     * @param privateKey The private key
     * @return The encoding
     * @throws CryptoException Problem encountered while getting the encoded private key
     */
    public static byte[] get(PrivateKey privateKey) throws CryptoException {
        try {
            // DER encoding for each key type is a sequence
            ASN1Sequence asn1Sequence;

            if (privateKey instanceof RSAPrivateCrtKey) {
                RSAPrivateCrtKey rsaPrivateKey = (RSAPrivateCrtKey) privateKey;

                ASN1EncodableVector vec = new ASN1EncodableVector();
                vec.add(new ASN1Integer(VERSION));
                vec.add(new ASN1Integer(rsaPrivateKey.getModulus()));
                vec.add(new ASN1Integer(rsaPrivateKey.getPublicExponent()));
                vec.add(new ASN1Integer(rsaPrivateKey.getPrivateExponent()));
                vec.add(new ASN1Integer(rsaPrivateKey.getPrimeP()));
                vec.add(new ASN1Integer(rsaPrivateKey.getPrimeQ()));
                vec.add(new ASN1Integer(rsaPrivateKey.getPrimeExponentP()));
                vec.add(new ASN1Integer(rsaPrivateKey.getPrimeExponentQ()));
                vec.add(new ASN1Integer(rsaPrivateKey.getCrtCoefficient()));
                asn1Sequence = new DERSequence(vec);
            } else if (privateKey instanceof DSAPrivateKey) {
                DSAPrivateKey dsaPrivateKey = (DSAPrivateKey) privateKey;
                DSAParams dsaParams = dsaPrivateKey.getParams();

                BigInteger primeModulusP = dsaParams.getP();
                BigInteger primeQ = dsaParams.getQ();
                BigInteger generatorG = dsaParams.getG();
                BigInteger secretExponentX = dsaPrivateKey.getX();

                // Derive public key from private key parts, ie Y = G^X mod P
                BigInteger publicExponentY = generatorG.modPow(secretExponentX, primeModulusP);

                ASN1EncodableVector vec = new ASN1EncodableVector();
                vec.add(new ASN1Integer(VERSION));
                vec.add(new ASN1Integer(primeModulusP));
                vec.add(new ASN1Integer(primeQ));
                vec.add(new ASN1Integer(generatorG));
                vec.add(new ASN1Integer(publicExponentY));
                vec.add(new ASN1Integer(secretExponentX));
                asn1Sequence = new DERSequence(vec);
            } else {
                ECPrivateKey ecKey = (ECPrivateKey) privateKey;
                org.bouncycastle.asn1.sec.ECPrivateKey ecPrivateKey = EccUtil.convertToECPrivateKeyStructure(ecKey);
                asn1Sequence = (ASN1Sequence) ecPrivateKey.toASN1Primitive();
            }

            return asn1Sequence.getEncoded();
        } catch (IOException ex) {
            throw new CryptoException(res.getString("NoDerEncodeOpenSslPrivateKey.exception.message"), ex);
        }
    }

    /**
     * OpenSSL encode a private key and PEM the encoding.
     *
     * @param privateKey The private key
     * @return The PEM'd encoding
     * @throws CryptoException Problem encountered while getting the encoded private key
     */
    public static String getPem(PrivateKey privateKey) throws CryptoException {
        byte[] openSsl = get(privateKey);

        String pemType;

        if (privateKey instanceof RSAPrivateCrtKey) {
            pemType = OPENSSL_RSA_PVK_PEM_TYPE;
        } else if (privateKey instanceof ECPrivateKey) {
            pemType = OPENSSL_EC_PVK_PEM_TYPE;
        } else {
            pemType = OPENSSL_DSA_PVK_PEM_TYPE;
        }

        PemInfo pemInfo = new PemInfo(pemType, null, openSsl);

        return PemUtil.encode(pemInfo);
    }

    /**
     * OpenSSL encode and encrypt a private key. Encrypted OpenSSL private keys
     * must always be PEM'd.
     *
     * @param privateKey The private key
     * @param pbeType    PBE algorithm to use for encryption
     * @param password   Encryption password
     * @return The encrypted, PEM'd encoding
     * @throws CryptoException Problem encountered while getting the encoded private key
     */
    public static String getEncrypted(PrivateKey privateKey, OpenSslPbeType pbeType, Password password)
            throws CryptoException {
        byte[] openSsl = get(privateKey);

        String pemType;

        if (privateKey instanceof RSAPrivateCrtKey) {
            pemType = OPENSSL_RSA_PVK_PEM_TYPE;
        } else if (privateKey instanceof ECPrivateKey) {
            pemType = OPENSSL_EC_PVK_PEM_TYPE;
        } else {
            pemType = OPENSSL_DSA_PVK_PEM_TYPE;
        }

        byte[] salt = generateSalt(pbeType.saltSize() / 8);

        String saltHex = bytesToHex(salt);

        byte[] encOpenSsl;

        try {
            byte[] encryptKey = deriveKeyFromPassword(password, salt, pbeType.keySize());

            // Create cipher - use all the salt as the IV
            Cipher cipher = createCipher(pbeType.jceCipher(), encryptKey, salt, ENCRYPT_MODE);

            encOpenSsl = cipher.doFinal(openSsl);
        } catch (GeneralSecurityException ex) {
            throw new CryptoException(MessageFormat.format(res.getString("OpenSslEncryptionFailed.exception.message"),
                                                           pbeType.friendly()), ex);
        }

        PemAttributes attributes = new PemAttributes();

        attributes.add(new PemAttribute(PROC_TYPE_ATTR_NAME, PROC_TYPE_ATTR_VALUE));

        String dekInfoAttrValue = MessageFormat.format(DEK_INFO_ATTR_VALUE_TEMPLATE, pbeType.dekInfo(), saltHex);
        attributes.add(new PemAttribute(DEK_INFO_ATTR_NAME, dekInfoAttrValue));

        PemInfo pemInfo = new PemInfo(pemType, attributes, encOpenSsl);

        return PemUtil.encode(pemInfo);
    }

    /**
     * Load an unencrypted OpenSSL private key from the stream. The encoding of
     * the private key may be PEM or DER.
     *
     * @param pvkData Stream to load the unencrypted private key from
     * @return The private key
     * @throws PrivateKeyEncryptedException If private key is encrypted
     * @throws CryptoException              Problem encountered while loading the private key
     * @throws IOException                  An I/O error occurred
     */
    public static PrivateKey load(byte[] pvkData) throws CryptoException, IOException {

        EncryptionType encType = getEncryptionType(pvkData);

        if (encType == null) {
            throw new CryptoException(res.getString("NotValidOpenSsl.exception.message"));
        }

        if (encType == ENCRYPTED) {
            throw new PrivateKeyEncryptedException(res.getString("OpenSslIsEncrypted.exception.message"));
        }

        // Check if stream is PEM encoded
        PemInfo pemInfo = PemUtil.decode(pvkData);

        if (pemInfo != null) {
            // It is - get DER from PEM
            pvkData = pemInfo.getContent();
        }

        try (ASN1InputStream asn1InputStream = new ASN1InputStream(pvkData)) {
            // Read OpenSSL DER structure
            ASN1Primitive openSsl = asn1InputStream.readObject();
            asn1InputStream.close();

            if (openSsl instanceof ASN1Sequence) {
                ASN1Sequence seq = (ASN1Sequence) openSsl;

                if (seq.size() == 9) { // RSA private key

                    BigInteger version = ((ASN1Integer) seq.getObjectAt(0)).getValue();
                    BigInteger modulus = ((ASN1Integer) seq.getObjectAt(1)).getValue();
                    BigInteger publicExponent = ((ASN1Integer) seq.getObjectAt(2)).getValue();
                    BigInteger privateExponent = ((ASN1Integer) seq.getObjectAt(3)).getValue();
                    BigInteger primeP = ((ASN1Integer) seq.getObjectAt(4)).getValue();
                    BigInteger primeQ = ((ASN1Integer) seq.getObjectAt(5)).getValue();
                    BigInteger primeExponentP = ((ASN1Integer) seq.getObjectAt(6)).getValue();
                    BigInteger primeExponenetQ = ((ASN1Integer) seq.getObjectAt(7)).getValue();
                    BigInteger crtCoefficient = ((ASN1Integer) seq.getObjectAt(8)).getValue();

                    if (!version.equals(VERSION)) {
                        throw new CryptoException(
                                MessageFormat.format(res.getString("OpenSslVersionIncorrect.exception.message"),
                                                     "" + VERSION.intValue(), "" + version.intValue()));
                    }

                    RSAPrivateCrtKeySpec rsaPrivateCrtKeySpec = new RSAPrivateCrtKeySpec(modulus, publicExponent,
                                                                                         privateExponent, primeP,
                                                                                         primeQ, primeExponentP,
                                                                                         primeExponenetQ,
                                                                                         crtCoefficient);

                    KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                    return keyFactory.generatePrivate(rsaPrivateCrtKeySpec);
                } else if (seq.size() == 6) { // DSA private key

                    BigInteger version = ((ASN1Integer) seq.getObjectAt(0)).getValue();
                    BigInteger primeModulusP = ((ASN1Integer) seq.getObjectAt(1)).getValue();
                    BigInteger primeQ = ((ASN1Integer) seq.getObjectAt(2)).getValue();
                    BigInteger generatorG = ((ASN1Integer) seq.getObjectAt(3)).getValue();
                    // publicExponentY not req for pvk: sequence.getObjectAt(4);
                    BigInteger secretExponentX = ((ASN1Integer) seq.getObjectAt(5)).getValue();

                    if (!version.equals(VERSION)) {
                        throw new CryptoException(
                                MessageFormat.format(res.getString("OpenSslVersionIncorrect.exception.message"),
                                                     "" + VERSION.intValue(), "" + version.intValue()));
                    }

                    DSAPrivateKeySpec dsaPrivateKeySpec = new DSAPrivateKeySpec(secretExponentX, primeModulusP, primeQ,
                                                                                generatorG);

                    KeyFactory keyFactory = KeyFactory.getInstance("DSA");
                    return keyFactory.generatePrivate(dsaPrivateKeySpec);
                } else if (seq.size() >= 2) { // EC private key (RFC 5915)

                    org.bouncycastle.asn1.sec.ECPrivateKey pKey = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(
                            seq);
                    AlgorithmIdentifier algId = new AlgorithmIdentifier(X9ObjectIdentifiers.id_ecPublicKey,
                                                                        pKey.getParametersObject());
                    PrivateKeyInfo privInfo = new PrivateKeyInfo(algId, pKey);
                    return new JcaPEMKeyConverter().getPrivateKey(privInfo);
                } else {
                    throw new CryptoException(
                            MessageFormat.format(res.getString("OpenSslSequenceIncorrectSize.exception.message"),
                                                 "" + seq.size()));
                }
            } else {
                throw new CryptoException(res.getString("OpenSslSequenceNotFound.exception.message"));
            }
        } catch (Exception ex) {
            throw new CryptoException(res.getString("NoLoadOpenSslPrivateKey.exception.message"), ex);
        }
    }

    /**
     * Load an encrypted OpenSSL private key from the specified stream. The
     * encoding of the private key will be PEM.
     *
     * @param pvkData  BA to load the encrypted private key from
     * @param password Password to decrypt
     * @return The private key
     * @throws PrivateKeyUnencryptedException     If private key is unencrypted
     * @throws PrivateKeyPbeNotSupportedException If private key PBE algorithm is not supported
     * @throws CryptoException                    Problem encountered while loading the private key
     * @throws IOException                        An I/O error occurred
     */
    public static PrivateKey loadEncrypted(byte[] pvkData, Password password) throws CryptoException, IOException {

        EncryptionType encType = getEncryptionType(pvkData);

        if (encType == null) {
            throw new CryptoException(res.getString("NotValidOpenSsl.exception.message"));
        }

        if (encType == UNENCRYPTED) {
            throw new PrivateKeyUnencryptedException(res.getString("OpenSslIsUnencrypted.exception.message"));
        }

        // OpenSSL must be encrypted and therefore must be PEM
        PemInfo pemInfo = PemUtil.decode(pvkData);

        byte[] encKey = pemInfo.getContent();

        PemAttributes attributes = pemInfo.getAttributes();
        String dekInfo = attributes.get(DEK_INFO_ATTR_NAME).getValue();

        // Split DEK-Info into encryption pbe algorithm and salt
        int separator = dekInfo.indexOf(',');

        if (separator == -1) {
            throw new CryptoException(
                    MessageFormat.format(res.getString("OpenSslDekInfoMalformed.exception.message"), dekInfo));
        }

        String encAlg = dekInfo.substring(0, separator);
        String salt = dekInfo.substring(separator + 1);

        byte[] saltBytes = hexToBytes(salt);

        OpenSslPbeType pbeType = OpenSslPbeType.resolveDekInfo(encAlg);

        if (pbeType == null) {
            throw new PrivateKeyPbeNotSupportedException(encAlg, MessageFormat.format(
                    res.getString("PrivateKeyWrappingAlgUnsupported.exception.message"), encAlg));
        }

        try {
            byte[] decryptKey = deriveKeyFromPassword(password, saltBytes, pbeType.keySize());

            // Create cipher - use all the salt as the IV
            Cipher cipher = createCipher(pbeType.jceCipher(), decryptKey, saltBytes, DECRYPT_MODE);

            byte[] key = cipher.doFinal(encKey);

            return load(key);
        } catch (GeneralSecurityException ex) {
            throw new CryptoException(MessageFormat.format(res.getString("OpenSslDecryptionFailed.exception.message"),
                                                           pbeType.friendly()), ex);
        }
    }

    /**
     * Detect if a OpenSSL private key is encrypted or not.
     *
     * @param openSsl BA data containing OpenSSL private key
     * @return Encryption type or null if not a valid OpenSSL private key
     * @throws IOException If an I/O problem occurred
     */
    public static EncryptionType getEncryptionType(byte[] openSsl) throws IOException {

        // In PEM format?
        PemInfo pemInfo = PemUtil.decode(openSsl);

        if (pemInfo != null) {
            String pemType = pemInfo.getType();

            // PEM type of OpenSSL?
            if (OPENSSL_RSA_PVK_PEM_TYPE.equals(pemType) || OPENSSL_DSA_PVK_PEM_TYPE.equals(pemType) ||
                OPENSSL_EC_PVK_PEM_TYPE.equals(pemType)) {

                // Encrypted? It is if PEM contains appropriate header attributes/values
                PemAttributes pemAttributes = pemInfo.getAttributes();

                if ((pemAttributes != null) && (pemAttributes.get(PROC_TYPE_ATTR_NAME) != null) &&
                    (pemAttributes.get(PROC_TYPE_ATTR_NAME).getValue().equals(PROC_TYPE_ATTR_VALUE)) &&
                    (pemAttributes.get(DEK_INFO_ATTR_NAME) != null)) {
                    return ENCRYPTED;
                } else {
                    return UNENCRYPTED;
                }
            }
        }

        // In ASN.1 format?
        try {
            // If OpenSSL will be a sequence of 9 (RSA) or 6 (DSA) integers or 2-4 mixed elements (EC)
            ASN1Primitive key = ASN1Primitive.fromByteArray(openSsl);

            if (key instanceof ASN1Sequence) {
                ASN1Sequence seq = (ASN1Sequence) key;

                // handle EC structure first (RFC 5915)
                //   ECPrivateKey ::= SEQUENCE {
                //	     version        INTEGER { ecPrivkeyVer1(1) } (ecPrivkeyVer1),
                //	     privateKey     OCTET STRING,
                //	     parameters [0] ECParameters {{ NamedCurve }} OPTIONAL,
                //	     publicKey  [1] BIT STRING OPTIONAL
                //	   }
                if ((seq.size() >= 2) && (seq.size() <= 4) && seq.getObjectAt(0) instanceof ASN1Integer) {
                    BigInteger version = ((ASN1Integer) seq.getObjectAt(0)).getValue();
                    if (version.equals(VERSION_EC)) {
                        if (seq.getObjectAt(1) instanceof ASN1OctetString) {
                            return UNENCRYPTED; // ASN.1 OpenSSL is always unencrypted
                        } else {
                            return null; // Not OpenSSL
                        }
                    }
                }

                for (int i = 0; i < seq.size(); i++) {
                    if (!(seq.getObjectAt(i) instanceof ASN1Integer)) {
                        return null; // Not OpenSSL
                    }
                }

                if ((seq.size() == 9) || (seq.size() == 6)) {
                    return UNENCRYPTED; // ASN.1 OpenSSL is always unencrypted
                }
            }
        } catch (IOException ex) {
            return null; // Not an OpenSSL file
        }

        return null; // Not an OpenSSL file
    }

    private static byte[] generateSalt(int size) {
        return PRNG.generate(size);
    }

    private static byte[] deriveKeyFromPassword(Password password, byte[] salt, int keySize) throws CryptoException {
        // Derive a key of the required size from the supplied password and salt

        byte[] key = new byte[(keySize / 8)];

        byte[] passwordBytes = password.toByteArray();

        // Keep digesting password and salt until we have enough bytes for key
        MessageDigest messageDigest = DigestUtil.getMessageDigester(DigestType.MD5);

        byte[] result;
        int currentPos = 0;

        while (currentPos < key.length) {
            messageDigest.update(passwordBytes);

            // Only use the first 8 bytes of salt
            messageDigest.update(salt, 0, 8);

            result = messageDigest.digest();

            // How many more bytes do we need to complete key?
            int stillNeed = key.length - currentPos;

            // If this round gave us more than we need - trim it down
            if (result.length > stillNeed) {
                byte[] b = new byte[stillNeed];
                System.arraycopy(result, 0, b, 0, b.length);
                result = b;
            }

            // Add round's digest to key
            System.arraycopy(result, 0, key, currentPos, result.length);
            currentPos += result.length;

            if (currentPos < key.length) {
                // Next round starts with a hash this round's hash
                messageDigest.reset();
                messageDigest.update(result);
            }
        }

        return key;
    }

    private static byte[] hexToBytes(String hex) {
        byte[] b = new byte[hex.length() / 2];
        for (int i = 0; i < b.length; i++) {
            String hexByte = hex.substring(2 * i, 2 * (i + 1));
            b[i] = (byte) Integer.parseInt(hexByte, 16);
        }
        return b;
    }

    private static String bytesToHex(byte[] bytes) {
        int expectedHexLength = (bytes.length * 2);

        String hex = new BigInteger(1, bytes).toString(16).toUpperCase();

        int leadingZeros = expectedHexLength - hex.length();

        for (int i = 0; i < leadingZeros; i++) {
            hex = "0" + hex; // Use leading zeros to ensure correct string
            // length
        }

        return hex;
    }

    private static Cipher createCipher(String transformation, byte[] key, byte[] iv, int operation)
            throws CryptoException {
        SecretKey secretKey = new SecretKeySpec(key, transformation);
        IvParameterSpec ivParams = new IvParameterSpec(iv);

        try {
            Cipher cipher = Cipher.getInstance(transformation, KSE.BC);
            cipher.init(operation, secretKey, ivParams);
            return cipher;
        } catch (GeneralSecurityException ex) {
            throw new CryptoException(
                    MessageFormat.format(res.getString("OpenSslCreateCipherFailed.exception.message"), transformation),
                    ex);
        }
    }

    /**
     * Legacy format only supported for RSA, DSA, and EC curves (except Edwards)
     */
    public static boolean isOpenSSLFormatSupported(PrivateKey privateKey) {
        KeyPairType keyPairType = KeyPairUtil.getKeyPairType(privateKey);
        return OPEN_SSL_SUPPORTED_KEYPAIR.contains(keyPairType);
    }

}
