9

I am talking about the Java implementation. Is it more secure to use reflection in order to access the internal Field value of type char[] inside a java.lang.String instance and overwrite the containing values like the following example?

As an example why someone could use it: Someone enters a Password and after the Password is used (maybe Authorization) this exact String will be of no use after that, except to read out of the Memory, he could overwrite it and the Password as clear readable String will be out of the memory.

Example Implementation:

SecureRandom random = SecureRandom.getInstanceStrong();
String source = "1234SecureThingy"

Field charField = String.class.getDeclaredField("value");
charField.setAccessible(true);

char[] noise = new char[source.length()];
for (int i = 0; i < source.length(); i++) {
    noise[i] = (char) random.nextInt(Character.MAX_VALUE + 1);
}

charField.set(source, noise);

TL;DR: Is it more secure to Remove the value of a String with Reflection?

4
  • Why use a String instead of a char[] in the first place? Commented Dec 11, 2014 at 6:06
  • many implementation i see used String as Input Value from the user, even Password Fields etc. See docs.oracle.com/javafx/2/api/javafx/scene/control/… and the getText Method, which is, as far as i know, the only possibility to get the password the user typed.
    – Serverfrog
    Commented Dec 11, 2014 at 9:28
  • @user2313067 many Java APIs use (only) String, though a few mostly crypto-specific like Keystore.put do use char[] to be easy to wipe. @Serverfrog (1) you don't overwrite anything, arrays in Java are reference objects almost (not quite) like classes (2) if you do overwrite, randomness and especially cryptorandomness is a complete waste of time (and opportunity for failure) (3) if you do overwrite a String literal or otherwise interned value, like your example, you'll screw up lots of things, but of course a real password should never be literal and should not be interned. ... Commented Dec 11, 2014 at 10:43
  • ... but even if you carefully wipe all the Strings you have a reference to, there'll be lots more of them, and StringBuilders or StringBuffers and perhaps even other forms of the same data, in the middleware, and often un-reused pre-GC copies all lying about in memory, that you can't realistically find and wipe, which makes your effort largely ineffective. Sorry. Commented Dec 11, 2014 at 10:48

2 Answers 2

22
+50

By fiddling with the internal contents of String instances, you incur the risk of severely breaking your application.

The first reason is that String instances are supposed to be immutable, which means that instances may be reused; when you modify "your" string you may actually modify other strings that are conceptually distinct but happen to have the same contents. This kind of reuse can also happen internally, if String instances really refer an underlying char[] with a couple of index to delimit a chunk within that array. See this page for some more details. Generally speaking, code that uses String instances relies on their immutability, and breaking that invariant can lead to far-reaching disagreeable consequences.

A second reason is that the internal contents of String instances are not documented and may change. In fact, they already did so several times. If we consider only the Sun/Oracle JVM (already a bold move, since there are other JVM out there, e.g the one from IBM), then Java 6 versions (from update 21 onwards) can use compressed strings, meaning that the char[] is automatically converted to a byte[] if the characters happen to all be in the 0..255 range (i.e. all characters are really part of Latin-1). The "compressed strings" were designed to get top marks in some benchmarks, but were later on dropped (Java 7 does not have them). However, this is sufficient to show that the internal storage format may change without prior notice. And they did it again in Java 7 update 6.

Thus, using an alternate JVM, or simply updating your JVM to a later version (as is highly recommended when there are security holes to fix), can totally break your code, possibly silently, meaning that you get data corruption instead of a clean exception that merely kills your application. This is undesirable, so don't do it. You cannot reliably muck with how String instances are internally organized. As a side note, accessing private fields is also not a really viable option for Java applets (you cannot do it with an unsigned applet, for instance).

