diff --git a/src/main/java/com/alibaba/druid/pool/DecryptException.java b/src/main/java/com/alibaba/druid/pool/DecryptException.java new file mode 100644 index 0000000000..e84e568d8d --- /dev/null +++ b/src/main/java/com/alibaba/druid/pool/DecryptException.java @@ -0,0 +1,24 @@ +package com.alibaba.druid.pool; + +/** + * 加密出错 + * + * @author Jonas Yang + */ +public class DecryptException extends Exception { + public DecryptException() { + super(); //To change body of overridden methods use File | Settings | File Templates. + } + + public DecryptException(String s) { + super(s); //To change body of overridden methods use File | Settings | File Templates. + } + + public DecryptException(String s, Throwable throwable) { + super(s, throwable); //To change body of overridden methods use File | Settings | File Templates. + } + + public DecryptException(Throwable throwable) { + super(throwable); //To change body of overridden methods use File | Settings | File Templates. + } +} diff --git a/src/main/java/com/alibaba/druid/pool/Decrypter.java b/src/main/java/com/alibaba/druid/pool/Decrypter.java new file mode 100644 index 0000000000..d4a8325681 --- /dev/null +++ b/src/main/java/com/alibaba/druid/pool/Decrypter.java @@ -0,0 +1,49 @@ +package com.alibaba.druid.pool; + +/** + *
+ * 解密敏感参数
+ * 使用解密器 只要简单实现这个类就可以了。
+ *
+ * public class MyDecrypter implement Decrypter {
+ *     public SensitiveParameters decrypt(SensitiveParameters parameters) throws DecryptException {
+ *         String url = ....;
+ *         String username = ....;
+ *         String password = ....;
+ *
+ *         return new SensitiveParameters(url, username, password);
+ *     }
+ * }
+ *
+ * DruidDataSource dataSource = new DruidDataSource();
+ * //设置DataSource的其他参数
+ * dataSource.setDecrypter(new MyDecrypter);
+ *
+ * [下面是Spring的配置]
+ *
+ * <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
+ *     <property name="url" value="${jdbc_url}" />
+ *     <property name="username" value="${jdbc_user}" />
+ *     <property name="password" value="${jdbc_password}" />
+ *     <property name="decrypter"><ref bean="myDecrypter"/></property>
+ * </bean>
+ *
+ * <bean id="myDecrypter" class="MyDecrypter"/>
+ *
+ * 
+ *
+ * 默认下, Druid 已经实现了AES, DES, RSA, Blowfish(兼容JBOSS)等加解密. AES, DES 解密的密文必须是通过Druid Tool的加密工具生成的 + *
+ * 使用加密工具: java -cp druid-x.x.x.jar com.alibaba.druid.support.security.tool.Main + * @author Jonas Yang + */ +public interface Decrypter { + + /** + * 返回一个解密后的(明文)参数 + * @param parameters 密文参数 + * @return + * @throws DecryptException 解密失败会抛出该异常 + */ + public SensitiveParameters decrypt(SensitiveParameters parameters) throws DecryptException; +} diff --git a/src/main/java/com/alibaba/druid/pool/SensitiveParameters.java b/src/main/java/com/alibaba/druid/pool/SensitiveParameters.java new file mode 100644 index 0000000000..2532c09690 --- /dev/null +++ b/src/main/java/com/alibaba/druid/pool/SensitiveParameters.java @@ -0,0 +1,35 @@ +package com.alibaba.druid.pool; + +/** + *
+ * 存放, 数据库的关键参数:URL, 用户名, 密码
+ * 如果存放的是明文, 请尽可能缩短实例的作用域
+ * 
+ * + * @author Jonas Yang + */ +public class SensitiveParameters { + private transient final String url; + + private transient final String username; + + private transient final String password; + + public SensitiveParameters(String url, String username, String password) { + this.url = url; + this.username = username; + this.password = password; + } + + public String getUrl() { + return url; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } +} diff --git a/src/main/java/com/alibaba/druid/support/security/decryptor/AbstractDecrypter.java b/src/main/java/com/alibaba/druid/support/security/decryptor/AbstractDecrypter.java new file mode 100644 index 0000000000..62a3ec9b16 --- /dev/null +++ b/src/main/java/com/alibaba/druid/support/security/decryptor/AbstractDecrypter.java @@ -0,0 +1,37 @@ +package com.alibaba.druid.support.security.decryptor; + +import com.alibaba.druid.pool.Decrypter; +import com.alibaba.druid.util.Base64; + +import javax.crypto.Cipher; + +/** + * 加密抽象类 + * + * @author Jonas Yang + */ +public abstract class AbstractDecrypter implements Decrypter { + + /** + * DES, AES 密钥都有长度限制, 如果不够, 默认使用该字段补长 + */ + protected static final String KEY_PADDING = "FOLLOW YOUR HEART. YOU CAN DO BEST THAN ANY ONE."; + + /** + * 传入密文, 返回明文 + * @param cipher 解密器 + * @param cipherString 密文 + * @return + * @throws Exception + */ + protected String decrypt(Cipher cipher, String cipherString) throws Exception { + if (cipherString == null || cipherString.length() == 0) { + return cipherString; + } + + byte[] cipherBytes = Base64.base64ToByteArray(cipherString); + byte[] plainBytes = cipher.doFinal(cipherBytes); + + return new String(plainBytes); + } +} diff --git a/src/main/java/com/alibaba/druid/support/security/decryptor/AesDecrypter.java b/src/main/java/com/alibaba/druid/support/security/decryptor/AesDecrypter.java new file mode 100644 index 0000000000..ca70ac47fe --- /dev/null +++ b/src/main/java/com/alibaba/druid/support/security/decryptor/AesDecrypter.java @@ -0,0 +1,54 @@ +package com.alibaba.druid.support.security.decryptor; + +import com.alibaba.druid.pool.DecryptException; +import com.alibaba.druid.pool.SensitiveParameters; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; + +/** + * AES 解密器 + * + * @author Jonas Yang + */ +public class AesDecrypter extends AbstractDecrypter { + + private final static int KEY_LENGTH = 16; //128 + + private volatile SecretKeySpec spec; + + public SensitiveParameters decrypt(SensitiveParameters parameters) throws DecryptException { + SecretKeySpec spec = getSpec(); + if (spec == null) { + throw new DecryptException("No key."); + } + + try { + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.DECRYPT_MODE, spec); + + String passwordPlainText = decrypt(cipher, parameters.getPassword()); + + return new SensitiveParameters(parameters.getUrl(), parameters.getUsername(), passwordPlainText); + } catch (Exception e) { + throw new DecryptException("Failed to decrypt JDBC password’", e); + } + } + + protected SecretKeySpec getSpec() { + return this.spec; + } + + public void setKey(String key) { + if (key == null) { + throw new IllegalArgumentException("The key cannot be null"); + } + + if (key.length() < 16) { + key = KEY_PADDING.substring(0, KEY_LENGTH - key.length()) + key; + } + + this.spec = new SecretKeySpec(key.getBytes(), "AES"); + } + +} diff --git a/src/main/java/com/alibaba/druid/support/security/decryptor/BlowfishDecrypter.java b/src/main/java/com/alibaba/druid/support/security/decryptor/BlowfishDecrypter.java new file mode 100644 index 0000000000..fa9c6e7795 --- /dev/null +++ b/src/main/java/com/alibaba/druid/support/security/decryptor/BlowfishDecrypter.java @@ -0,0 +1,46 @@ +package com.alibaba.druid.support.security.decryptor; + +import com.alibaba.druid.pool.DecryptException; +import com.alibaba.druid.pool.Decrypter; +import com.alibaba.druid.pool.SensitiveParameters; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.math.BigInteger; + +/** + * 拷贝JBOSS的Blowfish解密 + * @author Jonas Yang + */ +public class BlowfishDecrypter implements Decrypter { + + public SensitiveParameters decrypt(SensitiveParameters parameters) throws DecryptException { + byte[] kbytes = "jaas is the way".getBytes(); + SecretKeySpec key = new SecretKeySpec(kbytes, "Blowfish"); + + BigInteger n = new BigInteger(parameters.getPassword(), 16); + byte[] encoding = n.toByteArray(); + + if (encoding.length % 8 != 0) { + int length = encoding.length; + int newLength = ((length / 8) + 1) * 8; + int pad = newLength - length; //number of leading zeros + byte[] old = encoding; + encoding = new byte[newLength]; + + for (int i = old.length - 1; i >= 0; i--) { + encoding[i + pad] = old[i]; + } + } + + try { + Cipher cipher = Cipher.getInstance("Blowfish"); + cipher.init(Cipher.DECRYPT_MODE, key); + byte[] decryptedBytes = cipher.doFinal(encoding); + + return new SensitiveParameters(parameters.getUrl(), parameters.getUsername(), new String(decryptedBytes)); + } catch (Exception e) { + throw new DecryptException("Failed to decrypt JDBC password.", e); + } + } +} diff --git a/src/main/java/com/alibaba/druid/support/security/decryptor/DesDecrypter.java b/src/main/java/com/alibaba/druid/support/security/decryptor/DesDecrypter.java new file mode 100644 index 0000000000..1472fdc9a1 --- /dev/null +++ b/src/main/java/com/alibaba/druid/support/security/decryptor/DesDecrypter.java @@ -0,0 +1,63 @@ +package com.alibaba.druid.support.security.decryptor; + +import com.alibaba.druid.pool.DecryptException; +import com.alibaba.druid.pool.SensitiveParameters; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; + +/** + * DES 解密器 + * + * @author Jonas Yang + */ +public class DesDecrypter extends AbstractDecrypter { + + private static final int KEY_LENGTH = 24; + + private volatile SecretKey key; + + public SensitiveParameters decrypt(SensitiveParameters parameters) throws DecryptException { + SecretKey key = getKey(); + if (key == null) { + throw new DecryptException("No key."); + } + + try { + Cipher cipher = Cipher.getInstance("DES"); + cipher.init(Cipher.DECRYPT_MODE, this.key); + + String passwordPlainText = decrypt(cipher, parameters.getPassword()); + + return new SensitiveParameters(parameters.getUrl(), parameters.getUsername(), passwordPlainText); + } catch (Exception e) { + throw new DecryptException("Failed to decrypt JDBC parameters", e); + } + } + + public void setKey(String key) { + if (key == null) { + throw new IllegalArgumentException("The key cannot be null"); + } + + if (key.length() < 24) { + key = KEY_PADDING.substring(0, KEY_LENGTH - key.length()) + key; + } + + try { + byte[] keyBytes = key.getBytes(); + DESKeySpec spec = new DESKeySpec(keyBytes); + + SecretKeyFactory factory = SecretKeyFactory.getInstance("DES"); + this.key = factory.generateSecret(spec); + } catch (Exception e) { + throw new IllegalArgumentException("Cannot get key.", e); + } + } + + protected SecretKey getKey() { + return this.key; + } +} diff --git a/src/main/java/com/alibaba/druid/support/security/decryptor/RsaDecrypter.java b/src/main/java/com/alibaba/druid/support/security/decryptor/RsaDecrypter.java new file mode 100644 index 0000000000..bec91f8f38 --- /dev/null +++ b/src/main/java/com/alibaba/druid/support/security/decryptor/RsaDecrypter.java @@ -0,0 +1,114 @@ +package com.alibaba.druid.support.security.decryptor; + +import com.alibaba.druid.pool.DecryptException; +import com.alibaba.druid.pool.SensitiveParameters; +import com.alibaba.druid.support.logging.Log; +import com.alibaba.druid.support.logging.LogFactory; +import com.alibaba.druid.util.Base64; + +import javax.crypto.Cipher; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.cert.CertificateFactory; +import java.security.spec.X509EncodedKeySpec; +import java.security.cert.Certificate; + +/** + * RSA 解密器, 支持证书 + * + */ +public class RsaDecrypter extends AbstractDecrypter { + + public static Log log = LogFactory.getLog(RsaDecrypter.class); + + private volatile PublicKey publicKey; + + public SensitiveParameters decrypt(SensitiveParameters parameters) throws DecryptException { + PublicKey publicKey = getPublicKey(); + if (publicKey == null) { + throw new DecryptException("No public Key."); + } + + try { + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.DECRYPT_MODE, publicKey); + String passwordPlainText = decrypt(cipher, parameters.getPassword()); + + return new SensitiveParameters(parameters.getUrl(), parameters.getUsername(), passwordPlainText); + } catch (DecryptException e) { + throw e; + } catch (Exception e) { + throw new DecryptException("Failed to decrypt JDBC parameters", e); + } + } + + protected PublicKey getPublicKey() { + return this.publicKey; + } + + public void setPublicKeyString(String publicKeyString) { + if (publicKeyString == null) { + throw new IllegalArgumentException("The publicKeyString cannot be null"); + } + + try { + byte[] publicKeyBytes = Base64.base64ToByteArray(publicKeyString); + X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKeyBytes); + + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + this.publicKey = keyFactory.generatePublic(x509KeySpec); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to get public key", e); + } + } + + public void setPublicKeyFile(String publicKeyFile) { + FileInputStream in = null; + try { + in = new FileInputStream(publicKeyFile); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + int len = 0; + byte[] b = new byte[512/8]; + while ((len = in.read(b)) != -1) { + out.write(b, 0, len); + } + + byte[] publicKeyBytes = out.toByteArray(); + X509EncodedKeySpec spec = new X509EncodedKeySpec(publicKeyBytes); + KeyFactory factory = KeyFactory.getInstance("RSA"); + this.publicKey = factory.generatePublic(spec); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to get public key", e); + } finally { + if (in != null) { + try { + in.close(); + } catch (Exception e) {} + } + } + } + + public void setX509File(String x509File) { + FileInputStream in = null; + try { + in = new FileInputStream(x509File); + + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + Certificate cer = factory.generateCertificate(in); + this.publicKey = cer.getPublicKey(); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to get public key", e); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + log.warn("Cannot close file [" + x509File + "] in initialized public key."); + } + } + } + } +} diff --git a/src/main/java/com/alibaba/druid/support/security/tool/AbstractAction.java b/src/main/java/com/alibaba/druid/support/security/tool/AbstractAction.java new file mode 100644 index 0000000000..6dc2a04489 --- /dev/null +++ b/src/main/java/com/alibaba/druid/support/security/tool/AbstractAction.java @@ -0,0 +1,76 @@ +package com.alibaba.druid.support.security.tool; + +/** + * @author Jonas Yang + */ +public abstract class AbstractAction implements Action { + private int minKeyLength; + + private int maxKeyLength; + + private String prompt; + + public AbstractAction(int minKeyLength, int maxKeyLength, String prompt) { + if (minKeyLength < 0 || maxKeyLength < 0) { + throw new IllegalArgumentException("The key length must be a positive."); + } + + if (maxKeyLength < minKeyLength) { + throw new IllegalArgumentException("The max length of key cannot be less than min length."); + } + + this.minKeyLength = minKeyLength; + this.maxKeyLength = maxKeyLength; + this.prompt = "[" + prompt + "]"; + } + + protected abstract String encrypt(String keyString, String plainString) throws Exception; + + @Override + public void execute() { + System.out.println(); + + String keyString = readPassword(); + if (keyString == null) return; + + String plainString = System.console().readLine(this.prompt + "请输入要需要加密的明文: "); + if (plainString == null) { + System.err.println("输入不能为空."); + return; + } + + try { + String encryptedString = encrypt(keyString, plainString); + System.out.println("请记住以下的密文, 长度为[" + encryptedString.length() + "]."); + System.out.println(); + System.out.println(encryptedString); + System.out.println(); + } catch (Exception e) { + System.out.println("加密出错."); + e.printStackTrace(); + } + } + + protected String readPassword() { + char[] keyChars = System.console().readPassword(this.prompt + "请输入密码[长度最大为" + this.maxKeyLength + "字节]: "); + if (keyChars == null) { + System.err.println("错误: 密码不能为空!"); + return null; + } + + String keyString = new String(keyChars); + if (keyString == null) { + return null; + } + + if (keyString.length() < this.minKeyLength || keyString.length() > this.maxKeyLength) { + System.err.println("错误: 密码长度要大于等于" + this.minKeyLength + ", 小于等于" + this.maxKeyLength + "."); + return null; + } + + keyString = KEY_PADDING.substring(0, this.maxKeyLength - keyString.length()) + keyString; + + return keyString; + } + +} diff --git a/src/main/java/com/alibaba/druid/support/security/tool/Action.java b/src/main/java/com/alibaba/druid/support/security/tool/Action.java new file mode 100644 index 0000000000..799bdd990d --- /dev/null +++ b/src/main/java/com/alibaba/druid/support/security/tool/Action.java @@ -0,0 +1,15 @@ +package com.alibaba.druid.support.security.tool; + +/** + * @author Jonas Yang + */ +public interface Action { + /** + * DES, AES 密钥都有长度限制, 如果不够, 默认使用该字段补长 + */ + public String KEY_PADDING = "FOLLOW YOUR HEART. YOU CAN DO BEST THAN ANY ONE."; + + public String getId(); + + public void execute(); +} diff --git a/src/main/java/com/alibaba/druid/support/security/tool/AesAction.java b/src/main/java/com/alibaba/druid/support/security/tool/AesAction.java new file mode 100644 index 0000000000..3267ed9022 --- /dev/null +++ b/src/main/java/com/alibaba/druid/support/security/tool/AesAction.java @@ -0,0 +1,32 @@ +package com.alibaba.druid.support.security.tool; + +import sun.misc.BASE64Encoder; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; + +/** + * @author Jonas Yang + */ +public class AesAction extends AbstractAction { + + public AesAction() { + super(6, 16, "AES"); + } + + @Override + public String getId() { + return "AES"; + } + + protected String encrypt(String keyString, String plainString) throws Exception { + SecretKeySpec spec = new SecretKeySpec(keyString.getBytes(), "AES"); + + Cipher cipher = Cipher.getInstance("AES"); + cipher.init(Cipher.ENCRYPT_MODE, spec); + + byte[] encryptedBytes = cipher.doFinal(plainString.getBytes()); + + return new BASE64Encoder().encode(encryptedBytes); + } +} diff --git a/src/main/java/com/alibaba/druid/support/security/tool/BlowfishAction.java b/src/main/java/com/alibaba/druid/support/security/tool/BlowfishAction.java new file mode 100644 index 0000000000..1a0e4591f9 --- /dev/null +++ b/src/main/java/com/alibaba/druid/support/security/tool/BlowfishAction.java @@ -0,0 +1,47 @@ +package com.alibaba.druid.support.security.tool; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.math.BigInteger; + +/** + * @author Jonas Yang + */ +public class BlowfishAction implements Action { + + @Override + public String getId() { + return "Blowfish"; + } + + public void execute() { + System.out.println(); + String plainString = System.console().readLine("[Blowfish]请输入要需要加密的明文: "); + if (plainString == null) { + System.err.println("输入不能为空."); + return; + } + + try { + String encryptedString = encrypt(plainString); + System.out.println("请记住以下的密文, 长度为[" + encryptedString.length() + "]."); + System.out.println(); + System.out.println(encryptedString); + System.out.println(); + } catch (Exception e) { + System.out.println("加密出错."); + e.printStackTrace(); + } + } + + private String encrypt(String plainString) throws Exception { + byte[] kbytes = "jaas is the way".getBytes(); + SecretKeySpec key = new SecretKeySpec(kbytes, "Blowfish"); + + Cipher cipher = Cipher.getInstance("Blowfish"); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] encoding = cipher.doFinal(plainString.getBytes()); + BigInteger n = new BigInteger(encoding); + return n.toString(16); + } +} diff --git a/src/main/java/com/alibaba/druid/support/security/tool/DesAction.java b/src/main/java/com/alibaba/druid/support/security/tool/DesAction.java new file mode 100644 index 0000000000..cca8472900 --- /dev/null +++ b/src/main/java/com/alibaba/druid/support/security/tool/DesAction.java @@ -0,0 +1,37 @@ +package com.alibaba.druid.support.security.tool; + +import sun.misc.BASE64Encoder; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESKeySpec; + +/** + * @author Jonas Yang + */ +public class DesAction extends AbstractAction { + + public DesAction() { + super(6, 24, "DES"); + } + + @Override + public String getId() { + return "DES"; + } + + public String encrypt(String keyString, String plainString) throws Exception { + DESKeySpec spec = new DESKeySpec(keyString.getBytes()); + + SecretKeyFactory factory = SecretKeyFactory.getInstance("DES"); + SecretKey key = factory.generateSecret(spec); + + Cipher cipher = Cipher.getInstance("DES"); + cipher.init(Cipher.ENCRYPT_MODE, key); + + byte[] encryptedBytes = cipher.doFinal(plainString.getBytes()); + + return new BASE64Encoder().encode(encryptedBytes); + } +} diff --git a/src/main/java/com/alibaba/druid/support/security/tool/HelpAction.java b/src/main/java/com/alibaba/druid/support/security/tool/HelpAction.java new file mode 100644 index 0000000000..ea532c944d --- /dev/null +++ b/src/main/java/com/alibaba/druid/support/security/tool/HelpAction.java @@ -0,0 +1,56 @@ +package com.alibaba.druid.support.security.tool; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Jonas Yang + */ +public class HelpAction implements Action { + + private List list = new ArrayList(); + + @Override + public String getId() { + return "HELP"; + } + + public void execute() { + System.out.println(); + for (ActionDesc desc : list) { + System.out.println(desc.getKey() + ". " + desc.getAction().getId()); + } + } + + public void addAction(String key, Action action) { + this.list.add(new ActionDesc(key, action)); + } + + public Action getAction(String key) { + for (ActionDesc desc : list) { + if (desc.getKey().equals(key)) { + return desc.getAction(); + } + } + + return null; + } + + private static class ActionDesc { + private final String key; + private final Action action; + + public ActionDesc(String key, Action action) { + this.key = key; + this.action = action; + } + + public Action getAction() { + return action; + } + + public String getKey() { + return key; + } + } +} diff --git a/src/main/java/com/alibaba/druid/support/security/tool/Main.java b/src/main/java/com/alibaba/druid/support/security/tool/Main.java new file mode 100644 index 0000000000..daba0202b8 --- /dev/null +++ b/src/main/java/com/alibaba/druid/support/security/tool/Main.java @@ -0,0 +1,51 @@ +package com.alibaba.druid.support.security.tool; + +/** + *
+ * Druid 提供的加密工具. 如下运行
+ *
+ * java -cp druid-x.x.x.jar com.alibaba.druid.support.security.tool.Main
+ *
+ * 
+ * @author Jonas Yang + */ +public class Main { + private static boolean isEnd = false; + + private static HelpAction help = new HelpAction(); + + static { + int i = 1; + help.addAction(Integer.toString(i++), new RsaAction()); + help.addAction(Integer.toString(i++), new DesAction()); + help.addAction(Integer.toString(i++), new AesAction()); + help.addAction(Integer.toString(i++), new BlowfishAction()); + help.addAction("h", help); + help.addAction("q", new QuitAction()); + } + + public static void main(String[] args) { + header(); + help.execute(); + + while(!isEnd) { + String input = System.console().readLine("请输入选项: "); + input = input.toLowerCase(); + Action action = help.getAction(input); + if (action == null) { + System.err.println(input + " 不是一个合法的选项.[如需要帮助请输入 h]"); + continue; + } else { + action.execute(); + } + } + } + + public static void header() { + System.out.println("**************************************"); + System.out.println("* *"); + System.out.println("* Druid 加密工具 *"); + System.out.println("* *"); + System.out.println("**************************************"); + } +} diff --git a/src/main/java/com/alibaba/druid/support/security/tool/QuitAction.java b/src/main/java/com/alibaba/druid/support/security/tool/QuitAction.java new file mode 100644 index 0000000000..ec27d72b50 --- /dev/null +++ b/src/main/java/com/alibaba/druid/support/security/tool/QuitAction.java @@ -0,0 +1,25 @@ +package com.alibaba.druid.support.security.tool; + +/** + * @author Jonas Yang + */ +public class QuitAction implements Action { + + @Override + public String getId() { + return "QUIT"; + } + + public void execute() { + foot(); + System.exit(0); + } + + private static void foot() { + System.out.println("**************************************"); + System.out.println("* *"); + System.out.println("* 再 见 了 *"); + System.out.println("* *"); + System.out.println("**************************************"); + } +} diff --git a/src/main/java/com/alibaba/druid/support/security/tool/RsaAction.java b/src/main/java/com/alibaba/druid/support/security/tool/RsaAction.java new file mode 100644 index 0000000000..94829049f0 --- /dev/null +++ b/src/main/java/com/alibaba/druid/support/security/tool/RsaAction.java @@ -0,0 +1,132 @@ +package com.alibaba.druid.support.security.tool; + +import sun.misc.BASE64Encoder; + +import javax.crypto.Cipher; +import java.io.*; +import java.security.*; +import java.security.spec.PKCS8EncodedKeySpec; + +/** + * @author Jonas Yang + */ +public class RsaAction implements Action { + + @Override + public String getId() { + return "RSA"; + } + + public void execute() { + help(); + } + + private void help() { + System.out.println(); + System.out.println("RSA 选项: "); + System.out.println("1. 通过私钥加密"); + System.out.println("2. 创建 RSA 密钥"); + + String input = System.console().readLine("[RSA]请输入选项: "); + if (input == null || input.length() == 0) { + System.err.println("不是一个合法的输入."); + } + + if ("1".equals(input)) { + encrypt(); + return; + } + + if ("2".equals(input)) { + generateKeys(); + } + } + + private void encrypt() { + String filePath = System.console().readLine("[RSA]请输入私钥文件路径: "); + if (!new File(filePath).exists()) { + System.err.println("文件不存在[" + filePath + "]"); + } + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + FileInputStream in = null; + try { + in = new FileInputStream(filePath); + byte[] b = new byte[512/8]; + int len = 0; + while ((len = in.read(b)) != -1) { + out.write(b, 0, len); + } + } catch (Exception e) { + System.err.println("读取文件出错. " + e.getMessage()); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + } + } + } + + byte[] key = out.toByteArray(); + String plainString = System.console().readLine("[RSA]请输入要需要加密的明文: "); + + try { + PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(key); + KeyFactory factory = KeyFactory.getInstance("RSA"); + PrivateKey privateKey = factory.generatePrivate(spec); + Cipher cipher = Cipher.getInstance("RSA"); + cipher.init(Cipher.ENCRYPT_MODE, privateKey); + + byte[] encryptedBytes = cipher.doFinal(plainString.getBytes()); + String encryptedString = new BASE64Encoder().encode(encryptedBytes); + + System.out.println("请记住以下的密文, 长度为[" + encryptedString.length() + "]."); + System.out.println(); + System.out.println(encryptedString); + System.out.println(); + } catch (Exception e) { + System.err.println("加密错误. "); + e.printStackTrace(); + } + } + + private void generateKeys() { + String input = System.console().readLine("[RSA]请输入输出路径: "); + if (input == null) { + input = ""; + } + + try { + KeyPairGenerator gen = KeyPairGenerator.getInstance("RSA"); + gen.initialize(1024, new SecureRandom()); + KeyPair pair = gen.generateKeyPair(); + + String privateKeyPath = input + ".private.key"; + saveKey(pair.getPrivate(), privateKeyPath); + + String publicKeyPath = input + ".public.key"; + saveKey(pair.getPublic(), publicKeyPath); + + System.out.println("Private Key: " + privateKeyPath); + System.out.println("public Key: " + publicKeyPath); + System.out.println(); + } catch (Exception e) { + System.err.println("[RSA]创建 RSA 密钥出错. " + e.getMessage()); + } + + } + + private void saveKey(Key key, String path) throws Exception { + FileOutputStream out = null; + try { + out = new FileOutputStream(path); + out.write(key.getEncoded()); + } finally { + if (out != null) { + out.close(); + } + } + } + +} \ No newline at end of file diff --git a/src/test/java/com/alibaba/druid/bvt/pool/DruidDataSourceDecryptTest.java b/src/test/java/com/alibaba/druid/bvt/pool/DruidDataSourceDecryptTest.java new file mode 100644 index 0000000000..a6452ca0f3 --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/pool/DruidDataSourceDecryptTest.java @@ -0,0 +1,44 @@ +package com.alibaba.druid.bvt.pool; + +import com.alibaba.druid.pool.DecryptException; +import com.alibaba.druid.pool.Decrypter; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.pool.SensitiveParameters; +import com.alibaba.druid.util.JdbcUtils; +import org.junit.Assert; +import org.junit.Test; + +import java.sql.SQLException; + +/** + * @author Jonas Yang + */ +public class DruidDataSourceDecryptTest { + + @Test + public void testDecrypt() throws SQLException { + DruidDataSource dataSource = new DruidDataSource(); + dataSource.setUrl("jdbc:oracle:thin:@"); + dataSource.setUsername("xiaoyu"); + dataSource.setPassword("druid"); + + dataSource.setDecrypter(new Decrypter() { + @Override + public SensitiveParameters decrypt(SensitiveParameters parameters) throws DecryptException { + Assert.assertEquals("xiaoyu", parameters.getUsername()); + throw new DecryptException("Test"); + } + }); + + try { + dataSource.init(); + dataSource.createPhysicalConnection(); + Assert.assertTrue("Got Here.", false); + } catch (SQLException e) { + Assert.assertEquals("The cause clas is " + e.getCause().getClass(), DecryptException.class, e.getCause().getClass()); + Assert.assertEquals("The excepiton message is " + e.getCause().getMessage(), "Test", e.getCause().getMessage()); + } finally { + JdbcUtils.close(dataSource); + } + } +} diff --git a/src/test/java/com/alibaba/druid/bvt/support/security/DecrypterTestSuite.java b/src/test/java/com/alibaba/druid/bvt/support/security/DecrypterTestSuite.java new file mode 100644 index 0000000000..ff99affcad --- /dev/null +++ b/src/test/java/com/alibaba/druid/bvt/support/security/DecrypterTestSuite.java @@ -0,0 +1,11 @@ +package com.alibaba.druid.bvt.support.security; + +/** + * Created with IntelliJ IDEA. + * User: yangkingsel + * Date: 12-8-27 + * Time: 上午9:55 + * To change this template use File | Settings | File Templates. + */ +public class DecrypterTestSuite { +} diff --git a/src/test/java/com/alibaba/druid/support/security/decryptor/AesDecrypterTest.java b/src/test/java/com/alibaba/druid/support/security/decryptor/AesDecrypterTest.java new file mode 100644 index 0000000000..de94ee5933 --- /dev/null +++ b/src/test/java/com/alibaba/druid/support/security/decryptor/AesDecrypterTest.java @@ -0,0 +1,21 @@ +package com.alibaba.druid.support.security.decryptor; + +import com.alibaba.druid.pool.DecryptException; +import com.alibaba.druid.pool.SensitiveParameters; +import junit.framework.Assert; +import org.junit.Test; + +/** + * @author Jonas Yang + */ +public class AesDecrypterTest { + + @Test + public void testDecrypt() throws DecryptException { + AesDecrypter decrypter = new AesDecrypter(); + decrypter.setKey("123456"); + SensitiveParameters parameters = decrypter.decrypt(new SensitiveParameters("", "", "0UNXTyp/r/wdm+b5FAY1Jw==")); + + Assert.assertEquals("It is not same.", "xiaoyu", parameters.getPassword()); + } +} diff --git a/src/test/java/com/alibaba/druid/support/security/decryptor/BlowfishDecrypterTest.java b/src/test/java/com/alibaba/druid/support/security/decryptor/BlowfishDecrypterTest.java new file mode 100644 index 0000000000..61da96972d --- /dev/null +++ b/src/test/java/com/alibaba/druid/support/security/decryptor/BlowfishDecrypterTest.java @@ -0,0 +1,20 @@ +package com.alibaba.druid.support.security.decryptor; + +import com.alibaba.druid.pool.DecryptException; +import com.alibaba.druid.pool.SensitiveParameters; +import junit.framework.Assert; +import org.junit.Test; + +/** + * @author Jonas Yang + */ +public class BlowfishDecrypterTest { + + @Test + public void testDecrypt() throws DecryptException { + BlowfishDecrypter decrypter = new BlowfishDecrypter(); + SensitiveParameters parameters = decrypter.decrypt(new SensitiveParameters("", "", "64c5fd2979a86168")); + + Assert.assertEquals("It is not same.", "123456", parameters.getPassword()); + } +} diff --git a/src/test/java/com/alibaba/druid/support/security/decryptor/DesDecrypterTest.java b/src/test/java/com/alibaba/druid/support/security/decryptor/DesDecrypterTest.java new file mode 100644 index 0000000000..756f6d850e --- /dev/null +++ b/src/test/java/com/alibaba/druid/support/security/decryptor/DesDecrypterTest.java @@ -0,0 +1,21 @@ +package com.alibaba.druid.support.security.decryptor; + +import com.alibaba.druid.pool.DecryptException; +import com.alibaba.druid.pool.SensitiveParameters; +import junit.framework.Assert; +import org.junit.Test; + +/** + * @author Jonas Yang + */ +public class DesDecrypterTest { + + @Test + public void testDecrypt() throws DecryptException { + DesDecrypter decrypter = new DesDecrypter(); + decrypter.setKey("123456"); + SensitiveParameters parameters = decrypter.decrypt(new SensitiveParameters("", "", "rIsr38vJDyQ=")); + + Assert.assertEquals("It is not same", parameters.getPassword(), "xiaoyu"); + } +} diff --git a/src/test/java/com/alibaba/druid/support/security/decryptor/RsaDecrypterTest.java b/src/test/java/com/alibaba/druid/support/security/decryptor/RsaDecrypterTest.java new file mode 100644 index 0000000000..87080d3b10 --- /dev/null +++ b/src/test/java/com/alibaba/druid/support/security/decryptor/RsaDecrypterTest.java @@ -0,0 +1,24 @@ +package com.alibaba.druid.support.security.decryptor; + +import com.alibaba.druid.pool.DecryptException; +import com.alibaba.druid.pool.SensitiveParameters; +import junit.framework.Assert; +import org.junit.Test; + +/** + * @author Jonas Yang + */ +public class RsaDecrypterTest { + String encryptedString = "Zaa9qpyB9snnBUz7UBvcaHcNfWMKEWvhnx0dbWmwxBOJ9vkeZNXjV9MEJezSzL+6yQh7aO29vD67" + + "qedtD9ublVwwf+jTPuO1NjzAKy0+yWVQ1KdR2KaQuvCxFWiM1n6nbpkUGB6OVpoQgFnFzdKlyC9D" + + "KU3Q6evGa1hlZ8jnHcA="; + + @Test + public void testDecrypt() throws DecryptException { + RsaDecrypter decrypter = new RsaDecrypter(); + decrypter.setPublicKeyFile("/Users/yangkingsel/IdeaProjects/HelloWorld/out/production/HelloWorld/a.public.key"); + SensitiveParameters parameters = decrypter.decrypt(new SensitiveParameters("", "", encryptedString)); + + Assert.assertEquals("It is not same.", "xiaoyu", parameters.getPassword()); + } +}