{"id":552,"date":"2012-08-25T11:27:43","date_gmt":"2012-08-25T10:27:43","guid":{"rendered":"http:\/\/www.floyd.ch\/?p=552"},"modified":"2022-02-09T11:12:46","modified_gmt":"2022-02-09T10:12:46","slug":"a-weird-idea-java-strings-and-memory","status":"publish","type":"post","link":"https:\/\/www.floyd.ch\/?p=552","title":{"rendered":"A Weird Idea &#8211; Java, Strings and Memory"},"content":{"rendered":"<p>Disclaimer: I&#8217;m not a Java internals specialist. Let me know if I got a point wrong here.<\/p>\n<p>Wiping sensitive information when you don&#8217;t need it anymore is a very basic security rule. It makes sense to everyone when we talk about hard discs, but the same rule should be applied to memory (RAM). While memory management is quite straight forward in C derived languages (eg. allocate some memory, override it with random bytes when the sensitive information is not needed anymore), it&#8217;s quite hard to do it in Java. Is it even possible? Java Strings are immutable, that means you can not change the memory allocated for a String. The Java VM will make a copy of the String and change the copy. So from this perspective, <a href=\"https:\/\/docs.oracle.com\/javase\/1.5.0\/docs\/guide\/security\/jce\/JCERefGuide.html#PBEEx\" target=\"_blank\" rel=\"noopener\">Strings are not a good way to store sensitive information<\/a>. So let&#8217;s do it in char arrays I thought and wrote the following Java class:<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\r\n\/**\r\n* ----------------------------------------------------------------------------\r\n* &amp;quot;THE BEER-WARE LICENSE&amp;quot; (Revision 42):\r\n* &amp;lt;floyd at floyd dot ch&amp;gt; wrote this file. As long as you retain this notice you\r\n* can do whatever you want with this stuff. If we meet some day, and you think\r\n* this stuff is worth it, you can buy me a beer in return\r\n* floyd http:\/\/floyd.ch @floyd_ch &amp;lt;floyd at floyd dot ch&amp;gt;\r\n* August 2012\r\n* ----------------------------------------------------------------------------\r\n**\/\r\n\r\nimport java.util.Arrays;\r\nimport java.io.IOException;\r\nimport java.io.Serializable;\r\nimport java.lang.Exception;\r\n\r\n\/**\r\n * ATTENTION: This method is only ASCII safe, not Unicode.\r\n * It should be used to store sensitive information which are ASCII\r\n * \r\n * This class keeps track of the passed in char&#x5B;] and wipes all the old arrays\r\n * whenever they are changed. This means:\r\n * \r\n * char&#x5B;] r1 = {0x41, 0x41, 0x41, 0x41, 0x41, 0x41};\r\n * SecureString k = new SecureString(r1);\r\n * k.destroy();\r\n * System.out.println(r1); \/\/Attention! r1 will be {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}\r\n * \r\n * char&#x5B;] r2 = {0x41, 0x41, 0x41, 0x41, 0x41, 0x41};\r\n * SecureString k = new SecureString(r2);\r\n * k.append('C');\r\n * System.out.println(r2); \/\/Attention! r2 will be {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}\r\n * System.out.println(k.getByteArray()); \/\/correct\r\n * \r\n * General rule for this class:\r\n * - Never call toString() of this class. It will just return null:\r\n * char&#x5B;] r3 = {0x41, 0x41, 0x41, 0x41, 0x41, 0x41};\r\n * SecureString k = new SecureString(r3);\r\n * System.out.println(k); \/\/will print null, because it calls toString()\r\n * \r\n * - Choose the place to do the destroy() wisely. It should be done when an operation\r\n * is finished and the string is not needed anymore.\r\n *\/\r\n\r\npublic class SecureString implements Serializable{\r\n\tprivate static final long serialVersionUID = 1L;\r\n\tprivate char&#x5B;] charRepresentation;\r\n\tprivate char&#x5B;] charRepresentationLower;\r\n\tprivate byte&#x5B;] byteRepresentation;\r\n\tprivate boolean isDestroyed = false;\r\n\tprivate int length=0;\r\n\tprivate final static int OBFUSCATOR_BYTE = 0x55;\r\n\t\r\n\t\/**\r\n\t * After calling the constructor you should NOT wipe the char&#x5B;]\r\n\t * you just passed in (the stringAsChars reference)\r\n\t * @param stringAsChars\r\n\t *\/\r\n\tpublic SecureString(char&#x5B;] stringAsChars){\r\n\t\t\/\/Pointing to the same address as original char&#x5B;]\r\n\t\tthis.length = stringAsChars.length;\r\n\t\tthis.initialise(stringAsChars);\r\n\t}\r\n\t\r\n\t\/**\r\n\t * After calling the constructor you should NOT wipe the byte&#x5B;]\r\n\t * you just passed in (the stringAsBytes reference)\r\n\t * @param stringAsBytes\r\n\t *\/\r\n\tpublic SecureString(byte&#x5B;] stringAsBytes){\r\n\t\t\/\/Pointing to the same address as original byte&#x5B;]\r\n\t\tthis.length = stringAsBytes.length;\r\n\t\tthis.initialise(stringAsBytes);\r\n\t}\r\n\t\r\n\t\/**\r\n\t * @return length of the string\r\n\t *\/\r\n\tpublic int length(){\r\n\t\treturn this.length;\r\n\t}\r\n\t\r\n\t\/**\r\n\t * Initialising entire object based on a char array\r\n\t * @param stringAsChars\r\n\t *\/\r\n\tprivate void initialise(char&#x5B;] stringAsChars){\r\n\t\tthis.length = stringAsChars.length;\r\n\t\tcharRepresentation = stringAsChars; \r\n\t\tcharRepresentationLower = new char&#x5B;stringAsChars.length];\r\n\t\tbyteRepresentation = new byte&#x5B;stringAsChars.length];\r\n\t\tfor(int i=0; i&amp;lt;stringAsChars.length; i++){\r\n\t\t\tcharRepresentationLower&#x5B;i] = Character.toLowerCase(charRepresentation&#x5B;i]);\r\n\t\t\tbyteRepresentation&#x5B;i] = (byte)charRepresentation&#x5B;i];\r\n\t\t}\r\n\t}\r\n\t\r\n\t\/**\r\n\t * Initializing entire object based on a byte array\r\n\t * @param stringAsBytes\r\n\t *\/\r\n\tprivate void initialise(byte&#x5B;] stringAsBytes){\r\n\t\tthis.length = stringAsBytes.length;\r\n\t\tbyteRepresentation = stringAsBytes;\r\n\t\tcharRepresentation = new char&#x5B;stringAsBytes.length];\r\n\t\tcharRepresentationLower = new char&#x5B;stringAsBytes.length];\r\n\t\tfor(int i=0; i&amp;lt;stringAsBytes.length; i++){\r\n\t\t\tcharRepresentation&#x5B;i] = (char) byteRepresentation&#x5B;i];\r\n\t\t\tcharRepresentationLower&#x5B;i] = Character.toLowerCase(charRepresentation&#x5B;i]);\r\n\t\t}\r\n\t}\r\n\t\r\n\t\/**\r\n\t * We first obfuscate the string by XORing with OBFUSCATOR_BYTE\r\n\t * So it doesn't get serialized as plain text. THIS METHOD\r\n\t * WILL DESTROY THIS OBJECT AT THE END.\r\n\t * @param out\r\n\t * @throws IOException\r\n\t *\/\r\n\tprivate void writeObject(java.io.ObjectOutputStream out) throws IOException{\t\t\r\n\t\tout.writeInt(byteRepresentation.length);\r\n\t\tfor(int i = 0; i &amp;lt; byteRepresentation.length; i++ ){\r\n\t\t\tout.write((byte)(byteRepresentation&#x5B;i]) ^ (byte)(OBFUSCATOR_BYTE));\r\n\t\t}\r\n\t\t\/\/TODO: Test if the Android Intent is really only calling writeObject once,\r\n\t\t\/\/otherwise this could be a problem. Then I suggest that the SecureString\r\n\t\t\/\/is not passed in an intent (and never serialized)\r\n\t\tdestroy();\r\n\t}\r\n\t\r\n\t\/**\r\n\t * We first have to deobfuscate the string by XORing with OBFUSCATOR_BYTE\r\n\t * @param out\r\n\t * @throws IOException\r\n\t *\/\r\n\tprivate void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{\r\n\t\t\/\/we first have to deobfuscate\r\n\t\tint newLength = in.readInt();\r\n\t\tbyteRepresentation = new byte&#x5B;newLength];\r\n\t\tfor(int i = 0; i &amp;lt; newLength; i++ ){\r\n\t\t\tbyteRepresentation&#x5B;i] = (byte) ((byte)(in.read()) ^ (byte)(OBFUSCATOR_BYTE));\r\n\t\t}\r\n\t\tinitialise(byteRepresentation);\r\n\t}\r\n\t\r\n\t\/**\r\n\t * Use this method wisely\r\n\t * \r\n\t * When the destroy() method of this SecureString is called, the byte&#x5B;] \r\n\t * this method had returned will also be wiped!\r\n\t * \r\n\t * Never WRITE directly to this byte&#x5B;], but only READ\r\n\t * \r\n\t * @return reference to byte&#x5B;] of this SecureString\r\n\t *\/\r\n\tpublic byte&#x5B;] getByteArray(){\r\n\t\tif(this.isDestroyed)\r\n\t\t\treturn null;\r\n\t\treturn byteRepresentation;\r\n\t}\r\n\t\r\n\t\/**\r\n\t * This method should never be called.\r\n\t *\/\r\n\t@Deprecated\r\n\t@Override\r\n\tpublic String toString(){\r\n\t\treturn null;\r\n\t}\r\n\t\r\n\t\/**\r\n\t * This method should never be called, because it means you already stored the\r\n\t * sensitive information in a String.\r\n\t * @param string\r\n\t * @return\r\n\t * @throws Exception\r\n\t *\/\r\n\t\r\n\t@Deprecated\r\n\tpublic boolean equals(String string) throws Exception{\r\n\t\tthrow new Exception(&amp;quot;YOU ARE NOT SUPPOSED TO CALL equals(String string) of SecureString, &amp;quot;+\r\n\t\t\t\t&amp;quot;because your are not supposed to have the other value in a string in the first place!&amp;quot;);\r\n\t}\r\n\t\/**\r\n\t * This method should never be called, because it means you already stored the\r\n\t * sensitive information in a String.\r\n\t * @param string\r\n\t * @return\r\n\t * @throws Exception\r\n\t *\/\r\n\t\r\n\t@Deprecated\r\n\tpublic boolean equalsIgnoreCase(String string) throws Exception{\r\n\t\tthrow new Exception(&amp;quot;YOU ARE NOT SUPPOSED TO CALL equalsIgnoreCase(String string) of SecureString, &amp;quot;+\r\n\t\t\t\t&amp;quot;because your are not supposed to have the other value in a string in the first place!&amp;quot;);\r\n\t}\r\n\t\/**\r\n\t * Comparing if the two strings stored in the SecureString objects are equal\r\n\t * @param secureString2\r\n\t * @return true if strings are equal\r\n\t *\/\r\n\tpublic boolean equals(SecureString secureString2){\r\n\t\tif(this.isDestroyed)\r\n\t\t\treturn false;\r\n\t\treturn Arrays.equals(this.charRepresentation, secureString2.charRepresentation);\r\n\t}\r\n\t\r\n\t\/**\r\n\t * Comparing if the two strings stored in the SecureString objects are equalsIgnoreCase\r\n\t * @param secureString2\r\n\t * @return true if strings are equal ignoring case\r\n\t *\/\r\n\tpublic boolean equalsIgnoreCase(SecureString secureString2){\r\n\t\tif(this.isDestroyed)\r\n\t\t\treturn false;\r\n\t\treturn Arrays.equals(this.charRepresentationLower, secureString2.charRepresentationLower);\r\n\t}\r\n\t\r\n\t\/**\r\n\t * Delete a char at the given position\r\n\t * @param index\r\n\t *\/\r\n\tpublic void deleteCharAt(int index){\r\n\t\tif(this.isDestroyed)\r\n\t\t\treturn;\r\n\t\tif(this.length == 0 || index &amp;gt;= length)\r\n\t\t\treturn;\r\n\t\twipe(charRepresentationLower);\r\n\t\twipe(byteRepresentation);\r\n\t\tchar&#x5B;] newCharRepresentation = new char&#x5B;length-1];\r\n\t\tfor(int i=0; i&amp;lt;index; i++)\r\n\t\t\tnewCharRepresentation&#x5B;i] = charRepresentation&#x5B;i];\r\n\t\tfor(int i=index+1; i&amp;lt;this.length-1; i++)\r\n\t\t\tnewCharRepresentation&#x5B;i-1] = charRepresentation&#x5B;i];\r\n\t\twipe(charRepresentation);\r\n\t\tinitialise(newCharRepresentation);\r\n\t}\r\n\t\r\n\t\/**\r\n\t * Append a char at the end of the string\r\n\t * @param c\r\n\t *\/\r\n\tpublic void append(char c){\r\n\t\twipe(charRepresentationLower);\r\n\t\twipe(byteRepresentation);\r\n\t\tchar&#x5B;] newCharRepresentation = new char&#x5B;length+1];\r\n\t\tfor(int i=0; i&amp;lt;this.length; i++)\r\n\t\t\tnewCharRepresentation&#x5B;i] = charRepresentation&#x5B;i];\r\n\t\tnewCharRepresentation&#x5B;length-1] = c;\r\n\t\twipe(charRepresentation);\r\n\t\tinitialise(newCharRepresentation);\r\n\t}\r\n\t\r\n\t\/**\r\n\t * Internal method to wipe a char&#x5B;]\r\n\t * @param chars\r\n\t *\/\r\n\tprivate void wipe(char&#x5B;] chars){\r\n\t\tfor(int i=0; i&amp;lt;chars.length; i++){\r\n\t\t\t\/\/set to hex AA\r\n\t\t\tint val = 0xAA;\r\n\t\t\tchars&#x5B;i] = (char) val; \r\n\t\t\t\/\/set to hex 55\r\n\t\t\tval = 0x55;\r\n\t\t\tchars&#x5B;i] = (char) val; \r\n\t\t\t\/\/set to hex 00\r\n\t\t\tval = 0x00;\r\n\t\t\tchars&#x5B;i] = (char) val; \r\n\t\t}\r\n\t}\r\n\t\r\n\t\/**\r\n\t * Internal method to wipe a byte&#x5B;]\r\n\t * @param chars\r\n\t *\/\r\n\tprivate void wipe(byte&#x5B;] bytes){\r\n\t\tfor(int i=0; i&amp;lt;bytes.length; i++){\r\n\t\t\t\/\/set to hex AA\r\n\t\t\tint val = 0xAA;\r\n\t\t\tbytes&#x5B;i] = (byte) val; \r\n\t\t\t\/\/set to hex 55\r\n\t\t\tval = 0x55;\r\n\t\t\tbytes&#x5B;i] = (byte) val; \r\n\t\t\t\/\/set to hex 00\r\n\t\t\tval = 0x00;\r\n\t\t\tbytes&#x5B;i] = (byte) val;\r\n\t\t}\r\n\t}\r\n\t\r\n\t\/**\r\n\t * Safely wipes all the data\r\n\t *\/\r\n\tpublic void destroy(){\r\n\t\tif(this.isDestroyed)\r\n\t\t\treturn;\r\n\t\twipe(charRepresentation);\r\n\t\twipe(charRepresentationLower);\r\n\t\twipe(byteRepresentation);\r\n\t\t\r\n\t\t\/\/loose references\r\n\t\tcharRepresentation = null; \r\n\t\tcharRepresentationLower = null; \r\n\t\tbyteRepresentation = null; \r\n\t\t\r\n\t\tthis.length = 0;\r\n\t\t\r\n\t\t\/\/TODO: call garbage collector?\r\n\t\t\/\/Runtime.getRuntime().gc();\r\n\t\t\r\n\t\tthis.isDestroyed = true;\t\t\r\n\t}\r\n\t\r\n\t\/**\r\n\t * This method is only to check if non-sensitive data\r\n\t * is part of the SecureString\r\n\t * @return true if SecureString contains the given String\r\n\t *\/\r\n\tpublic boolean contains(String nonSensitiveData){\r\n\t\tif(this.isDestroyed)\r\n\t\t\treturn false;\r\n\t\tif(nonSensitiveData.length() &amp;gt; this.length)\r\n\t\t\treturn false;\r\n\t\tchar&#x5B;] nonSensitiveDataChars = nonSensitiveData.toCharArray();\r\n\t\tint positionInNonSensitiveData = 0;\r\n\t\tfor(int i=0; i &amp;lt; this.length; i++){\r\n\t\t\tif(positionInNonSensitiveData == nonSensitiveDataChars.length)\r\n\t\t\t\treturn true;\r\n\t\t\tif(this.charRepresentation&#x5B;i] == nonSensitiveDataChars&#x5B;positionInNonSensitiveData])\r\n\t\t\t\tpositionInNonSensitiveData++;\r\n\t\t\telse\r\n\t\t\t\tpositionInNonSensitiveData = 0;\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n}\r\n<\/pre>\n<p>Does that make any sense? How does the Java VM handle this? And how does the Android Dalvik Java VM handle this? Does the Android system cache the UI inputs anyway (so there would be no point in wiping our copies)? Is the data really going to be wiped when I use this class? Where are the real Java internals heroes out there?<\/p>\n<p>Update 1, 11th September 2013: Added Beerware license. Note: This was just some idea I had. Use at your own risk.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Disclaimer: I&#8217;m not a Java internals specialist. Let me know if I got a point wrong here. Wiping sensitive information when you don&#8217;t need it anymore is a very basic security rule. It makes sense to everyone when we talk &hellip; <a href=\"https:\/\/www.floyd.ch\/?p=552\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11,18],"tags":[158,88,83,80,87,85,89,86,84],"class_list":["post-552","post","type-post","status-publish","format-standard","hentry","category-android","category-coding","tag-android","tag-dalvik","tag-immutable-strings","tag-java","tag-java-internals","tag-securestring","tag-sensitive-information","tag-string","tag-wiping"],"_links":{"self":[{"href":"https:\/\/www.floyd.ch\/index.php?rest_route=\/wp\/v2\/posts\/552","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.floyd.ch\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.floyd.ch\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.floyd.ch\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.floyd.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=552"}],"version-history":[{"count":17,"href":"https:\/\/www.floyd.ch\/index.php?rest_route=\/wp\/v2\/posts\/552\/revisions"}],"predecessor-version":[{"id":1252,"href":"https:\/\/www.floyd.ch\/index.php?rest_route=\/wp\/v2\/posts\/552\/revisions\/1252"}],"wp:attachment":[{"href":"https:\/\/www.floyd.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=552"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.floyd.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=552"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.floyd.ch\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=552"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}