Password based encryption
authorMathieu Baudier <mbaudier@argeo.org>
Tue, 27 Sep 2011 20:52:04 +0000 (20:52 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Tue, 27 Sep 2011 20:52:04 +0000 (20:52 +0000)
git-svn-id: https://svn.argeo.org/commons/trunk@4759 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

basic/runtime/org.argeo.basic.nodeps/src/main/java/org/argeo/StreamUtils.java [new file with mode: 0644]
basic/runtime/org.argeo.basic.nodeps/src/main/java/org/argeo/util/crypto/PasswordBasedEncryption.java [new file with mode: 0644]
basic/runtime/org.argeo.basic.nodeps/src/test/java/org/argeo/util/crypto/PasswordBasedEncryptionTest.java [new file with mode: 0644]

diff --git a/basic/runtime/org.argeo.basic.nodeps/src/main/java/org/argeo/StreamUtils.java b/basic/runtime/org.argeo.basic.nodeps/src/main/java/org/argeo/StreamUtils.java
new file mode 100644 (file)
index 0000000..10af68f
--- /dev/null
@@ -0,0 +1,43 @@
+package org.argeo;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/** Utilities to be used when APache COmmons IO is not available. */
+public class StreamUtils {
+
+       public static void copy(InputStream in, OutputStream out)
+                       throws IOException {
+               byte[] buf = new byte[8192];
+               while (true) {
+                       int length = in.read(buf);
+                       if (length < 0)
+                               break;
+                       out.write(buf, 0, length);
+               }
+       }
+
+       public static void closeQuietly(InputStream in) {
+               if (in != null)
+                       try {
+                               in.close();
+                       } catch (Exception e) {
+                               //
+                       }
+       }
+
+       public static void closeQuietly(OutputStream out) {
+               if (out != null)
+                       try {
+                               out.close();
+                       } catch (Exception e) {
+                               //
+                       }
+       }
+
+       private StreamUtils() {
+
+       }
+
+}
diff --git a/basic/runtime/org.argeo.basic.nodeps/src/main/java/org/argeo/util/crypto/PasswordBasedEncryption.java b/basic/runtime/org.argeo.basic.nodeps/src/main/java/org/argeo/util/crypto/PasswordBasedEncryption.java
new file mode 100644 (file)
index 0000000..b1c18eb
--- /dev/null
@@ -0,0 +1,169 @@
+package org.argeo.util.crypto;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.Key;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.argeo.ArgeoException;
+import org.argeo.StreamUtils;
+
+/** Simple password based encryption / decryption */
+public class PasswordBasedEncryption {
+       public final static Integer DEFAULT_ITERATION_COUNT = 1024;
+       public final static Integer DEFAULT_KEY_LENGTH = 256;
+       public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1";
+       public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES";
+       public final static String DEFAULT_CIPHER = "AES/CBC/PKCS5Padding";
+       public final static String DEFAULT_CHARSET = "UTF-8";
+
+       private static byte[] DEFAULT_SALT_8 = { (byte) 0xA9, (byte) 0x9B,
+                       (byte) 0xC8, (byte) 0x32, (byte) 0x56, (byte) 0x35, (byte) 0xE3,
+                       (byte) 0x03 };
+       private static byte[] DEFAULT_IV_16 = { (byte) 0xA9, (byte) 0x9B,
+                       (byte) 0xC8, (byte) 0x32, (byte) 0x56, (byte) 0x35, (byte) 0xE3,
+                       (byte) 0x03, (byte) 0xA9, (byte) 0x9B, (byte) 0xC8, (byte) 0x32,
+                       (byte) 0x56, (byte) 0x35, (byte) 0xE3, (byte) 0x03 };
+
+       private final Key key;
+       private final Cipher ecipher;
+       private final Cipher dcipher;
+
+       public PasswordBasedEncryption(char[] password) {
+               try {
+                       byte[] salt = new byte[8];
+                       System.arraycopy(DEFAULT_SALT_8, 0, salt, 0, salt.length);
+                       for (int i = 0; i < password.length && i < salt.length; i++)
+                               salt[i] = (byte) password[i];
+                       byte[] iv = new byte[16];
+                       System.arraycopy(DEFAULT_IV_16, 0, iv, 0, iv.length);
+                       for (int i = 0; i < password.length && i < iv.length; i++)
+                               iv[i] = (byte) password[i];
+
+                       SecretKeyFactory keyFac = SecretKeyFactory
+                                       .getInstance(getSecretKeyFactoryName());
+                       PBEKeySpec keySpec = new PBEKeySpec(password, salt,
+                                       getIterationCount(), getKeyLength());
+                       String secKeyEncryption = getSecretKeyEncryption();
+                       if (secKeyEncryption != null) {
+                               SecretKey tmp = keyFac.generateSecret(keySpec);
+                               key = new SecretKeySpec(tmp.getEncoded(),
+                                               getSecretKeyEncryption());
+                       } else {
+                               key = keyFac.generateSecret(keySpec);
+                       }
+                       ecipher = Cipher.getInstance(getCipherName());
+                       ecipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
+                       // AlgorithmParameters params = ecipher.getParameters();
+                       // byte[] iv =
+                       // params.getParameterSpec(IvParameterSpec.class).getIV();
+                       dcipher = Cipher.getInstance(getCipherName());
+                       dcipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot get secret key", e);
+               }
+       }
+
+       public void encrypt(InputStream decryptedIn, OutputStream encryptedOut)
+                       throws IOException {
+               try {
+                       CipherOutputStream out = new CipherOutputStream(encryptedOut,
+                                       ecipher);
+                       StreamUtils.copy(decryptedIn, out);
+                       StreamUtils.closeQuietly(out);
+               } catch (IOException e) {
+                       throw e;
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot encrypt", e);
+               } finally {
+                       StreamUtils.closeQuietly(decryptedIn);
+               }
+       }
+
+       public void decrypt(InputStream encryptedIn, OutputStream decryptedOut)
+                       throws IOException {
+               try {
+                       CipherInputStream decryptedIn = new CipherInputStream(encryptedIn,
+                                       dcipher);
+                       StreamUtils.copy(decryptedIn, decryptedOut);
+               } catch (IOException e) {
+                       throw e;
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot decrypt", e);
+               } finally {
+                       StreamUtils.closeQuietly(encryptedIn);
+               }
+       }
+
+       public byte[] encryptString(String str) {
+               ByteArrayOutputStream out = null;
+               ByteArrayInputStream in = null;
+               try {
+                       out = new ByteArrayOutputStream();
+                       in = new ByteArrayInputStream(str.getBytes(DEFAULT_CHARSET));
+                       encrypt(in, out);
+                       return out.toByteArray();
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot encrypt", e);
+               } finally {
+                       StreamUtils.closeQuietly(out);
+               }
+       }
+
+       /** Closes the input stream */
+       public String decryptAsString(InputStream in) {
+               ByteArrayOutputStream out = null;
+               try {
+                       out = new ByteArrayOutputStream();
+                       decrypt(in, out);
+                       return new String(out.toByteArray(), DEFAULT_CHARSET);
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot decrypt", e);
+               } finally {
+                       StreamUtils.closeQuietly(out);
+               }
+       }
+
+       protected Key getKey() {
+               return key;
+       }
+
+       protected Cipher getEcipher() {
+               return ecipher;
+       }
+
+       protected Cipher getDcipher() {
+               return dcipher;
+       }
+
+       protected Integer getIterationCount() {
+               return DEFAULT_ITERATION_COUNT;
+       }
+
+       protected Integer getKeyLength() {
+               return DEFAULT_KEY_LENGTH;
+       }
+
+       protected String getSecretKeyFactoryName() {
+               return DEFAULT_SECRETE_KEY_FACTORY;
+       }
+
+       protected String getSecretKeyEncryption() {
+               return DEFAULT_SECRETE_KEY_ENCRYPTION;
+       }
+
+       protected String getCipherName() {
+               return DEFAULT_CIPHER;
+       }
+}
diff --git a/basic/runtime/org.argeo.basic.nodeps/src/test/java/org/argeo/util/crypto/PasswordBasedEncryptionTest.java b/basic/runtime/org.argeo.basic.nodeps/src/test/java/org/argeo/util/crypto/PasswordBasedEncryptionTest.java
new file mode 100644 (file)
index 0000000..d0687ba
--- /dev/null
@@ -0,0 +1,110 @@
+package org.argeo.util.crypto;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.security.AlgorithmParameters;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.PBEKeySpec;
+import javax.crypto.spec.PBEParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import junit.framework.TestCase;
+
+import org.argeo.StreamUtils;
+import org.argeo.util.crypto.PasswordBasedEncryption;
+
+public class PasswordBasedEncryptionTest extends TestCase {
+       public void testEncryptDecrypt() {
+               final String password = "test long password since they are more powerful";
+               PasswordBasedEncryption pbeEnc = new PasswordBasedEncryption(
+                               password.toCharArray());
+               String message = "Hello World!";
+               byte[] encrypted = pbeEnc.encryptString(message);
+               // System.out.println("Encrypted: '" + new String(encrypted) + "'");
+               PasswordBasedEncryption pbeDec = new PasswordBasedEncryption(
+                               password.toCharArray());
+               InputStream in = null;
+               in = new ByteArrayInputStream(encrypted);
+               String decrypted = pbeDec.decryptAsString(in);
+               // System.out.println("Decrypted: '" + decrypted + "'");
+               StreamUtils.closeQuietly(in);
+               assertEquals(message, decrypted);
+       }
+
+       public void testPBEWithMD5AndDES() throws Exception {
+               String password = "test";
+               String message = "Hello World!";
+
+               byte[] salt = { (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c,
+                               (byte) 0x7e, (byte) 0xc8, (byte) 0xee, (byte) 0x99 };
+
+               int count = 1024;
+
+               String cipherAlgorithm = "PBEWithMD5AndDES";
+               String secretKeyAlgorithm = "PBEWithMD5AndDES";
+               SecretKeyFactory keyFac = SecretKeyFactory
+                               .getInstance(secretKeyAlgorithm);
+               PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
+               PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, count);
+               SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);
+               Cipher ecipher = Cipher.getInstance(cipherAlgorithm);
+               ecipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);
+               Cipher dcipher = Cipher.getInstance(cipherAlgorithm);
+               dcipher.init(Cipher.DECRYPT_MODE, pbeKey, pbeParamSpec);
+
+               byte[] encrypted = ecipher.doFinal(message.getBytes());
+               byte[] decrypted = dcipher.doFinal(encrypted);
+               assertEquals(message, new String(decrypted));
+
+       }
+
+       public void testPBEWithSHA1AndAES() throws Exception {
+               String password = "test";
+               String message = "Hello World!";
+
+               byte[] salt = { (byte) 0xc7, (byte) 0x73, (byte) 0x21, (byte) 0x8c,
+                               (byte) 0x7e, (byte) 0xc8, (byte) 0xee, (byte) 0x99 };
+
+               int count = 1024;
+               // int keyLength = 256;
+               int keyLength = 128;
+
+               String cipherAlgorithm = "AES/CBC/PKCS5Padding";
+               String secretKeyAlgorithm = "PBKDF2WithHmacSHA1";
+               SecretKeyFactory keyFac = SecretKeyFactory
+                               .getInstance(secretKeyAlgorithm);
+               PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), salt,
+                               count, keyLength);
+               SecretKey tmp = keyFac.generateSecret(pbeKeySpec);
+               SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
+               Cipher ecipher = Cipher.getInstance(cipherAlgorithm);
+               ecipher.init(Cipher.ENCRYPT_MODE, secret);
+               AlgorithmParameters params = ecipher.getParameters();
+               byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
+               Cipher dcipher = Cipher.getInstance(cipherAlgorithm);
+               dcipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
+
+               byte[] encrypted = ecipher.doFinal(message.getBytes());
+               byte[] decrypted = dcipher.doFinal(encrypted);
+               assertEquals(message, new String(decrypted));
+
+               ByteArrayOutputStream out = new ByteArrayOutputStream();
+               CipherOutputStream cipherOut = new CipherOutputStream(out, ecipher);
+               cipherOut.write(message.getBytes());
+               StreamUtils.closeQuietly(cipherOut);
+               byte[] enc = out.toByteArray();
+
+               ByteArrayInputStream in = new ByteArrayInputStream(enc);
+               CipherInputStream cipherIn = new CipherInputStream(in, dcipher);
+               ByteArrayOutputStream dec = new ByteArrayOutputStream();
+               StreamUtils.copy(cipherIn, dec);
+               assertEquals(message, new String(dec.toByteArray()));
+       }
+}