protected <T extends KeySpec> T engineGetKeySpec(Key key, Class<T> keySpec)
     throws InvalidKeySpecException {
   try {
     // convert key to one of our keys
     // this also verifies that the key is a valid RSA key and ensures
     // that the encoding is X.509/PKCS#8 for public/private keys
     key = engineTranslateKey(key);
   } catch (InvalidKeyException e) {
     throw new InvalidKeySpecException(e);
   }
   if (key instanceof RSAPublicKey) {
     RSAPublicKey rsaKey = (RSAPublicKey) key;
     if (rsaPublicKeySpecClass.isAssignableFrom(keySpec)) {
       return keySpec.cast(new RSAPublicKeySpec(rsaKey.getModulus(), rsaKey.getPublicExponent()));
     } else if (x509KeySpecClass.isAssignableFrom(keySpec)) {
       return keySpec.cast(new X509EncodedKeySpec(key.getEncoded()));
     } else {
       throw new InvalidKeySpecException(
           "KeySpec must be RSAPublicKeySpec or " + "X509EncodedKeySpec for RSA public keys");
     }
   } else if (key instanceof RSAPrivateKey) {
     if (pkcs8KeySpecClass.isAssignableFrom(keySpec)) {
       return keySpec.cast(new PKCS8EncodedKeySpec(key.getEncoded()));
     } else if (rsaPrivateCrtKeySpecClass.isAssignableFrom(keySpec)) {
       if (key instanceof RSAPrivateCrtKey) {
         RSAPrivateCrtKey crtKey = (RSAPrivateCrtKey) key;
         return keySpec.cast(
             new RSAPrivateCrtKeySpec(
                 crtKey.getModulus(),
                 crtKey.getPublicExponent(),
                 crtKey.getPrivateExponent(),
                 crtKey.getPrimeP(),
                 crtKey.getPrimeQ(),
                 crtKey.getPrimeExponentP(),
                 crtKey.getPrimeExponentQ(),
                 crtKey.getCrtCoefficient()));
       } else {
         throw new InvalidKeySpecException("RSAPrivateCrtKeySpec can only be used with CRT keys");
       }
     } else if (rsaPrivateKeySpecClass.isAssignableFrom(keySpec)) {
       RSAPrivateKey rsaKey = (RSAPrivateKey) key;
       return keySpec.cast(
           new RSAPrivateKeySpec(rsaKey.getModulus(), rsaKey.getPrivateExponent()));
     } else {
       throw new InvalidKeySpecException(
           "KeySpec must be RSAPrivate(Crt)KeySpec or "
               + "PKCS8EncodedKeySpec for RSA private keys");
     }
   } else {
     // should not occur, caught in engineTranslateKey()
     throw new InvalidKeySpecException("Neither public nor private key");
   }
 }
 /**
  * Creates a {@link javax.crypto.Cipher} instance from a {@link javax.crypto.CipherSpi}.
  *
  * @param cipherSpi the {@link javax.cyrpto.CipherSpi} instance
  * @param transformation the transformation cipherSpi was obtained for
  * @return a {@link javax.crypto.Cipher} instance
  * @throws Throwable in case of an error
  */
 private static Cipher getCipher(final CipherSpi cipherSpi, final String transformation)
     throws Throwable {
   /* This API isn't public - usually you're expected to simply use Cipher.getInstance().
    * Due to the signed-jar restriction for JCE providers that is not an option, so we
    * use one of the private constructors of Cipher.
    */
   final Class<Cipher> cipherClass = Cipher.class;
   final Constructor<Cipher> cipherConstructor =
       cipherClass.getDeclaredConstructor(CipherSpi.class, String.class);
   cipherConstructor.setAccessible(true);
   try {
     return cipherConstructor.newInstance(cipherSpi, transformation);
   } catch (final InvocationTargetException e) {
     throw e.getCause();
   }
 }
  /**
   * Get the reflection of a private and protected method.
   *
   * Required since {@link java.lang.Class#getMethod(String, Class...)
   * only find public method
   *
   * @param clazz the class to search in
   * @param name the method name
   * @param parameterTypes the method's parameter types
   * @return the method reflection
   * @throws NoSuchMethodException
   */
  private static Method getMethod(
      final Class<?> clazz, final String name, final Class<?>... parameterTypes)
      throws NoSuchMethodException {
    try {
      /* Try to fetch the method reflection */
      return clazz.getDeclaredMethod(name, parameterTypes);
    } catch (final NoSuchMethodException e1) {
      /* No such method. Search base class if there is one,
       * otherwise complain */
      if (clazz.getSuperclass() != null) {
        try {
          /* Try to fetch the method from the base class */
          return getMethod(clazz.getSuperclass(), name, parameterTypes);
        } catch (final NoSuchMethodException e2) {
          /* We don't want the exception to indicate that the
           * *base* class didn't contain a matching method, but
           * rather that the subclass didn't.
           */
          final StringBuilder s = new StringBuilder();
          s.append(clazz.getName());
          s.append("(");
          boolean first = true;
          for (final Class<?> parameterType : parameterTypes) {
            if (!first) s.append(", ");
            first = false;
            s.append(parameterType);
          }
          s.append(")");

          throw new NoSuchMethodException(s.toString());
        }
      } else {
        /* No base class, complain */
        throw e1;
      }
    }
  }
  /**
   * Replacement for JCA/JCE's {@link javax.crypto.Cipher#getInstance}. The original method only
   * accepts JCE providers from signed jars, which prevents us from bundling our cryptography
   * provider Bouncy Caster with the application.
   *
   * @param transformation the transformation to find an implementation for
   */
  public static Cipher getCipher(final String transformation) {
    try {
      /* Split the transformation into algorithm, mode and padding */

      final Matcher transformation_matcher =
          s_transformation_pattern.matcher(transformation.toUpperCase());
      if (!transformation_matcher.matches())
        throw new RuntimeException("Transformation " + transformation + " is invalid");

      final String algorithm = transformation_matcher.group(1);
      final String mode = transformation_matcher.group(3);
      final String padding = transformation_matcher.group(4);
      final boolean isBareAlgorithm = (mode == null) && (padding == null);

      /* Build the property values we need to search for. */

      final String algorithmModePadding =
          !isBareAlgorithm ? algorithm + "/" + mode + "/" + padding : null;
      final String algorithmMode = !isBareAlgorithm ? algorithm + "/" + mode : null;
      final String algorithmPadding = !isBareAlgorithm ? algorithm + "//" + padding : null;

      /* Search the provider for implementations. We ask for more specific (i.e matching
       * the requested mode and or padding) implementation first, then fall back to more
       * generals ones which we then must configure for the mode and padding.
       */

      final CipherSpi cipherSpi;

      if (!isBareAlgorithm && (resolveProperty(Provider, "Cipher", algorithmModePadding) != null)) {
        @SuppressWarnings("unchecked")
        final Class<? extends CipherSpi> cipherSpiClass =
            (Class<? extends CipherSpi>)
                Class.forName(resolveProperty(Provider, "Cipher", algorithmModePadding));
        cipherSpi = cipherSpiClass.newInstance();
      } else if (!isBareAlgorithm && (resolveProperty(Provider, "Cipher", algorithmMode) != null)) {
        @SuppressWarnings("unchecked")
        final Class<? extends CipherSpi> cipherSpiClass =
            (Class<? extends CipherSpi>)
                Class.forName(resolveProperty(Provider, "Cipher", algorithmMode));
        cipherSpi = cipherSpiClass.newInstance();
        if (!isBareAlgorithm) cipherSpiSetPadding(cipherSpi, padding);
      } else if (!isBareAlgorithm
          && (resolveProperty(Provider, "Cipher", algorithmPadding) != null)) {
        @SuppressWarnings("unchecked")
        final Class<? extends CipherSpi> cipherSpiClass =
            (Class<? extends CipherSpi>)
                Class.forName(resolveProperty(Provider, "Cipher", algorithmPadding));
        cipherSpi = cipherSpiClass.newInstance();
        if (!isBareAlgorithm) cipherSpiSetMode(cipherSpi, mode);
      } else if (resolveProperty(Provider, "Cipher", algorithm) != null) {
        @SuppressWarnings("unchecked")
        final Class<? extends CipherSpi> cipherSpiClass =
            (Class<? extends CipherSpi>)
                Class.forName(resolveProperty(Provider, "Cipher", algorithm));
        cipherSpi = cipherSpiClass.newInstance();
        if (!isBareAlgorithm) {
          cipherSpiSetMode(cipherSpi, mode);
          cipherSpiSetPadding(cipherSpi, padding);
        }
      } else {
        throw new RuntimeException(
            "Provider "
                + Provider.getName()
                + " ("
                + Provider.getClass()
                + ") does not implement "
                + transformation);
      }

      /* Create a {@link javax.crypto.Cipher} instance from the {@link javax.crypto.CipherSpi} the provider gave us */

      s_logger.info("Using SPI " + cipherSpi.getClass() + " for " + transformation);
      return getCipher(cipherSpi, transformation.toUpperCase());
    } catch (final RuntimeException e) {
      throw e;
    } catch (final Error e) {
      throw e;
    } catch (final Throwable e) {
      throw new RuntimeException(
          "Provider "
              + Provider.getName()
              + " ("
              + Provider.getClass()
              + ") failed to instanciate "
              + transformation,
          e);
    }
  }