/* ************************************************************************ # # DivConq # # http://divconq.com/ # # Copyright: # Copyright 2014 eTimeline, LLC. All rights reserved. # # License: # See the license.txt file in the project's top-level directory for details. # # Authors: # * Andy White # ************************************************************************ */ package divconq.util; import java.security.InvalidKeyException; import java.security.SecureRandom; import javax.crypto.Cipher; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import divconq.lang.chars.Utf8Decoder; import divconq.lang.chars.Utf8Encoder; import divconq.log.Logger; import divconq.xml.XElement; /** * This is the default settings obfuscator, see ISettingsObfuscator for hints on * how to build your own. If a customer obfuscator is not provided then all * encrypted configuration settings use this implementation. * * This is good enough to keep the casual hacker at bay. The encryption key is * created by a combination of settings in config.xml and a hard coded default salt. * So to break an encryption the hacker needs both the code and the config file. * * @author Andy * * TODO consider a bcrypt implementation http://www.mindrot.org/projects/jBCrypt/ * */ public class BasicSettingsObfuscator implements ISettingsObfuscator { public static final byte[] DEFAULT_SALT = { (byte)201, (byte) 15, (byte)218, (byte)162, (byte) 33, (byte)104, (byte)194, (byte) 52, (byte)196, (byte)198, (byte) 98, (byte)139, (byte)128, (byte)220, (byte) 28, (byte)209, (byte) 41, (byte) 2, (byte) 78, (byte) 8, (byte)138, (byte)103, (byte)204, (byte)116, (byte) 2, (byte) 11, (byte)190, (byte)166, (byte) 59, (byte) 19, (byte)155, (byte) 34, (byte) 81, (byte) 74, (byte) 8, (byte)121, (byte)142, (byte) 52, (byte) 4, (byte)221, (byte)239, (byte)149, (byte) 25, (byte)179, (byte)205, (byte) 58, (byte) 67, (byte) 27, (byte) 48, (byte) 43, (byte) 10, (byte)109, (byte)242, (byte) 95, (byte) 20, (byte) 55, (byte) 79, (byte)225, (byte) 53, (byte)109, (byte)109, (byte) 81, (byte)194, (byte) 69 }; protected byte[] masterkey = null; protected SecretKeySpec aeskey = null; protected SecretKeySpec hmackey = null; @Override public void init(XElement config) { String salt1 = null; String salt2 = null; if (config != null) { salt1 = config.getAttribute("Id"); salt2 = config.getAttribute("Feed"); } byte[] skey = new byte[128]; if (StringUtil.isEmpty(salt1)) salt1 = "48656c6c6f"; else if (salt1.length() > 128) salt1 = salt1.substring(salt1.length() - 128); byte[] bsalt1 = HexUtil.decodeHex(salt1); if (bsalt1 == null) bsalt1 = DEFAULT_SALT; ArrayUtil.blockCopy(bsalt1, 0, skey, 128 - bsalt1.length, bsalt1.length); if (bsalt1.length < 64) ArrayUtil.blockCopy(DEFAULT_SALT, 0, skey, 64, 64 - bsalt1.length); if (StringUtil.isEmpty(salt2)) salt2 = "576f726c64"; else if (salt2.length() > 128) salt2 = salt2.substring(salt2.length() - 128); byte[] bsalt2 = HexUtil.decodeHex(salt2); if (bsalt2 == null) bsalt2 = DEFAULT_SALT; ArrayUtil.blockCopy(bsalt2, 0, skey, 0, bsalt2.length); if (bsalt2.length < 64) ArrayUtil.blockCopy(DEFAULT_SALT, bsalt2.length, skey, bsalt2.length, 64 - bsalt2.length); this.masterkey = skey; byte[] akey = new byte[16]; ArrayUtil.blockCopy(skey, bsalt2.length - 10, akey, 0, 16); // TODO confirm we are ending up with a 4096 bit encryption this.aeskey = new SecretKeySpec(akey, "AES"); this.hmackey = new SecretKeySpec(skey, "hmacSHA512"); } @Override public void configure(XElement config) { byte[] idbuff = new byte[64]; byte[] feedbuff = new byte[64]; SecureRandom rnd = new SecureRandom(); rnd.nextBytes(idbuff); rnd.nextBytes(feedbuff); config.setAttribute("Id", HexUtil.bufferToHex(idbuff)); config.setAttribute("Feed", HexUtil.bufferToHex(feedbuff)); } @Override public String decryptHexToString(CharSequence v) { return this.decryptString(HexUtil.decodeHex(v)); } @Override public String decryptString(byte[] v) { if (v == null) return null; try { Cipher c = Cipher.getInstance("AES"); c.init(Cipher.DECRYPT_MODE, this.aeskey); //System.out.println("a1: " + c.getAlgorithm()); //System.out.println("a2: " + c.getBlockSize()); return Utf8Decoder.decode(c.doFinal(v)).toString(); } catch(InvalidKeyException x) { Logger.warn("Invalid settings key: " + x, "Code", "202"); } catch(Exception x) { Logger.info("Failed decryption: " + x, "Code", "203"); } return null; } @Override public String encryptStringToHex(CharSequence v) { return HexUtil.bufferToHex(this.encryptString(v)); } @Override public byte[] encryptString(CharSequence v) { if (StringUtil.isEmpty(v)) return null; try { Cipher c = Cipher.getInstance("AES"); c.init(Cipher.ENCRYPT_MODE, this.aeskey); return c.doFinal(Utf8Encoder.encode(v)); } catch(InvalidKeyException x) { Logger.warn("Invalid settings key: " + x, "Code", "204"); } catch(Exception x) { Logger.info("Failed decryption: " + x, "Code", "205"); } return null; } @Override public Cipher encryptCipher() { try { Cipher c = Cipher.getInstance("AES"); c.init(Cipher.ENCRYPT_MODE, this.aeskey); return c; } catch(InvalidKeyException x) { Logger.warn("Invalid settings key: " + x, "Code", "204"); } catch(Exception x) { Logger.info("Failed encryption init: " + x, "Code", "205"); } return null; } @Override public Cipher decryptCipher() { try { Cipher c = Cipher.getInstance("AES"); c.init(Cipher.DECRYPT_MODE, this.aeskey); return c; } catch(InvalidKeyException x) { Logger.warn("Invalid settings key: " + x, "Code", "204"); } catch(Exception x) { Logger.info("Failed encryption init: " + x, "Code", "205"); } return null; } @Override public String hashStringToHex(CharSequence v) { return HexUtil.bufferToHex(this.hashString(v)); } @Override public byte[] hashString(CharSequence v) { if (StringUtil.isEmpty(v)) return null; try { Mac mac = Mac.getInstance("hmacSHA512"); mac.init(this.hmackey); return mac.doFinal(Utf8Encoder.encode(v)); } catch (Exception x) { Logger.info("Failed hash: " + x, "Code", "206"); } return null; } @Override public String getHashKey() { return HexUtil.bufferToHex(this.masterkey); } }