Can a secret be hidden in a 'safe' java cl

2020-02-05 04:29发布

This is a brainstorming question about what's possible in Java (or not). I want to know if it is possible to hide a secret within a class and prevent anymore from accessing it using Java code or any of its feature only (security, reflexion, serialization, class loaders, you-name-it...).

Here is what I have in mind so far:

public final class Safe {

    private String secret;
    private HashMap<String, Credentials> validCertificates
            = new HashMap<String, Credentials>();

    public Safe(String aSecret) {
        this.secret = aSecret;
    }

    public final class Credentials {
        private String user;
        private Credentials(String user) {
            this.user = user;
        }
    }

    public final Credentials getCredential(String user) {
        // Following test is just for illustrating the intention...
        if ( "accepted".equals(user) ) {
            return new Credentials(user);
        } else {
            return null;
        }
    }

    public String gimmeTheSecret(Credentials cred) {
        if ( this.validCertificates.get(cred.user) == cred ) {
            return secret;
        } else {
            return null;
        }
    }

    private void writeObject(ObjectOutputStream stream) throws IOException {
        throw new RuntimeException("No no no no no no no!!!");
    }

}

Can it be improved? Should it be improved? Is the idea of locking a secret in a safe class impossible to achieve?

EDIT

Relevance:

Some people question the relevance of the issue I am raising here. Although I am asking a general question in order to trigger an open conversation, there is a very concrete application to this class:

  • If I want to decrypt some messages, I need to load a private key data into a class. If I can't prevent other Java code from accessing it, then it is impossible to create a secure system. Of course, if I want to decrypt a message, I should rather do it in the class than giving away the secret, but still, the safe has to remain unbreakable.

Clarification:

  • Instances of the class are only created at runtime, not at compile time
  • Code can run in web server applications or any desktop or device applications
  • The class is only used to store a secret at runtime, in memory, no plans to persist it (for persistence, one can/should use classic encryption techniques)

Facts:

  • To implement security in a Java application, one should set a SecurityManager instance where checking methods are overridden as needed
  • This application can load untrusted code with secure class loaders and assign a protection domain for the classes it loads. This domain should not include a RuntimePermission("setSecurityManager").
  • Untrusted code can try to change the SecurityManager, but since the Secure Class Loader did not grant the setSecurityManager permission, a SecurityException will be thrown.

Solved issues:

Regarding the execution environment, we need to distinguish two cases:

  • Controlled environment: We get to start the application that will use untrusted code trying to break our 'safe'.

If we set a proper SecurityManager disabling reflection and restricting permissions on any loaded untrusted code, then our secret is safe.

  • Uncontrolled environment: Hacker gets to start the application which uses untrusted code trying to break our 'safe'.

The hacker can create his own application with its own security manager and Secure Class loader. It could load our code from the classpath and execute it as if it were our own application. In this case, he could break the safe.

12条回答
Rolldiameter
2楼-- · 2020-02-05 05:12

Adding a comment for anyone else who stumbles across this old thread....

Sometimes all you need is a 'signed' value, not an 'encrypted' value. E.g., (encoded) license tokens need to be signed but they shouldn't need to be encrypted. In these cases you can distribute the PUBLIC key and it's not an issue that anyone can see it. You're still protected since nobody else can create the signed token.

You can also use the public key to send encrypted messages to the server.

Of course this won't stop a knowledgeable and determined attacker - see the other responses. In this case someone could simply replace your public key with their own, esp. if you don't validate the cert chain. But if you can use signatures instead of encryption it will save you major headaches.

查看更多
时光不老,我们不散
3楼-- · 2020-02-05 05:14

No, it's not safe from other Java code. Your secret could be retrieved from an instance of Safe like this:

Field field = safe.getClass().getDeclaredField("secret");
field.setAccessible(true);
String secret = (String) field.get(safe);

Update: If you control the loading of the other Java code that you want to hide the secret from you can probably use a custom SecurityManager or ClassLoader to prevent access to it. You need to control the environment that this runs in to work though, e.g. a server you restrict access to.

Your edited question however mentions that the code can run on any desktop or device. In that case there's really nothing you can do to protect the secret from other processes that could do just about anything. Even if you encrypt it in memory another process can just intercept the key or even the plaintext secret as its passed around.

If you don't control the environment that you need something to be secure in then you likely need to consider a different approach. Perhaps you can avoid storing the secret in memory altogether?

查看更多
冷血范
4楼-- · 2020-02-05 05:19

You can obfuscate it from the .class file by using proguard or similar tools. You may also sign your JAR so other packages can't access it. Signing your JAR may help too.

查看更多
爱情/是我丢掉的垃圾
5楼-- · 2020-02-05 05:20

I think you can do this, but you end up pushing the security problem somewhere else. Is there any reason that "secret" cannot be encrypted using (for simplicity) your favourite symmetric key algorithm? The gimmeTheSecret() method would have to take an additional parameter being the secret key use to decrypt the secret.

Of course then the problem becomes that this secret key needs to be known and entered by a user or a machine storing it somewhere securely. You could use some kind of Hardware Security Module depending on how sensitive the data is and how much you want to spend!

查看更多
家丑人穷心不美
6楼-- · 2020-02-05 05:20

Assuming information passed to method calls are safe, a key is a good solution. The key doesn't need to be stored anywhere in the app, and because of this, the information can't be accessed through Java only. It gets interesting if you want a way to share the secret with others without giving them your key, which is what the shareSecret method is for below. However, it becomes tricky managing this. One process could be:

1) The secret seeker requests access, entering a temp key that is stored

2) The secret keeper grants access with their key, the temp key is deleted, and a temp Safe object is created that works for the temp key.

3) The secret seeker enters the temp key and a permanent key, the temp Safe object is deleted, and a new permanent Safe object is created that can be accessed with the permanent key.

Again, assuming parameters passed to method calls are safe, the main problem with the above procedure is that someone could have hijacked the temp key between 1 and 2 and use it to view the temp secret between steps 2 and 3. However, it would make it tougher to crack than storing it in a plain-text string.

public final class Safe {
    private String secret;

    public Safe(String secret, String key){
        this.secret = encode(secret, key}

    public String getSecret(String key){
        return decode(this.secret, credentials);
    }

    public Safe shareSecret(String fromKey, String toKey){
        return new Safe(decode(this.secret, fromKey), toKey); 
    }

    private String encode(String secret, String key){
       //Code to encode the secret based on key here...
    }

    private String decode(String secret, String key){
       //Code to decode the secret based on key here...
    }
}
查看更多
孤傲高冷的网名
7楼-- · 2020-02-05 05:21

http://code.google.com/p/joe-e/ is an object-capability subset of java which is meant to allow decomposable security -- the ability for one part of a program to preserve its security properties even when other parts of the program are controlled, compromised, or manipulated by an attacker.

That said, valid JVMs are allowed to extend the semantics of the language with additional language facilities, such as the ability to attach a debugger at runtime. Code that can use Runtime to invoke shell access could attach a debugger to many stock JVMs and work around private access limitations even if the JVM is set up so that normal reflection respects field privateness.

Joe-E disallows a lot of the reflective abuses of information hiding that could complicate this, and of course disallows unfiltered access to Runtime.

查看更多
登录 后发表回答