/**
  * Returns the value attached to a provider property.
  *
  * <p>Supports aliases, i.e. if there is no property named type.name but one named
  * Alg.Alias.type.name, the value of Alg.Alias.type.name is assumed to be the <b>name</b> of the
  * actual property.
  *
  * @param provider JCE provider
  * @param type type (Cipher, Algorithm, ...)
  * @param name transformation
  * @return the properties value which usually is the implementing class'es name
  */
 private static String resolveProperty(
     final Provider provider, final String type, final String name) {
   if (Provider.getProperty(type + "." + name) != null)
     return Provider.getProperty(type + "." + name);
   else if (Provider.getProperty("Alg.Alias." + type + "." + name) != null)
     return resolveProperty(
         provider, type, Provider.getProperty("Alg.Alias." + type + "." + name));
   else return null;
 }
  /**
   * 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);
    }
  }