#1
  1. No Profile Picture
    Registered User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Nov 2011
    Posts
    13
    Rep Power
    0

    Java and Encryption.. Help


    Dear Sirs et Madames,
    I am trying to create a Java application which takes a userID and then encrypts it. The code am using is just something I picked up off the net, I have NO experience with encryption. The problem is that even though the string to be encrypted should (and always will be) 10 characters long, the result is always 24 characters. Considering that the encrypted result will be transformed into a barcode, that creates a rather large barcode, unsuitable for my needs. Am sure there must be a way whereby I encrypt a 10 character string and get back a ten character encrypted version? The code I have is as below:

    Code:
     
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.security.InvalidKeyException;
    import java.security.NoSuchAlgorithmException;
     
    import javax.crypto.BadPaddingException;
    import javax.crypto.Cipher;
    import javax.crypto.IllegalBlockSizeException;
    import javax.crypto.NoSuchPaddingException;
    import javax.crypto.SecretKey;
    import javax.crypto.spec.SecretKeySpec;
     
    import sun.misc.BASE64Decoder;
    import sun.misc.BASE64Encoder;
     
    /**
     *  
     * 
     */
    public class CryptoMessage {
     
        private SecretKey key;
     
        
        
        private void generateKey() throws NoSuchAlgorithmException {
        	String keyString ="matako"; // I made this key up by the way!
        	byte[] keyB = new byte[24]; // a Triple DES key is a byte[24] array
        	
        	for (int i = 0; i < keyString.length() && i < keyB.length; i++) {
        		keyB[i] = (byte) keyString.charAt(i);
        	}
        	
        	// Make the Key
        	 key = new SecretKeySpec(keyB, "DESede");
        }
        
     
        private String encrypt(String message) throws IllegalBlockSizeException,
    	    BadPaddingException, NoSuchAlgorithmException,
    	    NoSuchPaddingException, InvalidKeyException,
    	    UnsupportedEncodingException {
    	// Get a cipher object.
    	Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
    	cipher.init(Cipher.ENCRYPT_MODE, key);
     
    	// Gets the raw bytes to encrypt, UTF8 is needed for
    	// having a standard character set
    	byte[] stringBytes = message.getBytes("UTF8");
     
    	// encrypt using the cypher
    	byte[] raw = cipher.doFinal(stringBytes);
     
    	// converts to base64 for easier display.
    	BASE64Encoder encoder = new BASE64Encoder();
    	String base64 = encoder.encode(raw);
     
    	return base64;
        }
     
          private String decrypt(String encrypted) throws InvalidKeyException,
    	    NoSuchAlgorithmException, NoSuchPaddingException,
    	    IllegalBlockSizeException, BadPaddingException, IOException {
     
    		// Get a cipher object.
    		Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
    		cipher.init(Cipher.DECRYPT_MODE, key);
     
    		//decode the BASE64 coded message
    		BASE64Decoder decoder = new BASE64Decoder();
    		byte[] raw = decoder.decodeBuffer(encrypted);
     
    		//decode the message
    		byte[] stringBytes = cipher.doFinal(raw);
     
    		//converts the decoded message to a String
    		String clear = new String(stringBytes, "UTF8");
    		return clear;
        }
     
        public CryptoMessage(String message) {
    	try {
    	    System.out.println("clear message: " + message);
     
    	    if(key == null){
    	    	generateKey();
    	    }
    	    System.out.println("key: " + key);
    	    String encrypted = encrypt(message);
    	    System.out.println("encrypted message: " + encrypted);
     
    	    String decrypted = decrypt(encrypted);
    	    System.out.println("decrypted message: " + decrypted);
     
    	} catch (NoSuchAlgorithmException e) {
    	    e.printStackTrace();
    	} catch (NoSuchPaddingException e) {
    	    e.printStackTrace();
    	} catch (InvalidKeyException e) {
    	    e.printStackTrace();
    	} catch (UnsupportedEncodingException e) {
    	    e.printStackTrace();
    	} catch (IllegalBlockSizeException e) {
    	    e.printStackTrace();
    	} catch (BadPaddingException e) {
    	    e.printStackTrace();
    	} catch (IOException e) {
    	    e.printStackTrace();
    	}
        }
     
        /**
         * 
         * @param args
         */
        public static void main(String[] args) {
    	    new CryptoMessage("KIB2001002");
     
        }
     
    }
    From what I understand, a triple DES key has to be a 24 byte array. What are the alternatives so that the encrypted version I get back is of the same number of characters (or less, if possible) than the original text?

    Also, I am not sure as to how well suited this solution is to my problem. Will different Java Virtual Machines produce different keys, meaning that multiple installations will not be able to reproduce the same encryption given the same keyString?

    Is there a simpler solution, considering that the only thing I desire is that the USER_ID is obfuscated to the human eye (doctors, nurses, prying eyes cannot tell WHO these blood results belong to, only the system can). In which case is there not a simple(r) obfuscation algorithm I could use?

    Thanks in advance,
    Tumaini
  2. #2
  3. No Profile Picture
    Contributing User
    Devshed Novice (500 - 999 posts)

    Join Date
    May 2007
    Posts
    765
    Rep Power
    928
    Originally Posted by blakkphoenixx
    The code am using is just something I picked up off the net, I have NO experience with encryption.
    Crypto is hard to get right. If you don't know what you're doing, it would be best to stop and learn about crypto first. Copy-paste of random code from the internet is an especially bad idea when working with crypto. For instance, that code you posted has three(!) weaknesses I can see right off--one that would let me break your encryption easily.

    Originally Posted by blakkphoenixx
    The problem is that even though the string to be encrypted should (and always will be) 10 characters long, the result is always 24 characters.
    There are a number of factors involved: DES encryption always accepts/produces data that is a multiple of 8 bytes long. The PKCS#5 padding you requested will always increase the length of the data by at least one byte. Base64 encoding will increase the length of the data by 33%.

    Originally Posted by blakkphoenixx
    Am sure there must be a way whereby I encrypt a 10 character string and get back a ten character encrypted version?
    You can use CFB mode (instead of ECB) for encryption which will retain the length of the input, but I think what you want is format preserving encryption. Java supports CFB using an algorithm name of "DESede/CFB8/NoPadding". I don't know of any support for format preserving encryption in Java's standard crypto.

    Originally Posted by blakkphoenixx
    Is there a simpler solution, considering that the only thing I desire is that the USER_ID is obfuscated to the human eye (doctors, nurses, prying eyes cannot tell WHO these blood results belong to, only the system can). In which case is there not a simple(r) obfuscation algorithm I could use?
    Generate a random ID storing the original User_ID and the random ID in your database. Your code (that has access to the database) can match them up later. No one else can.
    sub{*{$::{$_}}{CODE}==$_[0]&& print for(%:: )}->(\&Meh);
  4. #3
  5. No Profile Picture
    Registered User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Nov 2011
    Posts
    13
    Rep Power
    0
    Thanks for the response. I have adjusted the code so now it is using DESede/CFB8/NoPadding, like so:

    Code:
     
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.security.InvalidKeyException;
    import java.security.NoSuchAlgorithmException;
     
    import javax.crypto.BadPaddingException;
    import javax.crypto.Cipher;
    import javax.crypto.IllegalBlockSizeException;
    import javax.crypto.NoSuchPaddingException;
    import javax.crypto.SecretKey;
    import javax.crypto.spec.SecretKeySpec;
     
    import sun.misc.BASE64Decoder;
    import sun.misc.BASE64Encoder;
     
    /**
     *  
     * 
     */
    public class CryptoMessage {
     
        private SecretKey key;
     
        
        
        private void generateKey() throws NoSuchAlgorithmException {
        	String keyString ="matako"; // I made this key up by the way!
        	byte[] keyB = new byte[24]; // a Triple DES key is a byte[24] array
        	
        	for (int i = 0; i < keyString.length() && i < keyB.length; i++) {
        		keyB[i] = (byte) keyString.charAt(i);
        	}
        	
        	// Make the Key
        	 key = new SecretKeySpec(keyB, "DESede");
        }
        
     
        private String encrypt(String message) throws IllegalBlockSizeException,
    	    BadPaddingException, NoSuchAlgorithmException,
    	    NoSuchPaddingException, InvalidKeyException,
    	    UnsupportedEncodingException {
    	// Get a cipher object.
    	Cipher cipher = Cipher.getInstance("DESede/CFB8/NoPadding");
    	cipher.init(Cipher.ENCRYPT_MODE, key);
     
    	// Gets the raw bytes to encrypt, UTF8 is needed for
    	// having a standard character set
    	byte[] stringBytes = message.getBytes("UTF8");
     
    	// encrypt using the cypher
    	byte[] raw = cipher.doFinal(stringBytes);
     
    	// converts to base64 for easier display.
    	BASE64Encoder encoder = new BASE64Encoder();
    	String base64 = encoder.encode(raw);
     
    	return base64;
        }
     
          private String decrypt(String encrypted) throws InvalidKeyException,
    	    NoSuchAlgorithmException, NoSuchPaddingException,
    	    IllegalBlockSizeException, BadPaddingException, IOException {
     
    		// Get a cipher object.
    		Cipher cipher = Cipher.getInstance("DESede/CFB8/NoPadding");
    		cipher.init(Cipher.DECRYPT_MODE, key);
     
    		//decode the BASE64 coded message
    		BASE64Decoder decoder = new BASE64Decoder();
    		byte[] raw = decoder.decodeBuffer(encrypted);
     
    		//decode the message
    		byte[] stringBytes = cipher.doFinal(raw);
     
    		//converts the decoded message to a String
    		String clear = new String(stringBytes, "UTF8");
    		return clear;
        }
     
        public CryptoMessage(String message) {
    	try {
    	    System.out.println("clear message: " + message);
     
    	    if(key == null){
    	    	generateKey();
    	    }
    	    System.out.println("key: " + key);
    	    String encrypted = encrypt(message);
    	    System.out.println("encrypted message: " + encrypted);
     
    	    String decrypted = decrypt(encrypted);
    	    System.out.println("decrypted message: " + decrypted);
     
    	} catch (NoSuchAlgorithmException e) {
    	    e.printStackTrace();
    	} catch (NoSuchPaddingException e) {
    	    e.printStackTrace();
    	} catch (InvalidKeyException e) {
    	    e.printStackTrace();
    	} catch (UnsupportedEncodingException e) {
    	    e.printStackTrace();
    	} catch (IllegalBlockSizeException e) {
    	    e.printStackTrace();
    	} catch (BadPaddingException e) {
    	    e.printStackTrace();
    	} catch (IOException e) {
    	    e.printStackTrace();
    	}
        }
     
        /**
         * 
         * @param args
         */
        public static void main(String[] args) {
    	    new CryptoMessage("KIB2001002");
     
        }
     
    }
    Now I get this output:

    Code:
    clear message: KIB2001002
    key: javax.crypto.spec.SecretKeySpec@b069b417
    encrypted message: k7R+c+gSPyXiZQ==
    java.security.InvalidKeyException: Parameters missing
    	at com.sun.crypto.provider.SunJCE_f.a(DashoA13*..)
    	at com.sun.crypto.provider.DESedeCipher.engineInit(DashoA13*..)
    	at javax.crypto.Cipher.a(DashoA13*..)
    	at javax.crypto.Cipher.a(DashoA13*..)
    	at javax.crypto.Cipher.init(DashoA13*..)
    	at javax.crypto.Cipher.init(DashoA13*..)
    	at CryptoMessage.decrypt(CryptoMessage.java:68)
    	at CryptoMessage.<init>(CryptoMessage.java:93)
    	at CryptoMessage.main(CryptoMessage.java:118)
    It looks like it ENCRYPTS fine (albeit 16 characters and not 10), but when it tries to decrypt somwthing goes wrong, specifically on line :

    Code:
    cipher.init(Cipher.DECRYPT_MODE, key);
    Also, you mention 3 weaknesses, one of them major, could you point them out to me please?

    Thanks in advance,
    Tumaini
  6. #4
  7. No Profile Picture
    Registered User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Nov 2011
    Posts
    13
    Rep Power
    0
    After the alterations I also noticed that the encrypted String is not constant, it keeps changing. I need for the encrypted String to stay the same, as the study is a longitudinal study and there is a requirement that over time, the encrypted key can constantly be linked to the same individual over time...
  8. #5
  9. No Profile Picture
    Contributing User
    Devshed Novice (500 - 999 posts)

    Join Date
    May 2007
    Posts
    765
    Rep Power
    928
    Let me be more blunt: Good crypto is hard. Bad crypto is dangerous. Do not use crypto until you know what you're doing. There are many good resources to get you started; I tend to point to RSA's Cryptography FAQ (PDF) when asked. If you aren't willing to understand what you're doing, your only (ethical) alternative is to hire someone who does understand cryptography.

    Originally Posted by blakkphoenixx
    ...albeit 16 characters and not 10...
    As I pointed out above, base64 encoded data is 33% larger than the original. If you want to encrypt without changing the length and retain a printable character set you need a format-preserving encryption algorithm.

    After the alterations I also noticed that the encrypted String is not constant, it keeps changing.
    I'm no expert on Java crypto, but CFB encryption requires an IV and I believe Java generates a random one if you fail to supply one. (Which would explain the exception on decrypt if the IV has no default value.)

    Originally Posted by blakkphoenixx
    Also, you mention 3 weaknesses, one of them major, could you point them out to me please?
    Not if you're going to patch them over and release your security-hole filled code into a world filled with people who have more time than me and more crypto experience than me to attack it with (which is kind of the impression I've getting). I try to avoid doing irresponsible things that's aren't immediately and harmlessly amusing.


    Incidentally, From the information you've added in your last posts, it sounds like you really want a one-way-function rather than an encryption algorithm.
    sub{*{$::{$_}}{CODE}==$_[0]&& print for(%:: )}->(\&Meh);
  10. #6
  11. No Profile Picture
    Lost in code
    Devshed Supreme Being (6500+ posts)

    Join Date
    Dec 2004
    Posts
    8,301
    Rep Power
    7170
    After the alterations I also noticed that the encrypted String is not constant, it keeps changing.
    This is a rather important property of the algorithm. Your encrypted data would be vulnerable if the algorithm didn't produce different results each time. Like OmegaZero mentioned, this is probably because it's using a random IV, so you could force it to produce the same output by re-using the same IV, however re-using an IV is not secure.

    In order to decrypt the encrypted data you need to know both the key and the IV (which should be different for each message). This means you would need to store the IV with the ciphertext, which will significantly increase its length.

    It's probably going to be difficult to find a secure algorithm that can encrypt 80 bits into 80 bits. Encrypting data does involve some overhead, and with only 10 bytes you don't have a lot (any, actually) of room for overhead.

    Do you actually need to be able to decrypt the data? But even a secure hashing algorithm is going to produce more than 10 bytes of output.
    PHP FAQ

    Originally Posted by Spad
    Ah USB, the only rectangular connector where you have to make 3 attempts before you get it the right way around
  12. #7
  13. No Profile Picture
    Registered User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Nov 2011
    Posts
    13
    Rep Power
    0
    Originally Posted by OmegaZero
    Not if you're going to patch them over and release your security-hole filled code into a world filled with people who have more time than me and more crypto experience than me to attack it with (which is kind of the impression I've getting). I try to avoid doing irresponsible things that's aren't immediately and harmlessly amusing.
    I understand your point, but this is more of a closed (institutionally internal) system, not for global release. Even if it were released globally, it would make little sense. Here is what I mean: We have individuals in cohort studies who are given ID's unique to our institution e.g KIG0001001. A different study wants to use these same individuals for a more privacy sensitive study, hence the need to encrypt KIG0001001 into something obscure like QTkx8kmSsaOqcalkcMwUmQ==. This is only to prevent those within the institution from knowing who is who, as they are the only ones who can link KIG0001001. Even if Anyone outside the institution decrypts QTkx8kmSsaOqcalkcMwUmQ== to be KIG0001001, KIG0001001 still means next to nothing to somebody NOT within the institute. Afterall, the database which stores the encrypted individuals is physically separate from the database which stores the unencrypted individuals (and the database which stores encrypted individuals is not exposed to the world), so an external hacker would have to have access to both.

    Like I said, am no expert, and I probably should get an expert cryptographer as you suggest, but you don't bump into too many of those in Tanzania unfortunately. However, the cryptography required is to obfuscate personal details from prying eyes within the institute rather than without, as the unencrypted strings only have meaning to people within the institute. So any sort of simple obfuscation that can be implemented in Java that will prevent internal staff making the connections between encrypted and unencrypted individuals is what I need.

    Any advice, and all advice so far, greatly appreciated
  14. #8
  15. No Profile Picture
    Lost in code
    Devshed Supreme Being (6500+ posts)

    Join Date
    Dec 2004
    Posts
    8,301
    Rep Power
    7170
    For the new study, just generate a new random ID for each user. Keep a private database mapping the new IDs to the originals.
    PHP FAQ

    Originally Posted by Spad
    Ah USB, the only rectangular connector where you have to make 3 attempts before you get it the right way around

IMN logo majestic logo threadwatch logo seochat tools logo