A third reason, and perhaps the most compelling of the three, is that overwriting sensitive values in memory does not (reliably) work in Java. To know why, you have to understand how garbage collection algorithms work (this article is a very nice introduction to the basics). From the programmer's point of view, things are simple: an object is allocated, sits there in RAM, and when the application code ceases to reference it, the GC reclaims the memory. Internally, though, things may differ. In particular, most efficient GC algorithms tend to move objects in memory, i.e. really copy them from place to place. This is invisible to your code, because the GC adjusts references: since Java is strongly typed, you cannot notice that the internal representation of a pointer changed (you cannot cast a reference to an integer, for instance). This kind of copying allows for faster GC operation and better locality (with regards to caches). However, it implies that several copies of your precious data may survive elsewhere in RAM, completely out of your reach. Even if you could reliably overwrite your String contents, this would only impact the current storage area for that instance, leaving ghost copies of it untouched.

(In the Sun/Oracle JVM, GC that internally copy objects appeared around Java 1.3. This can be seen in their design for library code; old code used char[] for passwords, so as to prevent automatic reuse as may happen with String, and promote manual overwriting; newer code uses String because the library designers understood that this overwriting would not be reliable anyway.)


Does this mean that Java is inherently insecure ? No, because the importance of overwriting sensitive data in memory is greatly exaggerated. The idea that Thou Shallt Overwrite Passwords And Keys is one of these inherited dogmas: something that was relevant in a specific case long ago, but is now applied and enforced by lots of people who receive it as Divine Wisdom and fail to understand what it is really about. Overwriting memory is a nice thing to do for application code that runs on compromised systems, when attackers are not very competent: the scenario is an average home owner with a PC full of malware. The malware has full control of the machine, but, being a simple automated piece of code, it does not really exploit that control; the malware simply scans the RAM for sequences of characters that look like, say, credit card information. So we are talking of doomed client systems that manage to survive only because the attackers prefer it that way, and the data scavenging may be (potentially) mitigated with prompt overwriting of sensitive data only because the human attackers that control the malware simply don't have time to do a proper job of extracting the interesting bits, and instead have to rely on the dumbest of brutal full-memory scans.

None of this applies to a server application, or to client code that handles secrets with an actual non-negligible value. If a malicious attacker is in position to scan the RAM for sensitive data, and that data is worth 1 or 2 minutes of explicit attention from the human attacker, then no amount of overwriting will save you. Thus, in many contexts where security matters, overwriting of passwords and keys is just wasted effort, that gives a feeling of security, but does not actually improves things (though it may be convenient to awe auditors).

Compounding the issue is the fact that when your sensitive data appears in your Java code, it has already gone through various layers that are out of your reach. For instance, if you read the password from a file, then copies of it are retained in RAM used as cache by the kernel, and possibly one or two bounce buffers maintained by Java as intermediaries between the native world and the abstraction that Java offers. If the password was received from network over SSL, then the password again went through the internal buffering of the SSL library, that you cannot control. If we are talking about a client application and the password was merely typed by the user, then any malware that can scan memory also runs a keylogger and got the password even before it reached your code.

Therefore, as a summary: no, using reflection to overwrite your password in memory does NOT really improve security. It makes your code much more liable to break (even upon a simple minor update of the JVM), but offers no actual tangible gain in security. So don't do it.


Note: we talked about Java here, but all of the above equally applies to most other programming languages and frameworks, including .NET (C#), PHP, Ruby, Node.js, Python, Go... If you really want to keep track of sensitive data, then you have to use a language that is close enough to the bare metal (assembly, C, Forth) and follow it throughout the system, including base libraries, the kernel, and the device drivers. If you simply concentrate on the application code, then you are guaranteed to miss the point.

1
  • 1
    Adding keywords to find this easier with search engines: Heap Inspection Commented Apr 4, 2019 at 13:16
1

IMHO the usage of SealedObject is the correct way to store secret information inside JVM (Java Virtual Machine) memory.

This uses the standard security and crypto mechanism the JRE already supports.

Please look at my answer at https://stackoverflow.com/a/58933366/2590615 - I did not want to copy the content. There is also a link to a working example.

1
  • I didn't know about the SealedObject! Thanks! But what is interresting is, how they are managing the in-memory representation of the Object that goes inside the SealedObject.
    – Serverfrog
    Commented Nov 21, 2019 at 10:03

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .