- extended J8 mode supplying PBE and GCM based en-/decryption.
- extracted abstract classes (templates)
- extended tests
- use JUnit 5
- typo fixes


git-svn-id: https://svn.apache.org/repos/asf/turbine/fulcrum/trunk/yaafi-crypto@1853652 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/pom.xml b/pom.xml
index b9e4699..05cb3a9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -54,9 +54,10 @@
   <dependencies>
     <!-- testing dependencies -->
     <dependency>
-      <groupId>junit</groupId>
-      <artifactId>junit</artifactId>
-      <version>4.12</version>
+      <!-- to gt junit 5 -->
+      <groupId>org.apache.fulcrum</groupId>
+      <artifactId>fulcrum-testcontainer</artifactId>
+      <version>1.0.8</version>
       <scope>test</scope>
     </dependency>
   </dependencies>
diff --git a/src/java/org/apache/fulcrum/jce/crypto/CryptoParameters.java b/src/java/org/apache/fulcrum/jce/crypto/CryptoParameters.java
index 5c72346..efdac7a 100644
--- a/src/java/org/apache/fulcrum/jce/crypto/CryptoParameters.java
+++ b/src/java/org/apache/fulcrum/jce/crypto/CryptoParameters.java
@@ -49,8 +49,15 @@
      *  @see https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html
      *  
      *  PBEWithHmacSHA256AndAES_256/CBC/PKCS5Padding, PBEWithHmacSHA256AndAES_128/CBC/PKCS5Padding
+     *  or
+     *  GCM
      */
-    String ALGORITHM_J8 = "PBEWithHmacSHA256AndAES_256"; //"PBEWithHmacSHA256AndAES_128 ";
+    String ALGORITHM_J8_PBE = "PBEWithHmacSHA256AndAES_256"; //"PBEWithHmacSHA256AndAES_128 ";
+    String ALGORITHM_J8_GCM = "AES/GCM/NoPadding";
+    
+    public enum TYPES {
+        PBE, GCM
+    }
     
     /**
      * Prefix to decrypted hex hash to get a clue, what to use and what it is.
diff --git a/src/java/org/apache/fulcrum/jce/crypto/CryptoStreamFactoryImpl.java b/src/java/org/apache/fulcrum/jce/crypto/CryptoStreamFactoryImpl.java
index 30c7ee1..3c92617 100644
--- a/src/java/org/apache/fulcrum/jce/crypto/CryptoStreamFactoryImpl.java
+++ b/src/java/org/apache/fulcrum/jce/crypto/CryptoStreamFactoryImpl.java
@@ -21,13 +21,10 @@
 
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.OutputStream;
 import java.security.GeneralSecurityException;
 import java.security.Key;
 
 import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.CipherOutputStream;
 import javax.crypto.SecretKeyFactory;
 import javax.crypto.spec.PBEKeySpec;
 import javax.crypto.spec.PBEParameterSpec;
@@ -52,7 +49,7 @@
  * @author <a href="mailto:maakus@earthlink.net">Markus Hahn</a>
  */
 
