From 9de5b5babb1d3676b89ceed1e27b67f81c798625 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Tue, 27 Sep 2011 20:52:04 +0000 Subject: [PATCH] Password based encryption git-svn-id: https://svn.argeo.org/commons/trunk@4759 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- .../src/main/java/org/argeo/StreamUtils.java | 43 +++++ .../util/crypto/PasswordBasedEncryption.java | 169 ++++++++++++++++++ .../crypto/PasswordBasedEncryptionTest.java | 110 ++++++++++++ 3 files changed, 322 insertions(+) create mode 100644 basic/runtime/org.argeo.basic.nodeps/src/main/java/org/argeo/StreamUtils.java create mode 100644 basic/runtime/org.argeo.basic.nodeps/src/main/java/org/argeo/util/crypto/PasswordBasedEncryption.java create mode 100644 basic/runtime/org.argeo.basic.nodeps/src/test/java/org/argeo/util/crypto/PasswordBasedEncryptionTest.java 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 index 000000000..10af68fa4 --- /dev/null +++ b/basic/runtime/org.argeo.basic.nodeps/src/main/java/org/argeo/StreamUtils.java @@ -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 index 000000000..b1c18eb82 --- /dev/null +++ b/basic/runtime/org.argeo.basic.nodeps/src/main/java/org/argeo/util/crypto/PasswordBasedEncryption.java @@ -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 index 000000000..d0687baa9 --- /dev/null +++ b/basic/runtime/org.argeo.basic.nodeps/src/test/java/org/argeo/util/crypto/PasswordBasedEncryptionTest.java @@ -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())); + } +} -- 2.39.2