Sensitive data (such as encryption keys, passwords, social security numbers, credit card numbers, etc.) stored in memory can be leaked if it is stored in a managed String object.
It would seem logical to collect and store the password in an object of type java.lang.String.
However, Objects of type String are immutable, i.e., there are no methods defined that allow you to change (overwrite) or zero out the contents of a String after usage at least until it is garbage collected.
It would seem logical to collect and store the password in an object of type java.lang.String.
However, Objects of type String are immutable, i.e., there are no methods defined that allow you to change (overwrite) or zero out the contents of a String after usage at least until it is garbage collected.
This feature makes String objects unsuitable for storing security sensitive information such as user passwords.
You should always collect and store security sensitive information in a char array instead i,e when all operations are finished with Char[], it can be overwritten with zero’s or junk text to clear it from memory.
Some people believe that , Instead of String, use Char[] for any sensitive data , but i will proof that char[] is still not secure in JVM heap memory.
GuardedString can protect the sensitive data against heap or memory dumps , i will show it with the proof.
GuardedString
Secure string implementation that solves the problems associated with keeping passwords as java.lang.String. That is, anything represented as a String is kept in memory as a clear text password and stays in memory at least until it is garbage collected.
The GuardedString class alleviates this problem by storing the characters in memory in an encrypted form.The encryption key will be a randomly-generated key.
In their serialized form, GuardedString will be encrypted using a known default key. This is to provide a minimum level of protection regardless of the transport. For communications with the Remote Connector Framework it is recommended that deployments enable SSL for true encryption.
GuardedString is packed into connid-framework JAR, declared as Maven dependency:
1. framework-0.4.3.jar
org.syncope.identityconnectors framework 0.4.3
2. framework-internal-0.4.3.jar
org.syncope.identityconnectors framework-internal 0.4.3 runtime
Store secure password using GuardedString :
GuardedString guardedString = new GuardedString("Secure_123!".toCharArray());
Below are the steps executed internally to retain the GuardedString inside JVM Heap :
- Initialized the GuardedString from the given clear characters.
- Converts chars to bytes without using any external functions that might allocate additional buffers for the potentially sensitive data. This guarantees the caller that they only need to cleanup the input and result.
- Call a new RandomEncryptor , generate a random key from KeyGenerator using AES and generate IV using default IV bytes.
- Encrypt the byte data using "AES/CBC/PKCS5Padding" with the above generated Key and IvParameterSpec.
- Computes the base 64 encoded SHA1 hash of the input , so that two GuardedString instance can be compared with the input clear characters.
- Clears an array of potentially sensitive bytes
Access secure password from GuardedString :
final StringBuilder clearPwd = new StringBuilder(); guardedString.access(new GuardedString.Accessor() { @Override public void access(final char[] clearChars) { clearPwd.append(clearChars); } }); String plainText = clearPwd.toString(); System.out.println(plainText);
Below are the steps executed internally to access the GuardedString from JVM Heap :
- Provides access to the clear-text value of the string in a controlled fashion. The clear-text characters will only be available for the duration of the call and automatically zeroed out following the call.
- Check encrypted data disposed or not i,e in-memory representation of the string is cleared or not.
- If disposed then throw String is disposed other wise decrypt the encrypted bytes using same Key and IvParameterSpec retained with that object.
- Converts bytes to chars without using any external functions that might allocate additional buffers for the potentially sensitive data. This guarantees the caller that they only need to cleanup the input and result.
- Clears an array of potentially sensitive bytes.
Full Example :
import org.identityconnectors.common.security.GuardedString; public class GuradedStringExample { public static void main(String[] args) { GuardedString guardedString = new GuardedString("Sridhar".toCharArray()); guardedString.makeReadOnly(); System.out.println("Guarded String : " + guardedString.toString()); final StringBuilder clearPwd = new StringBuilder(); guardedString.access(new GuardedString.Accessor() { @Override public void access(final char[] clearChars) { clearPwd.append(clearChars); } }); String plainText = clearPwd.toString(); guardedString.dispose(); System.out.println("Plain Text : " + plainText); } }
Output:
Guarded String : org.identityconnectors.common.security.GuardedString@233c1b9a
Plain Text : Sridhar
Why dispose a GuardedString ?
- If You are no longer to use the GuardedString after accessing the plain text ,then call the dispose method so that in-memory representation of the string will be cleared.
guardedString.dispose();
- Once it disposed, You can't append or modify the GuardedString, if you will try to modify it then it will throw "java.lang.IllegalStateException: String is disposed"
guardedString.dispose(); guardedString.appendChar('a');
- Also once disposed , Even you can't access any more the clear text from GuardedString, if you will try to access it then it will throw "java.lang.IllegalStateException: String is disposed"
guardedString.dispose(); final StringBuilder clearPwd = new StringBuilder(); guardedString.access(new GuardedString.Accessor() { @Override public void access(final char[] clearChars) { clearPwd.append(clearChars); } });
Why make ReadOnly a GuardedString ?
- You can make a GuardedString as readonly by calling makeReadOnly() method, so that no one can modify the content of the plain text.
guardedString.makeReadOnly();
- Once You made it as read only, then you can't append or modify the string , if you will try to modify it, immediately it will throw "java.lang.IllegalStateException: String is read-only"
guardedString.makeReadOnly(); guardedString.appendChar('a');
- You can check whether a String has been marked read-only or not by calling isReadOnly() method, if the return value is true then it has already marked as read-only, hence you can't append any character into it.
if(!guardedString.isReadOnly()){ guardedString.appendChar('a'); }
Can we make ReadOnly and dispose both on a String?
- From above we concluded that,once dispose method called, the in-memory representation of a string will get cleared. So on whom we will apply the readOnly , that's why we can't call a makeReadOnly() method after disposing a string, if you will try to do it then it will immediately throw "java.lang.IllegalStateException: String is disposed"
guardedString.dispose(); guardedString.makeReadOnly();
guardedString.dispose(); guardedString.dispose();
- At most only one time you can call a dispose() method on a GuardedString and you can call it from any where ,just only make sure once a string disposed you can't no longer to access it or modify it. Hence you can dispose a read only string without any issue.
guardedString.makeReadOnly(); guardedString.dispose();
Can we compare two GuardedString objects like String ?
- Yes,Two GuardedStrings will be equals only when both strings having the same secure hash
GuardedString guardedString1 = new GuardedString("Secure123!".toCharArray()); GuardedString guardedString2 = new GuardedString("Secure123!".toCharArray()); // Two GuardedString are equals only when both strings having the same secure hash if(guardedString1.equals(guardedString2)){ System.out.println("Both string having same secure hash"); }
- You can find a secure hash of String using SHA MessageDigest and Base64
// Find the secure hash of string "Secure123!" byte[] byteData = SecurityUtil.charsToBytes("Secure123!".toCharArray()); MessageDigest hasher = MessageDigest.getInstance("SHA"); byte [] data = hasher.digest(byteData); String base64SHA1Hash = Base64.encode(data);
- You can easily verify the secure hash value of a String with GuardedString explicitly by calling verifyBase64SHA1Hash() method
// Verify the Base64SHA1Hash explicitly guardedString1.verifyBase64SHA1Hash(base64SHA1Hash);
Full Example :
import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import org.identityconnectors.common.Base64; import org.identityconnectors.common.security.GuardedString; import org.identityconnectors.common.security.SecurityUtil; public class GuradedStringSecureHashExample { public static void main(String[] args) throws NoSuchAlgorithmException { GuardedString guardedString1 = new GuardedString("Secure123!".toCharArray()); GuardedString guardedString2 = new GuardedString("Secure123!".toCharArray()); // Two GuardedString are equals only when both strings having the same secure hash if(guardedString1.equals(guardedString2)){ System.out.println("Both string having same secure hash"); } // Find the secure hash of string "Secure123!" byte[] byteData = SecurityUtil.charsToBytes("Secure123!".toCharArray()); MessageDigest hasher = MessageDigest.getInstance("SHA"); byte [] data = hasher.digest(byteData); String base64SHA1Hash = Base64.encode(data); System.out.println("Secure Hash of Secure123! : "+base64SHA1Hash); // Verify the Base64SHA1Hash explicitly boolean isVerified = guardedString1.verifyBase64SHA1Hash(base64SHA1Hash); System.out.println(isVerified); } }
Output:
Both string having same secure hash
Secure Hash of Secure123! : aP4bwUJ3lKTnT560tWVN/h8actg=
true
Can we create clone or deep copy of a GuardedString ?
- Yes,you can create the copy of a GuardedString by calling copy() method, but make sure the copy will not be read-only if the instance is read-only and you can't create a copy on a disposed string.
import java.security.NoSuchAlgorithmException; import org.identityconnectors.common.security.GuardedString; public class GuradedStringSecureHashExample { public static void main(String[] args) throws NoSuchAlgorithmException { GuardedString guardedString = new GuardedString("Sridhar".toCharArray()); GuardedString cloneString = guardedString.copy(); guardedString.appendChar('J'); if(guardedString.equals(cloneString)){ System.out.println("Both are having same hash"); }else{ System.out.println("Both are having different hash"); } System.out.println("Guarded String : " + guardedString.hashCode()); System.out.println("Clone String : " + cloneString.hashCode()); } }
Output:
Both are having different hash
Guarded String : -1585176579
Clone String : 541992091
Verify how GS is secured , but not both String and char[]
- Run the below example GuradedStringHeapDumpExample.java.
- Take the heap dump while running it, i have used jmap command to take the heap i,e
jmap -dump:live,file=/tmp/securepassword2.hprof <processid>
- Import the heap dumped file(securepassword2.hprof) into Memory Analyzer (MAT).
MAT:
- The Eclipse Memory Analyzer is a fast and feature-rich Java heap analyzer that helps you find memory leaks and reduce memory consumption.
- Use the Memory Analyzer to analyze productive heap dumps with hundreds of millions of objects, quickly calculate the retained sizes of objects, see who is preventing the Garbage Collector from collecting objects, run a report to automatically extract leak suspects.
- Download MAT from here download
GuradedStringHeapDumpExample.java
import java.util.ArrayList; import java.util.List; import org.identityconnectors.common.security.GuardedString; public class GuradedStringHeapDumpExample { public static void main(String[] args) { List<PasswordStorage> passwordStorages = new ArrayList<>(); for(int i=0;i<100000;i++){ passwordStorages.add(new PasswordStorage()); } } } class PasswordStorage { private String plainPassword = "Secure_123!"; private GuardedString guardedPassword = new GuardedString("Secure_123!".toCharArray()); private char[] charArrayPassword = "Secure_123!".toCharArray(); public String getPlainPassword() { return plainPassword; } public void setPlainPassword(String plainPassword) { this.plainPassword = plainPassword; } public GuardedString getGuardedPassword() { return guardedPassword; } public void setGuardedPassword(GuardedString guardedPassword) { this.guardedPassword = guardedPassword; } public char[] getCharArrayPassword() { return charArrayPassword; } public void setCharArrayPassword(char[] charArrayPassword) { this.charArrayPassword = charArrayPassword; } }
Analyze the sensitive password leakage from heap memory dump :
Step 1 : After importing the dump , you will get leak suspect reports from dashboard and go to the list_objects suspect report.
Step 2 : You can now able to see the object variable values of class PasswordStorage i,e plainPassword in String ,guardedPassword in GuardedString and charArrayPassword in char[]
i,e char[] and String values are clearly visible with the plain text password.
Step 3: There is no difference whether you are using String or char[] to store the plain text password, as String is internally using only char[] its self , see the below image.
Step 4: GuardedString internally encrypt the byte array data using secure random key and generate the 64 encoded SHA1 hash.
Step 5: GuardedString used a symmetric AES algorithm with the unique random IvParameterSpec and Key by calling RandomEncryptor.
Nice blog, Sridhar.
ReplyDeleteCan I say its an improved version of https://bit.ly/2sbqbc8 and https://bit.ly/2IT6XSL these crypto theory. I'm not sure what happens at JVM/Heap level.
Thanks for the blog.
Thanks Pravin for your feedback. Crypto theory is a different concept ,i will shortly post a blog on it.
DeleteNice Post Sir..
ReplyDeleteDetailed explanation along with program snippet which helps to clear an idea so easily.
I will encourage you to target beginners as well so it will help them to gain mastery in their Java skills.
Wish you good luck for your future posts.
Thank you Sourabh for your feedback.
DeleteVery helpful post to understand secure data storage in java....thanks shtidhar bhai.....waiting for more posts like this
ReplyDeleteThank you Devendra for your feedback , yes there will be more posts in coming days.
DeleteThe Java platform allows users to download untrusted code over a network. It cannot infect the host system with a virus, cannot read or write files from the hard drive, This capability alone makes the Java platform unique. Free Random Password Generator
ReplyDeleteIn real time production environments , generally application monitoring tools like zabbix, Hyperic etc are being used
Deleteto monitor the remote instances for performance management , heap memory , CPU and System RAM utilization's on the fly.
Different VM arguments need to configure on remote application servers to enabling remote JMX monitoring,
-Dcom.sun.management.jmxremote=true
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.local.only=false
-Dcom.sun.management.jmxremote.ssl=false
-Djava.rmi.server.hostname=172.xx.xxx.190
-Dcom.sun.management.jmxremote.port=9999
-Dcom.sun.management.jmxremote.rmi.port=9998
Any one can easily take the remote heap dump and read the sensitive data from memory.
The short answer is that Nevada, Wyoming (and a couple of different states) do offer privacy assurances, yet that is no certification you'll secure your advantages or keep away from a risk for your direct. getmoreprivacy
ReplyDeleteWondering if there is a way to transfer the GuardedString without placing either a StringBuilder or String onto the heap? We use code like this,
ReplyDelete@Test
public void testGuardedString() throws Exception {
GuardedString guardedString = new GuardedString("some_secret_string".toCharArray());
guardedString.access(chars ->{
String clearText = new String(chars);
});
}
The string above is placed onto the heap. Thoughts?
nice idea, good work
ReplyDeleteThanks Sridhar. It is very nice post.
ReplyDeleteThank you very much for your post. Is there any equivalent solution for Android? I tried using this Java Library in my Android app, but I was not succesfull.
ReplyDelete