-public class CryptoStreamFactoryImpl implements CryptoStreamFactory
+public class CryptoStreamFactoryImpl extends CryptoStreamFactoryTemplate implements CryptoStreamFactory
 {
     /** the salt for the PBE algorithm */
     protected byte[] salt;
@@ -80,14 +77,16 @@
      * Factory method to get a default instance
      * @return an instance of the CryptoStreamFactory
      */
-    public synchronized static CryptoStreamFactory getInstance()
+    public static CryptoStreamFactory getInstance()
     {
-        if( CryptoStreamFactoryImpl.instance == null )
-        {
-            CryptoStreamFactoryImpl.instance = new CryptoStreamFactoryImpl();
+        synchronized(CryptoStreamFactoryImpl.class ) {
+            if( CryptoStreamFactoryImpl.instance == null )
+            {
+                CryptoStreamFactoryImpl.instance = new CryptoStreamFactoryImpl();
+            }
+    
+            return CryptoStreamFactoryImpl.instance;
         }
-
-        return CryptoStreamFactoryImpl.instance;
     }
 
     /**
@@ -125,82 +124,6 @@
     }
 
     /**
-     * @see org.apache.fulcrum.jce.crypto.CryptoStreamFactory#getInputStream(java.io.InputStream, String)
-     */
-    public InputStream getInputStream(InputStream is, String decryptionMode) throws GeneralSecurityException, IOException {
-
-        InputStream result = null;
-
-        if( "auto".equalsIgnoreCase(decryptionMode) )
-        {
-            result = getSmartInputStream(is);
-        }
-        else if( "true".equalsIgnoreCase(decryptionMode) )
-        {
-            result = getInputStream(is);
-        }
-        else
-        {
-            result = is;
-        }
-        return result;
-    }
-
-    /**
-     * @see org.apache.fulcrum.jce.crypto.CryptoStreamFactory#getInputStream(java.io.InputStream, String, char[])
-     */
-    public InputStream getInputStream(InputStream is, String decryptionMode, char[] password) throws GeneralSecurityException, IOException {
-
-        InputStream result = null;
-
-        if( "auto".equalsIgnoreCase(decryptionMode) )
-        {
-            result = getSmartInputStream(is, password);
-        }
-        else if( "true".equalsIgnoreCase(decryptionMode) )
-        {
-            result = getInputStream(is, password);
-        }
-        else
-        {
-            result = is;
-        }
-        return result;
-    }
-
-    /**
-     * @see org.apache.fulcrum.jce.crypto.CryptoStreamFactory#getInputStream(java.io.InputStream)
-     */
-    public InputStream getInputStream( InputStream is )
-        throws GeneralSecurityException, IOException
-    {
-        Cipher cipher = this.createCipher( Cipher.DECRYPT_MODE, PasswordFactory.getInstance().create() );
-        return new CipherInputStream( is, cipher );
-    }
-
-    /**
-     * @see org.apache.fulcrum.jce.crypto.CryptoStreamFactory#getInputStream(java.io.InputStream,char[])
-     */
-    public InputStream getInputStream( InputStream is, char[] password )
-        throws GeneralSecurityException, IOException
-    {
-        Cipher cipher = this.createCipher( Cipher.DECRYPT_MODE, password );
-        return new CipherInputStream( is, cipher );
-    }
-
-    /**
-     * @see org.apache.fulcrum.jce.crypto.CryptoStreamFactory#getSmartInputStream(java.io.InputStream)
-     */
-    public InputStream getSmartInputStream(InputStream is)
-        throws GeneralSecurityException, IOException
-    {
-        return this.getSmartInputStream(
-            is,
-            PasswordFactory.getInstance().create()
-            );
-    }
-
-    /**
      * @see org.apache.fulcrum.jce.crypto.CryptoStreamFactory#getSmartInputStream(java.io.InputStream,char[])
      */
     public InputStream getSmartInputStream(InputStream is, char[] password )
@@ -218,25 +141,6 @@
     }
 
     /**
-     * @see org.apache.fulcrum.jce.crypto.CryptoStreamFactory#getOutputStream(java.io.OutputStream)
-     */
-    public OutputStream getOutputStream( OutputStream os )
-        throws GeneralSecurityException, IOException
-    {
-        Cipher cipher = this.createCipher( Cipher.ENCRYPT_MODE, PasswordFactory.getInstance().create() );
-        return new CipherOutputStream( os, cipher );    }
-
-    /**
-     * @see org.apache.fulcrum.jce.crypto.CryptoStreamFactory#getOutputStream(java.io.OutputStream, char[])
-     */
-    public OutputStream getOutputStream( OutputStream os, char[] password )
-        throws GeneralSecurityException, IOException
-    {
-        Cipher cipher = this.createCipher( Cipher.ENCRYPT_MODE, password );
-        return new CipherOutputStream( os, cipher );
-    }
-
-    /**
      * @return Returns the algorithm.
      */
     protected String getAlgorithm()
@@ -275,7 +179,7 @@
      * @return the key
      * @throws GeneralSecurityException creating the key failed
      */
-    private Key createKey( char[] password )
+    protected Key createKey( char[] password )
         throws GeneralSecurityException
     {
         SecretKeyFactory keyFactory;
@@ -303,7 +207,7 @@
      * @throws GeneralSecurityException creating a cipher failed
      * @throws IOException creating a cipher failed
      */
-    private Cipher createCipher( int mode, char[] password )
+    protected Cipher createCipher( int mode, char[] password )
         throws GeneralSecurityException, IOException
     {
         Cipher cipher;
diff --git a/src/java/org/apache/fulcrum/jce/crypto/CryptoStreamFactoryJ8.java b/src/java/org/apache/fulcrum/jce/crypto/CryptoStreamFactoryJ8.java
index 5c73b71..8dc7be6 100644
--- a/src/java/org/apache/fulcrum/jce/crypto/CryptoStreamFactoryJ8.java
+++ b/src/java/org/apache/fulcrum/jce/crypto/CryptoStreamFactoryJ8.java
@@ -27,7 +27,7 @@
 /**
  * Interface for creating encrypting/decrypting streams. 
  *
- * @author <a href="mailto:siegfried.goeschl@it20one.at">Siegfried Goeschl </a>
+ * @author <a href="mailto:gk@apache.org">Georg Kallidis </a>
  */
 
 public interface CryptoStreamFactoryJ8 extends CryptoStreamFactory
@@ -44,4 +44,17 @@
      */
     OutputStream getOutputStream(InputStream is, OutputStream os, char[] password)
         throws GeneralSecurityException, IOException;
+    
+    /**
+     * Creates input stream based on the decryption mode
+     * using the default password.
+     *
+     * @param is the input stream to be wrapped
+     * @param decryptionMode the decryption mode (true|false|auto)
+     * @return an decrypting input stream
+     * @throws GeneralSecurityException creating the input stream failed
+     * @throws IOException creating the input stream failed
+     */
+    InputStream getInputStream(InputStream is, char[] password)
+            throws GeneralSecurityException, IOException;
 }
diff --git a/src/java/org/apache/fulcrum/jce/crypto/CryptoStreamFactoryJ8Impl.java b/src/java/org/apache/fulcrum/jce/crypto/CryptoStreamFactoryJ8Impl.java
index 5d465e8..93462eb 100644
--- a/src/java/org/apache/fulcrum/jce/crypto/CryptoStreamFactoryJ8Impl.java
+++ b/src/java/org/apache/fulcrum/jce/crypto/CryptoStreamFactoryJ8Impl.java
@@ -58,7 +58,7 @@
  * @author <a href="mailto:siegfried.goeschl@it20one.at">Siegfried Goeschl </a>
  * @author <a href="mailto:maakus@earthlink.net">Markus Hahn</a>
  */
-
+@Deprecated
 public final class CryptoStreamFactoryJ8Impl extends CryptoStreamFactoryImpl implements CryptoStreamFactoryJ8
 {
 
@@ -106,7 +106,7 @@
         this.salt =  generateSalt();
         this.count = CryptoParameters.COUNT_J8;
         this.providerName = PROVIDERNAME;
-        this.algorithm = CryptoParameters.ALGORITHM_J8;
+        this.algorithm = CryptoParameters.ALGORITHM_J8_PBE;
     }
     
     /**
@@ -131,7 +131,7 @@
         this.salt = salt;
         this.count = count;
         this.providerName = PROVIDERNAME;
-        this.algorithm = CryptoParameters.ALGORITHM_J8;
+        this.algorithm = CryptoParameters.ALGORITHM_J8_PBE;
     }
 
 
diff --git a/src/java/org/apache/fulcrum/jce/crypto/CryptoStreamFactoryJ8Template.java b/src/java/org/apache/fulcrum/jce/crypto/CryptoStreamFactoryJ8Template.java
new file mode 100644
index 0000000..31485b4
--- /dev/null
+++ b/src/java/org/apache/fulcrum/jce/crypto/CryptoStreamFactoryJ8Template.java
@@ -0,0 +1,172 @@
+package org.apache.fulcrum.jce.crypto;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.AlgorithmParameters;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.crypto.Cipher;
+
+import org.apache.fulcrum.jce.crypto.CryptoParameters.TYPES;
+import org.apache.fulcrum.jce.crypto.algo.CryptoStreamGCMImpl;
+import org.apache.fulcrum.jce.crypto.algo.CryptoStreamPBEImpl;
+
+/**
+ * Concrete factory for creating encrypting/decrypting streams. 
+ * 
+ * 
+ **/
+public abstract class CryptoStreamFactoryJ8Template extends CryptoStreamFactoryImpl implements CryptoStreamFactoryJ8
+{
+
+    protected static final int SALT_SIZE = 16; //might increase cipher length
+    protected static final int KEY_SIZE = 256;
+
+    /** the default instances */
+    protected static Map<TYPES,CryptoStreamFactoryJ8Template> instances = new ConcurrentHashMap();
+    
+    protected AlgorithmParameters algorithmParameters;// used only for debugging
+   
+    public CryptoStreamFactoryJ8Template() {
+       
+    }
+
+    /**
+     * Factory method to get a default instance
+     * @return an instance of the CryptoStreamFactory
+     */
+    public static CryptoStreamFactoryJ8 getInstance(TYPES type) 
+    {
+        synchronized (CryptoStreamFactoryJ8Template.class) {
+            if( !instances.containsKey(type) )
+            {
+                try {
+                    instances.put(type, 
+                            (type.equals(TYPES.PBE))? new CryptoStreamPBEImpl():
+                                new CryptoStreamGCMImpl()
+                            );
+                } catch (GeneralSecurityException e) {
+                    e.printStackTrace();
+                    throw new RuntimeException(e.getMessage());
+                }
+            }
+    
+            return instances.get(type);
+        }
+    }
+
+
+    /**
+     * Constructor
+     *
+     * @param salt the salt for the PBE algorithm
+     * @param count the iteration for PBEParameterSpec
+     * @paramn type {@link TYPES}
+     */
+    public CryptoStreamFactoryJ8Template( byte[] salt, int count, TYPES type)
+    {
+        this.salt = salt;
+        this.count = count;
+        this.providerName = PROVIDERNAME;
+        this.algorithm = type.equals(TYPES.PBE)? CryptoParameters.ALGORITHM_J8_PBE:
+            CryptoParameters.ALGORITHM_J8_GCM;
+    }
+
+
+    /**
+     * @see org.apache.fulcrum.jce.crypto.CryptoStreamFactory#getSmartInputStream(java.io.InputStream)
+     */
+    @Override
+    public InputStream getSmartInputStream(InputStream is)
+        throws GeneralSecurityException, IOException
+    {
+        return this.getSmartInputStream(
+            is,
+            PasswordFactory.getInstance("SHA-256").create()
+            );
+    }
+
+    /**
+     * @see org.apache.fulcrum.jce.crypto.CryptoStreamFactory#getInputStream(java.io.InputStream,char[])
+     */
+    @Override
+    public InputStream getInputStream( InputStream is, char[] password )
+        throws GeneralSecurityException, IOException
+    {
+        byte[] encrypted =  this.createCipher( is, Cipher.DECRYPT_MODE, password );
+        InputStream eis = new ByteArrayInputStream(encrypted);
+        return eis;
+    }
+
+    
+    @Override
+    public OutputStream getOutputStream(InputStream is, OutputStream os, char[] password)
+            throws GeneralSecurityException, IOException {
+        byte[] encrypted =  this.createCipher( is, Cipher.ENCRYPT_MODE, password );
+        InputStream eis = new ByteArrayInputStream(encrypted);
+        StreamUtil.copy(eis, os);
+        return os;
+    }
+
+    /**
+     * Create a PBE key.
+     *
+     * @param password the password to use.
+     * @param salt if provided this is used, otherweise {@link #getSalt()}.
+     * @return the key
+     * @throws GeneralSecurityException creating the key failed
+     */
+    protected abstract Key createKey( char[] password, byte[] salt ) 
+            throws GeneralSecurityException;
+
+    /**
+     * Create a Cipher.
+     *
+     * @param mode the cipher mode
+     * @param password the password
+     * @return an instance of a cipher
+     * @throws GeneralSecurityException creating a cipher failed
+     * @throws IOException creating a cipher failed
+     */
+    protected abstract byte[] createCipher(InputStream is, int mode, char[] password )
+        throws GeneralSecurityException, IOException;
+    
+    protected byte[] generateSalt() throws GeneralSecurityException {
+        SecureRandom random;
+        try {
+            random = SecureRandom.getInstance("SHA1PRNG");
+            byte[] salt = new byte[SALT_SIZE ];
+            random.nextBytes(salt);
+            return salt;
+        } catch (NoSuchAlgorithmException e) {
+            throw new GeneralSecurityException(e);  
+        }
+    }
+
+}
diff --git a/src/java/org/apache/fulcrum/jce/crypto/CryptoStreamFactoryTemplate.java b/src/java/org/apache/fulcrum/jce/crypto/CryptoStreamFactoryTemplate.java
new file mode 100644
index 0000000..347e218
--- /dev/null
+++ b/src/java/org/apache/fulcrum/jce/crypto/CryptoStreamFactoryTemplate.java
@@ -0,0 +1,167 @@
+package org.apache.fulcrum.jce.crypto;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+
+/**
+ * Concrete factory for creating encrypting/decrypting streams. The
+ * implementation uses the JCA (Java Crypto Extension) supplied
+ * by SUN (using SunJCE 1.42).
+ *
+ * The implementation uses as PBEWithMD5AndDES for encryption which
+ * should be sufficent for most applications.
+ *
+ * The implementation also supplies a default password in the case that
+ * the programmer don't want to have additional hassles. It is easy to
+ * reengineer the password being used but much better than a hard-coded
+ * password in the application.
+ *
+ * The code uses parts from Markus Hahn's Blowfish library found at
+ * http://blowfishj.sourceforge.net/
+ *
+ * @author <a href="mailto:siegfried.goeschl@it20one.at">Siegfried Goeschl </a>
+ * @author <a href="mailto:maakus@earthlink.net">Markus Hahn</a>
+ */
+
+public abstract class CryptoStreamFactoryTemplate implements CryptoStreamFactory
+{
+
+    /** the default instance */
+    protected static CryptoStreamFactory instance;
+    
+    public static CryptoStreamFactory getInstance() {
+        return instance;
+    }
+
+    public static void setInstance(CryptoStreamFactory instance) {
+        CryptoStreamFactoryTemplate.instance = instance;
+    }
+
+    /**
+     * @see org.apache.fulcrum.jce.crypto.CryptoStreamFactory#getInputStream(java.io.InputStream, String)
+     */
+    public InputStream getInputStream(InputStream is, String decryptionMode) throws GeneralSecurityException, IOException {
+
+        InputStream result = null;
+
+        if( "auto".equalsIgnoreCase(decryptionMode) )
+        {
+            result = getSmartInputStream(is);
+        }
+        else if( "true".equalsIgnoreCase(decryptionMode) )
+        {
+            result = getInputStream(is);
+        }
+        else
+        {
+            result = is;
+        }
+        return result;
+    }
+
+    /**
+     * @see org.apache.fulcrum.jce.crypto.CryptoStreamFactory#getInputStream(java.io.InputStream, String, char[])
+     */
+    public InputStream getInputStream(InputStream is, String decryptionMode, char[] password) throws GeneralSecurityException, IOException {
+
+        InputStream result = null;
+
+        if( "auto".equalsIgnoreCase(decryptionMode) )
+        {
+            result = getSmartInputStream(is, password);
+        }
+        else if( "true".equalsIgnoreCase(decryptionMode) )
+        {
+            result = getInputStream(is, password);
+        }
+        else
+        {
+            result = is;
+        }
+        return result;
+    }
+
+    /**
+     * @see org.apache.fulcrum.jce.crypto.CryptoStreamFactory#getInputStream(java.io.InputStream)
+     */
+    public InputStream getInputStream( InputStream is )
+        throws GeneralSecurityException, IOException
+    {
+        Cipher cipher = this.createCipher( Cipher.DECRYPT_MODE, PasswordFactory.getInstance().create() );
+        return new CipherInputStream( is, cipher );
+    }
+
+    /**
+     * @see org.apache.fulcrum.jce.crypto.CryptoStreamFactory#getInputStream(java.io.InputStream,char[])
+     */
+    public InputStream getInputStream( InputStream is, char[] password )
+        throws GeneralSecurityException, IOException
+    {
+        Cipher cipher = this.createCipher( Cipher.DECRYPT_MODE, password );
+        return new CipherInputStream( is, cipher );
+    }
+
+    /**
+     * @see org.apache.fulcrum.jce.crypto.CryptoStreamFactory#getSmartInputStream(java.io.InputStream)
+     */
+    public InputStream getSmartInputStream(InputStream is)
+        throws GeneralSecurityException, IOException
+    {
+        return this.getSmartInputStream(
+            is,
+            PasswordFactory.getInstance().create()
+            );
+    }
+
+    /**
+     * @see org.apache.fulcrum.jce.crypto.CryptoStreamFactory#getSmartInputStream(java.io.InputStream,char[])
+     */
+    public abstract InputStream getSmartInputStream(InputStream is, char[] password )
+        throws GeneralSecurityException, IOException;
+
+    /**
+     * @see org.apache.fulcrum.jce.crypto.CryptoStreamFactory#getOutputStream(java.io.OutputStream)
+     */
+    public OutputStream getOutputStream( OutputStream os )
+        throws GeneralSecurityException, IOException
+    {
+        Cipher cipher = this.createCipher( Cipher.ENCRYPT_MODE, PasswordFactory.getInstance().create() );
+        return new CipherOutputStream( os, cipher );    }
+
+    /**
+     * @see org.apache.fulcrum.jce.crypto.CryptoStreamFactory#getOutputStream(java.io.OutputStream, char[])
+     */
+    public OutputStream getOutputStream( OutputStream os, char[] password )
+        throws GeneralSecurityException, IOException
+    {
+        Cipher cipher = this.createCipher( Cipher.ENCRYPT_MODE, password );
+        return new CipherOutputStream( os, cipher );
+    }
+    protected abstract Cipher createCipher(int encryptMode, char[] password) throws GeneralSecurityException, IOException;
+
+}
diff --git a/src/java/org/apache/fulcrum/jce/crypto/CryptoUtilJ8.java b/src/java/org/apache/fulcrum/jce/crypto/CryptoUtilJ8.java
index e0d1fe2..3287aa5 100644
--- a/src/java/org/apache/fulcrum/jce/crypto/CryptoUtilJ8.java
+++ b/src/java/org/apache/fulcrum/jce/crypto/CryptoUtilJ8.java
@@ -19,11 +19,14 @@
  * under the License.
  */
 
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.security.GeneralSecurityException;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.fulcrum.jce.crypto.CryptoParameters.TYPES;
 
 /**
  * Helper class to provde generic functions to work with CryptoStreams.
@@ -38,28 +41,59 @@
 public final class CryptoUtilJ8 extends CryptoUtil {
 
     
-    /** the default instance */
-    private static CryptoUtilJ8 instance;
+    protected TYPES type;// default see instance
+   
+    
+    public TYPES getType() {
+        return type;
+    }
+
+    /** the typed default instances */    
+    private static Map<TYPES,CryptoUtilJ8> cryptoUtilJ8s = new ConcurrentHashMap();
     
     
     /**
      * Factory method to get a default instance
+     * @param type 
      * @return an instance of the CryptoStreamFactory
      */
-    public synchronized static CryptoUtilJ8 getInstance()
+    public static CryptoUtilJ8 getInstance(TYPES type)
     {
-        if( CryptoUtilJ8.instance == null )
-        {
-            CryptoUtilJ8.instance = new CryptoUtilJ8();
+        synchronized (CryptoUtilJ8.class) {
+            if( !cryptoUtilJ8s.containsKey(type) )
+            {
+                cryptoUtilJ8s.put(type, new CryptoUtilJ8(type) );
+            }
+    
+            return cryptoUtilJ8s.get(type);
         }
-
-        return CryptoUtilJ8.instance;
+    }
+    
+    /**
+     * Factory method to get a default instance
+     * 
+     * default type PDC
+     * @return an instance of the CryptoStreamFactory
+     */
+    public static CryptoUtilJ8 getInstance()
+    {
+        synchronized (CryptoUtilJ8.class) {
+            if( cryptoUtilJ8s.isEmpty() && !cryptoUtilJ8s.containsKey(TYPES.PBE) )
+            {
+                cryptoUtilJ8s.put(TYPES.PBE, new CryptoUtilJ8(TYPES.PBE) );
+            }
+    
+            return cryptoUtilJ8s.get(TYPES.PBE);
+        }
+    }
+    
+    public CryptoUtilJ8(TYPES type) {
+        this.type = type;
     }
     
     public CryptoUtilJ8() {
-        useClearTextHeader = true;
     }
-    
+
     /**
      * Copies from a source to a target object using encryption and a caller
      * supplied CryptoStreamFactory.
@@ -105,6 +139,6 @@
      * @return the CryptoStreamFactory to be used
      */
     public CryptoStreamFactory getCryptoStreamFactory() {
-        return CryptoStreamFactoryJ8Impl.getInstance();
+            return CryptoStreamFactoryJ8Template.getInstance(type);
     }
 }
diff --git a/src/java/org/apache/fulcrum/jce/crypto/algo/CryptoStreamGCMImpl.java b/src/java/org/apache/fulcrum/jce/crypto/algo/CryptoStreamGCMImpl.java
new file mode 100644
index 0000000..ed0a005
--- /dev/null
+++ b/src/java/org/apache/fulcrum/jce/crypto/algo/CryptoStreamGCMImpl.java
@@ -0,0 +1,211 @@
+package org.apache.fulcrum.jce.crypto.algo;
+
+import java.io.ByteArrayOutputStream;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ *  * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.fulcrum.jce.crypto.CryptoParameters;
+import org.apache.fulcrum.jce.crypto.CryptoStreamFactoryJ8Template;
+import org.apache.fulcrum.jce.crypto.StreamUtil;
+
+/**
+ * Concrete implementation for creating encrypting/decrypting streams. The
+ * implementation uses the JCA (Java Crypto Extension) supplied
+ * by SUN (using SunJCE 1.42).
+ *
+ * The implementation uses @see {@link CryptoParameters#ALGORITHM_J8_GCM} for encryption which
+ * should be sufficent for most applications.
+ *
+ * The implementation also supplies a default password in the case that
+ * the programmer don't want to have additional hassles. It is easy to
+ * reengineer the password being used but much better than a hard-coded
+ * password in the application.
+ *
+ *
+ * @author <a href="mailto:gk@apache.org">Georg Kallidis</a>
+ */
+
+public final class CryptoStreamGCMImpl extends CryptoStreamFactoryJ8Template
+{  
+
+    protected static final int IV_SIZE = 12;
+    /**
+     * Constructor
+     */
+    public CryptoStreamGCMImpl() throws GeneralSecurityException
+    {
+        this.salt =  generateSalt();
+        this.count = CryptoParameters.COUNT_J8;// not used
+        this.providerName = PROVIDERNAME;
+        this.algorithm = CryptoParameters.ALGORITHM_J8_GCM;
+    }
+
+
+    /**
+     * Constructor
+     *
+     * @param salt the salt for the PBE algorithm
+     * @param count the iteration for PBEParameterSpec
+     */
+    public CryptoStreamGCMImpl( byte[] salt, int count) throws GeneralSecurityException
+    {
+        this.salt = salt;
+        this.count = count;
+        this.providerName = PROVIDERNAME;
+        this.algorithm = CryptoParameters.ALGORITHM_J8_GCM;
+    }
+
+    /**
+     * Create a AES/GCM key.
+     *
+     * @param password the password to use.
+     * @param salt if provided this is used, otherweise {@link #getSalt()}.
+     * @return the key
+     * @throws GeneralSecurityException creating the key failed
+     */
+    @Override
+    protected Key createKey( char[] password, byte[] salt ) 
+            throws GeneralSecurityException
+    {
+
+        SecretKey key = new SecretKeySpec(((salt == null)? this.getSalt(): salt), "AES"); 
+        return key;
+    }
+
+    /**
+     * Create a Cipher.
+     *
+     * @param mode the cipher mode
+     * @param password the password
+     * @return an instance of a cipher
+     * @throws GeneralSecurityException creating a cipher failed
+     * @throws IOException creating a cipher failed
+     */
+    @Override
+    protected byte[] createCipher(InputStream is, int mode, char[] password )
+        throws GeneralSecurityException, IOException
+    {
+        Cipher cipher;
+        
+        ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
+        long total = StreamUtil.copy(is, bos);
+        byte[] input = bos.toByteArray();
+        
+        byte[] ciphertext = null;  
+        byte[] salt = null;
+        byte[] iv = null;
+        
+        if (mode == Cipher.DECRYPT_MODE) {   
+            
+            ByteBuffer byteBuffer = ByteBuffer.wrap(input);
+            salt = new byte[ SALT_SIZE ];
+            byteBuffer.get(salt);
+            iv = new byte[ IV_SIZE ];
+            byteBuffer.get(iv);
+            ciphertext = new byte[byteBuffer.remaining()];
+            byteBuffer.get(ciphertext);
+            
+//            salt = Arrays.copyOfRange(input, 0, SALT_SIZE );
+//            iv = Arrays.copyOfRange(input, salt.length, salt.length + 16 );
+//            ciphertext = Arrays.copyOfRange(input, salt.length + iv.length, input.length);// cut out salt and iv
+        }
+        
+        Key key = this.createKey( password, salt );
+        
+        if( this.getProviderName() == null )
+        {
+            cipher = Cipher.getInstance( this.getAlgorithm() );
+        }
+        else
+        {
+            cipher = Cipher.getInstance( this.getAlgorithm(), this.getProviderName() );
+        }
+        
+        // save
+        if (mode == Cipher.DECRYPT_MODE) {
+            
+            GCMParameterSpec gcmParamSpec = new GCMParameterSpec(128, iv);
+            cipher.init( mode, key, gcmParamSpec );
+            
+            //cipher.init( mode, key, algorithmParameters );
+            ciphertext = cipher.doFinal(ciphertext); // actually the unencrypted bytes
+        }
+        
+        // save
+        if (mode == Cipher.ENCRYPT_MODE) {        
+            iv = generateIV();
+            GCMParameterSpec gcmParamSpec = new GCMParameterSpec(128, iv);
+            
+            salt = this.getSalt();
+            cipher.init( mode, key, gcmParamSpec );
+
+            //algorithmParameters = cipher.getParameters();
+            
+            // might update with associated Data
+            // cipher.updateAAD(associatedData );// not supported PBEWithHmacSHA256AndAES_256
+            
+            byte[] result = cipher.doFinal(input);
+            //iv = cipher.getIV(); // AES has 128bit block size, but iv is 16bit 
+           
+            // Salt and IV need to be stored with the result, otherwise we can't decrypt the message later.
+            ByteBuffer byteBuffer = ByteBuffer.allocate(salt.length + iv.length + result.length);
+            ciphertext = byteBuffer.put(salt).put(iv).put(result).array();
+            
+//            ciphertext = new byte[salt.length + iv.length + result.length];
+//            
+//            System.arraycopy(salt, 0, ciphertext, 0, salt.length);
+//            System.arraycopy(iv, 0, ciphertext, salt.length, iv.length);
+//            System.arraycopy(result, 0, ciphertext, salt.length + iv.length, result.length);// push after salt and iv  
+        }
+        return ciphertext;
+    }
+    
+    private byte[] generateIV( ) throws GeneralSecurityException {
+        SecureRandom random;
+        try {
+            random = SecureRandom.getInstance("SHA1PRNG");
+            byte[] iv = new byte[ IV_SIZE ];
+            random.nextBytes(iv);
+            return iv;
+        } catch (NoSuchAlgorithmException e) {
+            throw new GeneralSecurityException(e);  
+        }
+    }
+
+    @Override
+    protected Cipher createCipher(int encryptMode, char[] password) throws GeneralSecurityException, IOException {
+        throw new RuntimeException("not provided for this implementation");
+    }
+
+
+}
diff --git a/src/java/org/apache/fulcrum/jce/crypto/algo/CryptoStreamPBEImpl.java b/src/java/org/apache/fulcrum/jce/crypto/algo/CryptoStreamPBEImpl.java
new file mode 100644
index 0000000..de1cb4c
--- /dev/null
+++ b/src/java/org/apache/fulcrum/jce/crypto/algo/CryptoStreamPBEImpl.java
@@ -0,0 +1,221 @@
+package org.apache.fulcrum.jce.crypto.algo;
+
+import java.io.ByteArrayOutputStream;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ *  * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+import javax.crypto.Cipher;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+
+import org.apache.fulcrum.jce.crypto.CryptoParameters;
+import org.apache.fulcrum.jce.crypto.CryptoStreamFactoryJ8Template;
+import org.apache.fulcrum.jce.crypto.StreamUtil;
+
+/**
+ * Concrete implementation for creating encrypting/decrypting streams. The
+ * implementation uses the JCA (Java Crypto Extension) supplied
+ * by SUN (using SunJCE 1.42).
+ *
+ * The implementation uses as @see {@link CryptoParameters#ALGORITHM_J8_PBE} for encryption which
+ * should be sufficent for most applications.
+ *
+ * The implementation also supplies a default password in the case that
+ * the programmer don't want to have additional hassles. It is easy to
+ * reengineer the password being used but much better than a hard-coded
+ * password in the application.
+ *
+ * The code uses parts from Markus Hahn's Blowfish library found at
+ * http://blowfishj.sourceforge.net/
+ *
+ * @author <a href="mailto:gk@apache.org">Georg Kallidis</a>
+ * @author <a href="mailto:siegfried.goeschl@it20one.at">Siegfried Goeschl </a>
+ * @author <a href="mailto:maakus@earthlink.net">Markus Hahn</a>
+ */
+
+public final class CryptoStreamPBEImpl extends CryptoStreamFactoryJ8Template
+{
+
+    protected static final int IV_SIZE = 16;
+    /**
+     * Constructor
+     */
+    public CryptoStreamPBEImpl() throws GeneralSecurityException
+    {
+        this.salt =  generateSalt();
+        this.count = CryptoParameters.COUNT_J8;
+        this.providerName = PROVIDERNAME;
+        this.algorithm = CryptoParameters.ALGORITHM_J8_PBE;
+    }
+    
+    /**
+     * Constructor
+     *
+     * @param salt the salt for the PBE algorithm
+     * @param count the iteration for PBEParameterSpec
+     */
+    public CryptoStreamPBEImpl( byte[] salt, int count) throws GeneralSecurityException
+    {
+        this.salt = salt;
+        this.count = count;
+        this.providerName = PROVIDERNAME;
+        this.algorithm = CryptoParameters.ALGORITHM_J8_PBE;
+    }
+
+    /**
+     * Create a PBE key.
+     *
+     * @param password the password to use.
+     * @param salt if provided this is used, otherweise {@link #getSalt()}.
+     * @return the key
+     * @throws GeneralSecurityException creating the key failed
+     */
+    @Override
+    protected Key createKey( char[] password, byte[] salt ) 
+            throws GeneralSecurityException
+    {
+        SecretKeyFactory keyFactory;
+        String algorithm = this.getAlgorithm();
+        
+        PBEKeySpec keySpec = new PBEKeySpec(password, (salt == null)? this.getSalt(): salt, this.getCount(), KEY_SIZE );
+
+        byte[] encodedTmp = null;
+        try {
+            if( getProviderName() == null )
+            {
+                keyFactory = SecretKeyFactory.getInstance( algorithm );
+            }
+            else
+            {
+                keyFactory = SecretKeyFactory.getInstance( algorithm, this.getProviderName() );
+            }
+            return keyFactory.generateSecret(keySpec);
+            
+        } catch (NoSuchAlgorithmException e) {
+            throw new GeneralSecurityException(e);
+        } finally {
+            if (encodedTmp != null) {
+                Arrays.fill(encodedTmp, (byte)0); 
+            }
+            if (keySpec != null) {
+                keySpec.clearPassword();
+            }
+        }
+    }
+
+    /**
+     * Create a Cipher.
+     *
+     * @param mode the cipher mode
+     * @param password the password
+     * @return an instance of a cipher
+     * @throws GeneralSecurityException creating a cipher failed
+     * @throws IOException creating a cipher failed
+     */
+    @Override
+    protected byte[] createCipher(InputStream is, int mode, char[] password )
+        throws GeneralSecurityException, IOException
+    {
+        Cipher cipher;
+        PBEParameterSpec paramSpec = null; 
+        
+        ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
+        long total = StreamUtil.copy(is, bos);
+        byte[] input = bos.toByteArray();
+        
+        byte[] ciphertext = null;
+        
+        byte[] salt = null;
+        byte[] iv = null;
+        if (mode == Cipher.DECRYPT_MODE) {   
+            
+            ByteBuffer byteBuffer = ByteBuffer.wrap(input);
+            salt = new byte[SALT_SIZE ];
+            byteBuffer.get(salt);
+            iv = new byte[ IV_SIZE ];
+            byteBuffer.get(iv);
+            ciphertext = new byte[byteBuffer.remaining()];
+            byteBuffer.get(ciphertext);
+            
+//            salt = Arrays.copyOfRange(input, 0, SALT_SIZE );
+//            iv = Arrays.copyOfRange(input, salt.length, salt.length + 16 );
+//            ciphertext = Arrays.copyOfRange(input, salt.length + iv.length, input.length);// cut out salt and iv
+        }
+        
+        Key key = this.createKey( password, salt );
+        
+        if( this.getProviderName() == null )
+        {
+            cipher = Cipher.getInstance( this.getAlgorithm() );
+        }
+        else
+        {
+            cipher = Cipher.getInstance( this.getAlgorithm(), this.getProviderName() );
+        }
+        
+        // save
+        if (mode == Cipher.DECRYPT_MODE) {
+           
+            paramSpec = new PBEParameterSpec( salt, this.getCount(), new IvParameterSpec(iv) );
+     
+            cipher.init( mode, key, paramSpec );
+            //cipher.init( mode, key, algorithmParameters );
+            ciphertext = cipher.doFinal(ciphertext); // actually the unencrypted bytes
+        }
+        
+        // save
+        if (mode == Cipher.ENCRYPT_MODE) {        
+            paramSpec = new PBEParameterSpec( this.getSalt(), this.getCount() );
+            salt = paramSpec.getSalt();
+            cipher.init( mode, key, paramSpec );   
+            //algorithmParameters = cipher.getParameters();
+ 
+            byte[] result = cipher.doFinal(input);
+            iv = cipher.getIV(); // AES has 128bit block size, but iv is 16bit 
+           
+            // Salt and IV need to be stored with the result, otherwise we can't decrypt the message later.
+            ByteBuffer byteBuffer = ByteBuffer.allocate(salt.length + iv.length + result.length);
+            ciphertext = byteBuffer.put(salt).put(iv).put(result).array();
+            
+//            ciphertext = new byte[salt.length + iv.length + result.length];         
+//            System.arraycopy(salt, 0, ciphertext, 0, salt.length);
+//            System.arraycopy(iv, 0, ciphertext, salt.length, iv.length);
+//            System.arraycopy(result, 0, ciphertext, salt.length + iv.length, result.length);// push after salt and iv  
+        }
+        return ciphertext;
+    }
+
+
+    @Override
+    protected Cipher createCipher(int encryptMode, char[] password) throws GeneralSecurityException, IOException {
+        throw new RuntimeException("not provided for this implementation");
+    }
+
+}
diff --git a/src/java/org/apache/fulcrum/jce/crypto/cli/MainJ8.java b/src/java/org/apache/fulcrum/jce/crypto/cli/MainJ8.java
index a0ad7f6..5f7bcab 100644
--- a/src/java/org/apache/fulcrum/jce/crypto/cli/MainJ8.java
+++ b/src/java/org/apache/fulcrum/jce/crypto/cli/MainJ8.java
@@ -84,7 +84,7 @@
      */
     public static void printHelp()
     {
-        System.out.println("\r\n*** Command line tool for encrypting/decrypting strings/files ***\r\n*** algorithm based on "+ CryptoParameters.ALGORITHM_J8+ "***\r\n");
+        System.out.println("\r\n*** Command line tool for encrypting/decrypting strings/files ***\r\n*** algorithm based on "+ CryptoParameters.ALGORITHM_J8_PBE+ "***\r\n");
         System.out.println( "*** Usage: ***\r\n");
         System.out.println("java -cp target\\classes; "+ MainJ8.class.getName()+ " <operation mode:file:string> <coding mode:enc|dec> <password> <path|string> [target]\r\ne.g.\r\n");
         System.out.println( MainJ8.class.getSimpleName()+ " file [enc|dec] passwd source [target]");
@@ -152,7 +152,7 @@
         }
         else if( cipherMode.equals("enc") )
         {
-            System.out.println("Enrypting " + sourceFile.getAbsolutePath() );
+            System.out.println("Encrypting " + sourceFile.getAbsolutePath() );
             cryptoUtilJ8.encrypt( fis, baos, password );
             fis.close();
 
diff --git a/src/test/org/apache/fulcrum/jce/crypto/CryptoUtilJ8ParameterizedTest.java b/src/test/org/apache/fulcrum/jce/crypto/CryptoUtilJ8ParameterizedTest.java
new file mode 100644
index 0000000..2ecbd60
--- /dev/null
+++ b/src/test/org/apache/fulcrum/jce/crypto/CryptoUtilJ8ParameterizedTest.java
@@ -0,0 +1,328 @@
+package org.apache.fulcrum.jce.crypto;
+
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fulcrum.jce.crypto.CryptoParameters.TYPES;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+
+/**
+ * Test suite for crypto functionality
+ *
+ * @author <a href="mailto:siegfried.goeschl@it20one.at">Siegfried Goeschl</a>
+ */
+public class CryptoUtilJ8ParameterizedTest {
+    /** the password to be used */
+    private String password;
+
+    /** the test data directory */
+    private File testDataDirectory;
+
+    /** the temp data director */
+    private File tempDataDirectory;
+    
+    private List<CryptoUtilJ8> cryptoUtilJ8s = new ArrayList<>();
+    
+
+    /**
+     * Constructor
+     */
+    public CryptoUtilJ8ParameterizedTest() {
+
+        this.password = "mysecret";
+        this.testDataDirectory = new File("./src/test/data");
+        this.tempDataDirectory = new File("./target/temp");
+        this.tempDataDirectory.mkdirs();
+    }
+
+    
+//    @ParameterizedTest
+//    @EnumSource( TYPES.class )
+//    public void setUp(TYPES type) throws Exception {
+//        cryptoUtilJ8 = CryptoUtilJ8.getInstance(type); // (TYPES.PBE);
+//    }
+
+    /**
+     * @return Returns the password.
+     */
+    protected char[] getPassword() {
+        return password.toCharArray();
+    }
+
+    /**
+     * @return Returns the tempDataDirectory.
+     */
+    protected File getTempDataDirectory() {
+        return tempDataDirectory;
+    }
+
+    /**
+     * @return Returns the testDataDirectory.
+     */
+    protected File getTestDataDirectory() {
+        return testDataDirectory;
+    }
+    
+    @AfterEach
+    public void setup() {
+        cryptoUtilJ8s.clear(); 
+    }
+    
+    /** Encrypt a text file 
+     * @throws Exception Generic exception
+     */
+    @ParameterizedTest
+    @EnumSource( TYPES.class )
+    public void testTextEncryption(TYPES type)  {
+        
+        cryptoUtilJ8s.add(CryptoUtilJ8.getInstance(type));
+        File sourceFile = new File(this.getTestDataDirectory(), "plain.txt");
+        File targetFile = new File(this.getTempDataDirectory(), "plain.j8.enc.txt");
+        
+        cryptoUtilJ8s.forEach(cuj8 -> {
+            try {
+                System.out.println("checking "+ cuj8.getType());
+                cuj8.encrypt(sourceFile, targetFile, this.getPassword());
+            } catch (GeneralSecurityException e) {
+                e.printStackTrace();
+                fail();
+            } catch (IOException e) {
+                e.printStackTrace();
+                fail();
+            }
+        } );
+    }
+
+    /** Decrypt a text file 
+     * @throws Exception Generic exception
+     */
+    @ParameterizedTest
+    @EnumSource( TYPES.class )
+    public void testTextDecryption(TYPES type) {
+        cryptoUtilJ8s.add(CryptoUtilJ8.getInstance(type));
+            cryptoUtilJ8s.forEach(cuj8 -> {
+                System.out.println("checking "+ cuj8.getType());
+                try {
+                    File sourceFile = new File(this.getTestDataDirectory(), "plain.txt");
+                    File targetFile = new File(this.getTempDataDirectory(), "plain.j8.enc.txt");
+                    cuj8.encrypt(sourceFile, targetFile, this.getPassword());
+                    
+                    File sourceFile2 = targetFile;
+                    File targetFile2 = new File(this.getTempDataDirectory(), "plain.j8.dec.txt");
+                    cuj8.decrypt(sourceFile2, targetFile2.getAbsolutePath(), this.getPassword());
+                    assertEquals(
+                            new String(Files.readAllBytes( Paths.get(sourceFile.toURI())) ), 
+                            new String(Files.readAllBytes( Paths.get(targetFile2.toURI())) )
+                            );
+                } catch (GeneralSecurityException | IOException e) {
+                    e.printStackTrace();
+                    fail();
+                }
+            });
+    }
+    
+    /** Encrypt a PDF file 
+     * 
+     * @throws Exception Generic exception
+     */
+    @ParameterizedTest
+    @EnumSource( TYPES.class )
+    public void testPdfEncryption(TYPES type) {
+        cryptoUtilJ8s.add(CryptoUtilJ8.getInstance(type));
+        File sourceFile = new File(this.getTestDataDirectory(), "plain.pdf");
+        File targetFile = new File(this.getTempDataDirectory(), "plain.j8.enc.pdf");
+        cryptoUtilJ8s.forEach(cuj8 -> { 
+            try {
+                cuj8.encrypt(sourceFile, targetFile, this.getPassword());
+            } catch (GeneralSecurityException | IOException e) {
+                e.printStackTrace();
+                fail();
+            }    
+        });        
+    }
+
+    /** Decrypt a PDF file 
+     * 
+     * @throws Exception Generic exception
+     */
+    @ParameterizedTest
+    @EnumSource( TYPES.class )
+    public void testPdfDecryption(TYPES type)  {
+        cryptoUtilJ8s.add(CryptoUtilJ8.getInstance(type));
+        //testPdfEncryption();
+        cryptoUtilJ8s.forEach(cuj8 -> { 
+            try {
+                File sourceFile = new File(this.getTestDataDirectory(), "plain.pdf");
+                File targetFile = new File(this.getTempDataDirectory(), "plain.j8.enc.pdf");
+                cuj8.encrypt(sourceFile, targetFile, this.getPassword());
+                
+                File sourceFile2 = targetFile;
+                File targetFile2 = new File(this.getTempDataDirectory(), "plain.j8.dec.pdf");
+                cuj8.decrypt(sourceFile2, targetFile2, this.getPassword());
+                
+                assertEquals(
+                        new String(Files.readAllBytes( Paths.get(sourceFile.toURI())) ), 
+                        new String(Files.readAllBytes( Paths.get(targetFile2.toURI())) )
+                        );
+            } catch (GeneralSecurityException | IOException e) {
+                e.printStackTrace();
+                fail();
+            }    
+        }); 
+
+    }
+
+    /** Test encryption and decryption of Strings
+     * 
+     *  @throws Exception Generic exception
+     */
+    @ParameterizedTest
+    @EnumSource( TYPES.class )
+    public void testStringEncryption(TYPES type) {
+        cryptoUtilJ8s.add(CryptoUtilJ8.getInstance(type));
+        char[] testVector = new char[513];
+
+        for (int i = 0; i < testVector.length; i++) {
+            testVector[i] = (char) i;
+        }
+
+        String source = new String(testVector);
+        cryptoUtilJ8s.forEach(cuj8 -> { 
+            String cipherText;
+            String plainText;
+            try {
+                cipherText = cuj8.encryptString(source, this.getPassword());
+                plainText = cuj8.decryptString(cipherText, this.getPassword());
+                assertEquals(source, plainText, source +" is not equal with " + plainText); 
+            } catch (GeneralSecurityException | IOException e) {
+                e.printStackTrace();
+                fail();
+            }
+           
+        });
+        
+
+    }
+
+    /** Test encryption and decryption of Strings
+     * @throws Exception Generic exception
+     */
+    @ParameterizedTest
+    @EnumSource( TYPES.class )
+    public void testStringHandling(TYPES type)  {
+        cryptoUtilJ8s.add(CryptoUtilJ8.getInstance(type));
+        String source = "Nobody knows the toubles I have seen ...";
+        cryptoUtilJ8s.forEach(cuj8 -> { 
+            String cipherText;
+            try {
+                cipherText = cuj8.encryptString(source, this.getPassword());
+                String plainText = cuj8.decryptString(cipherText, this.getPassword());
+                assertEquals(source, plainText);   
+            } catch (GeneralSecurityException | IOException e) {
+                e.printStackTrace();
+                fail();
+            }
+         
+        });
+
+    }
+
+    /** Test creating a password
+     * @throws Exception Generic exception
+     */
+    @Test
+    public void testPasswordFactory() throws Exception {
+        char[] result = null;
+        result = PasswordFactory.getInstance("SHA-256").create();
+        System.out.println("random pw:" + new String(result));
+        result = PasswordFactory.getInstance("SHA-256",10_000).create(this.getPassword());
+        System.out.println("password pw with seed:" + new String(result));
+        assertNotNull(result);
+        return;
+    }
+    
+    /** Test encryption and decryption of binary data
+     * @throws Exception Generic exception
+     */
+    @ParameterizedTest
+    @EnumSource( TYPES.class )
+    public void testBinaryHandling(TYPES type) throws Exception {
+        cryptoUtilJ8s.add(CryptoUtilJ8.getInstance(type));
+        cryptoUtilJ8s.forEach(cuj8 -> { 
+            byte[] source = new byte[256];
+            byte[] result = null;
+
+            for (int i = 0; i < source.length; i++) {
+                source[i] = (byte) i;
+            }
+
+            ByteArrayOutputStream cipherText = new ByteArrayOutputStream();
+            ByteArrayOutputStream plainText = new ByteArrayOutputStream();
+            try {
+                cuj8.encrypt(source, cipherText, this.getPassword());
+                cuj8.decrypt(cipherText, plainText, this.getPassword());
+            } catch (GeneralSecurityException | IOException e) {
+                e.printStackTrace();
+                fail();
+            }
+            result = plainText.toByteArray();
+
+            for (int i = 0; i < source.length; i++) {
+                if (source[i] != result[i]) {
+                    fail("Binary data are different at position " + i);
+                }
+            }
+        });
+
+       
+
+    }
+    
+    /** Test encryption and decryption of Strings 
+     * @throws Exception Generic exception
+     */
+    @ParameterizedTest
+    @EnumSource( TYPES.class )
+    public void testStringWithPasswordEncryption(TYPES type) {
+        char[] password = "57cb-4a23-d838-45222".toCharArray();
+        String source = "e02c-3b76-ff1e-5d9a1";
+        cryptoUtilJ8s.add(CryptoUtilJ8.getInstance(type));
+        cryptoUtilJ8s.forEach(cuj8 -> { 
+            System.out.println("checking "+ cuj8.getType());
+            String cipherText = null;
+            try {
+                cipherText = cuj8.encryptString(source, password);
+                System.out.println(cipherText);// about 128
+                
+                System.out.println("length for " + cuj8.getType() + " is:" +cipherText.length());// about 128
+                if (cuj8.type == TYPES.PBE) {
+                    assertEquals(128, cipherText.length()); // 128bytes + 10 bytes for cleartext
+                } 
+                CryptoStreamFactoryJ8Template.setInstance(null);
+                String plainText = cuj8.decryptString(cipherText, password);
+                assertEquals(source, plainText);
+            } catch (GeneralSecurityException | IOException e) {
+                e.printStackTrace();
+                fail();
+            }
+            
+        });      
+
+    }
+
+}
diff --git a/src/test/org/apache/fulcrum/jce/crypto/CryptoUtilJ8Test.java b/src/test/org/apache/fulcrum/jce/crypto/CryptoUtilJ8Test.java
index 8855d69..455b165 100644
--- a/src/test/org/apache/fulcrum/jce/crypto/CryptoUtilJ8Test.java
+++ b/src/test/org/apache/fulcrum/jce/crypto/CryptoUtilJ8Test.java
@@ -1,14 +1,23 @@
 package org.apache.fulcrum.jce.crypto;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.List;
 
-import org.junit.Before;
-import org.junit.Test;
+import org.apache.fulcrum.jce.crypto.CryptoParameters.TYPES;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
 
 
 /**
@@ -16,7 +25,6 @@
  *
  * @author <a href="mailto:siegfried.goeschl@it20one.at">Siegfried Goeschl</a>
  */
-
 public class CryptoUtilJ8Test {
     /** the password to be used */
     private String password;
@@ -27,7 +35,8 @@
     /** the temp data director */
     private File tempDataDirectory;
     
-    private CryptoUtilJ8 cryptoUtilJ8;
+    private static List<CryptoUtilJ8> cryptoUtilJ8s = new ArrayList<>();
+    
 
     /**
      * Constructor
@@ -46,10 +55,28 @@
      * 
      * @throws Exception Generic exception
      */
-    @Before
-    public void setUp() throws Exception {
-        cryptoUtilJ8 = CryptoUtilJ8.getInstance();
+    @BeforeAll
+    public static void setUp() throws Exception {
+        cryptoUtilJ8s.clear();
+        for (TYPES type : CryptoParameters.TYPES.values()) {
+            cryptoUtilJ8s.add(CryptoUtilJ8.getInstance(type));
+        }
+        for (CryptoUtilJ8 cryptoUtilJ8 : cryptoUtilJ8s) {
+            System.out.println("registered cryptoUtilsJ8: "+ cryptoUtilJ8.getType() );
+            System.out.println( ((CryptoStreamFactoryJ8Template)cryptoUtilJ8.getCryptoStreamFactory()).getAlgorithm());
+        }
+
     }
+    @AfterAll
+    public static void destroy() {
+        cryptoUtilJ8s.clear();
+    }
+    
+//    @ParameterizedTest
+//    @EnumSource( TYPES.class )
+//    public void setUp(TYPES type) throws Exception {
+//        cryptoUtilJ8 = CryptoUtilJ8.getInstance(type); // (TYPES.PBE);
+//    }
 
     /**
      * @return Returns the password.
@@ -76,21 +103,47 @@
      * @throws Exception Generic exception
      */
     @Test
-    public void testTextEncryption() throws Exception {
+    public void testTextEncryption()  {
+        
         File sourceFile = new File(this.getTestDataDirectory(), "plain.txt");
         File targetFile = new File(this.getTempDataDirectory(), "plain.j8.enc.txt");
-        cryptoUtilJ8.encrypt(sourceFile, targetFile, this.getPassword());
+        
+        cryptoUtilJ8s.forEach(cuj8 -> {
+            try {
+                cuj8.encrypt(sourceFile, targetFile, this.getPassword());
+            } catch (GeneralSecurityException e) {
+                e.printStackTrace();
+                fail();
+            } catch (IOException e) {
+                e.printStackTrace();
+                fail();
+            }
+        } );
     }
 
     /** Decrypt a text file 
      * @throws Exception Generic exception
      */
     @Test
-    public void testTextDecryption() throws Exception {
-        testTextEncryption();
-        File sourceFile = new File(this.getTempDataDirectory(), "plain.j8.enc.txt");
-        File targetFile = new File(this.getTempDataDirectory(), "plain.j8.dec.txt");
-        cryptoUtilJ8.decrypt(sourceFile, targetFile.getAbsolutePath(), this.getPassword());
+    public void testTextDecryption() {           
+            cryptoUtilJ8s.forEach(cuj8 -> { 
+                try {
+                    File sourceFile = new File(this.getTestDataDirectory(), "plain.txt");
+                    File targetFile = new File(this.getTempDataDirectory(), "plain.j8.enc.txt");
+                    cuj8.encrypt(sourceFile, targetFile, this.getPassword());
+                    
+                    File sourceFile2 = new File(this.getTempDataDirectory(), "plain.j8.enc.txt");;
+                    File targetFile2 = new File(this.getTempDataDirectory(), "plain.j8.dec.txt");
+                    cuj8.decrypt(sourceFile2, targetFile2.getAbsolutePath(), this.getPassword());
+                    assertEquals(
+                            new String(Files.readAllBytes( Paths.get(sourceFile.toURI())) ), 
+                            new String(Files.readAllBytes( Paths.get(targetFile2.toURI())) )
+                            );
+                } catch (GeneralSecurityException | IOException e) {
+                    e.printStackTrace();
+                    fail();
+                }
+            });
     }
     
     /** Encrypt a PDF file 
@@ -98,10 +151,17 @@
      * @throws Exception Generic exception
      */
     @Test
-    public void testPdfEncryption() throws Exception {
+    public void testPdfEncryption() {
         File sourceFile = new File(this.getTestDataDirectory(), "plain.pdf");
         File targetFile = new File(this.getTempDataDirectory(), "plain.j8.enc.pdf");
-        cryptoUtilJ8.encrypt(sourceFile, targetFile, this.getPassword());
+        cryptoUtilJ8s.forEach(cuj8 -> { 
+            try {
+                cuj8.encrypt(sourceFile, targetFile, this.getPassword());
+            } catch (GeneralSecurityException | IOException e) {
+                e.printStackTrace();
+                fail();
+            }    
+        });        
     }
 
     /** Decrypt a PDF file 
@@ -109,11 +169,28 @@
      * @throws Exception Generic exception
      */
     @Test
-    public void testPdfDecryption() throws Exception {
-        testPdfEncryption();
-        File sourceFile = new File(this.getTempDataDirectory(), "plain.j8.enc.pdf");
-        File targetFile = new File(this.getTempDataDirectory(), "plain.j8.dec.pdf");
-        cryptoUtilJ8.decrypt(sourceFile, targetFile, this.getPassword());
+    public void testPdfDecryption()  {
+        //testPdfEncryption();
+        cryptoUtilJ8s.forEach(cuj8 -> { 
+            try {
+                File sourceFile = new File(this.getTestDataDirectory(), "plain.pdf");
+                File targetFile = new File(this.getTempDataDirectory(), "plain.j8.enc.pdf");
+                cuj8.encrypt(sourceFile, targetFile, this.getPassword());
+                
+                File sourceFile2 = new File(this.getTempDataDirectory(), "plain.j8.enc.pdf");
+                File targetFile2 = new File(this.getTempDataDirectory(), "plain.j8.dec.pdf");
+                cuj8.decrypt(sourceFile2, targetFile2, this.getPassword());
+                
+                assertEquals(
+                        new String(Files.readAllBytes( Paths.get(sourceFile.toURI())) ), 
+                        new String(Files.readAllBytes( Paths.get(targetFile2.toURI())) )
+                        );
+            } catch (GeneralSecurityException | IOException e) {
+                e.printStackTrace();
+                fail();
+            }    
+        }); 
+
     }
 
     /** Test encryption and decryption of Strings
@@ -121,7 +198,7 @@
      *  @throws Exception Generic exception
      */
     @Test
-    public void testStringEncryption() throws Exception {
+    public void testStringEncryption() {
         char[] testVector = new char[513];
 
         for (int i = 0; i < testVector.length; i++) {
@@ -129,20 +206,42 @@
         }
 
         String source = new String(testVector);
-        String cipherText = cryptoUtilJ8.encryptString(source, this.getPassword());
-        String plainText = cryptoUtilJ8.decryptString(cipherText, this.getPassword());
-        assertEquals(source +" is not equal with " + plainText, source, plainText);
+        cryptoUtilJ8s.forEach(cuj8 -> { 
+            String cipherText;
+            String plainText;
+            try {
+                cipherText = cuj8.encryptString(source, this.getPassword());
+                plainText = cuj8.decryptString(cipherText, this.getPassword());
+                assertEquals(source, plainText, source +" is not equal with " + plainText); 
+            } catch (GeneralSecurityException | IOException e) {
+                e.printStackTrace();
+                fail();
+            }
+           
+        });
+        
+
     }
 
     /** Test encryption and decryption of Strings
      * @throws Exception Generic exception
      */
     @Test
-    public void testStringHandling() throws Exception {
+    public void testStringHandling()  {
         String source = "Nobody knows the toubles I have seen ...";
-        String cipherText = cryptoUtilJ8.encryptString(source, this.getPassword());
-        String plainText = cryptoUtilJ8.decryptString(cipherText, this.getPassword());
-        assertEquals(source, plainText);
+        cryptoUtilJ8s.forEach(cuj8 -> { 
+            String cipherText;
+            try {
+                cipherText = cuj8.encryptString(source, this.getPassword());
+                String plainText = cuj8.decryptString(cipherText, this.getPassword());
+                assertEquals(source, plainText);   
+            } catch (GeneralSecurityException | IOException e) {
+                e.printStackTrace();
+                fail();
+            }
+         
+        });
+
     }
 
     /** Test creating a password
@@ -152,9 +251,9 @@
     public void testPasswordFactory() throws Exception {
         char[] result = null;
         result = PasswordFactory.getInstance("SHA-256").create();
-        System.out.println(new String(result));
+        System.out.println("random pw:" + new String(result));
         result = PasswordFactory.getInstance("SHA-256",10_000).create(this.getPassword());
-        System.out.println(new String(result));
+        System.out.println("password pw with seed:" + new String(result));
         assertNotNull(result);
         return;
     }
@@ -164,41 +263,65 @@
      */
     @Test
     public void testBinaryHandling() throws Exception {
-        byte[] source = new byte[256];
-        byte[] result = null;
+        
+        cryptoUtilJ8s.forEach(cuj8 -> { 
+            byte[] source = new byte[256];
+            byte[] result = null;
 
-        for (int i = 0; i < source.length; i++) {
-            source[i] = (byte) i;
-        }
-
-        ByteArrayOutputStream cipherText = new ByteArrayOutputStream();
-        ByteArrayOutputStream plainText = new ByteArrayOutputStream();
-
-        cryptoUtilJ8.encrypt(source, cipherText, this.getPassword());
-        cryptoUtilJ8.decrypt(cipherText, plainText, this.getPassword());
-
-        result = plainText.toByteArray();
-
-        for (int i = 0; i < source.length; i++) {
-            if (source[i] != result[i]) {
-                fail("Binary data are different at position " + i);
+            for (int i = 0; i < source.length; i++) {
+                source[i] = (byte) i;
             }
-        }
+
+            ByteArrayOutputStream cipherText = new ByteArrayOutputStream();
+            ByteArrayOutputStream plainText = new ByteArrayOutputStream();
+            try {
+                cuj8.encrypt(source, cipherText, this.getPassword());
+                cuj8.decrypt(cipherText, plainText, this.getPassword());
+            } catch (GeneralSecurityException | IOException e) {
+                e.printStackTrace();
+                fail();
+            }
+            result = plainText.toByteArray();
+
+            for (int i = 0; i < source.length; i++) {
+                if (source[i] != result[i]) {
+                    fail("Binary data are different at position " + i);
+                }
+            }
+        });
+
+       
+
     }
     
     /** Test encryption and decryption of Strings 
      * @throws Exception Generic exception
      */
     @Test
-    public void testStringWithPasswordEncryption() throws Exception {
+    public void testStringWithPasswordEncryption() {
         char[] password = "57cb-4a23-d838-45222".toCharArray();
         String source = "e02c-3b76-ff1e-5d9a1";
-        String cipherText = cryptoUtilJ8.encryptString(source, password);
-        System.out.println(cipherText);// about 128
-        assertEquals(138, cipherText.length()); // 128bytes + 10 bytes for cleartext
-        CryptoStreamFactoryJ8Impl.setInstance(null);
-        String plainText = cryptoUtilJ8.decryptString(cipherText, password);
-        assertEquals(source, plainText);
+        
+        cryptoUtilJ8s.forEach(cuj8 -> { 
+            String cipherText = null;
+            try {
+                cipherText = cuj8.encryptString(source, password);
+                System.out.println(cipherText);// about 128
+                
+                System.out.println("length for " + cuj8.getType() + " is:" +cipherText.length());// about 128
+                if (cuj8.type == TYPES.PBE) {
+                    assertEquals(128, cipherText.length()); // 128bytes + 10 bytes for cleartext
+                } 
+                CryptoStreamFactoryJ8Template.setInstance(null);
+                String plainText = cuj8.decryptString(cipherText, password);
+                assertEquals(source, plainText);
+            } catch (GeneralSecurityException | IOException e) {
+                e.printStackTrace();
+                fail();
+            }
+            
+        });      
+
     }
 
 }
diff --git a/src/test/org/apache/fulcrum/jce/crypto/CryptoUtilTest.java b/src/test/org/apache/fulcrum/jce/crypto/CryptoUtilTest.java
index 9657577..d3d68ce 100644
--- a/src/test/org/apache/fulcrum/jce/crypto/CryptoUtilTest.java
+++ b/src/test/org/apache/fulcrum/jce/crypto/CryptoUtilTest.java
@@ -63,9 +63,9 @@
 	 * @throws Exception Generic exception
 	 */
 	protected void setUp() throws Exception {
-		CryptoStreamFactoryImpl factory = new CryptoStreamFactoryImpl(CryptoParameters.SALT, CryptoParameters.COUNT);
+	    CryptoStreamFactoryImpl factory = new CryptoStreamFactoryImpl(CryptoParameters.SALT, CryptoParameters.COUNT);
 
-		CryptoStreamFactoryImpl.setInstance(factory);
+	    CryptoStreamFactoryImpl.setInstance(factory);
 	}
 
 	/**
diff --git a/src/test/org/apache/fulcrum/jce/crypto/Main.java b/src/test/org/apache/fulcrum/jce/crypto/Main.java
index 8560fa6..95b79df 100644
--- a/src/test/org/apache/fulcrum/jce/crypto/Main.java
+++ b/src/test/org/apache/fulcrum/jce/crypto/Main.java
@@ -136,7 +136,7 @@
         }
         else if( cipherMode.equals("enc") )
         {
-            System.out.println("Enrypting " + sourceFile.getAbsolutePath() );
+            System.out.println("Encrypting " + sourceFile.getAbsolutePath() );
             CryptoUtil.getInstance().encrypt( fis, baos, password );
             fis.close();
 
diff --git a/src/test/org/apache/fulcrum/jce/crypto/SmartDecryptingInputStreamTest.java b/src/test/org/apache/fulcrum/jce/crypto/SmartDecryptingInputStreamTest.java
index ed8278a..ca4ebb4 100644
--- a/src/test/org/apache/fulcrum/jce/crypto/SmartDecryptingInputStreamTest.java
+++ b/src/test/org/apache/fulcrum/jce/crypto/SmartDecryptingInputStreamTest.java
@@ -174,7 +174,7 @@
         FileInputStream fis = new FileInputStream( file );
 
         CryptoUtil.getInstance().encrypt(
-            CryptoStreamFactoryImpl.getInstance(),
+                CryptoStreamFactoryImpl.getInstance(),
             fis,
             baos,
             this.getPassword()
@@ -197,7 +197,7 @@
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
 
         SmartDecryptingInputStream sdis = new SmartDecryptingInputStream(
-            CryptoStreamFactoryImpl.getInstance(),
+                CryptoStreamFactoryImpl.getInstance(),
             bais,
             this.getPassword()
             );
@@ -219,7 +219,7 @@
         FileInputStream fis = new FileInputStream( file );
 
         SmartDecryptingInputStream sdis = new SmartDecryptingInputStream(
-            CryptoStreamFactoryImpl.getInstance(),
+                CryptoStreamFactoryImpl.getInstance(),
             fis,
             this.getPassword()
             );