机密可以隐藏在提供访问凭据的“安全” Java类中吗?

| 这是关于(或没有)Java可能实现的一个集思广益的问题。我想知道是否有可能在一个类中隐藏一个秘密,并阻止它不再使用Java代码或仅使用其任何功能(安全性,自反性,序列化,类加载器,you-name-it ...)来访问它。 到目前为止,我的想法是:
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!!!\");
    }

}
可以改善吗?应该改进吗?将秘密锁定在安全类别中的想法是不可能实现的吗? 编辑 关联: 有人质疑我在这里提出的问题的相关性。尽管我要提出一个一般性问题以触发公开对话,但该类有一个非常具体的应用程序: 如果要解密某些消息,则需要将私钥数据加载到类中。如果我不能阻止其他Java代码访问它,那么就不可能创建一个安全的系统。当然,如果我想解密一条消息,我宁愿在类中完成它,也不要泄露秘密,但是,保险箱必须保持牢不可破。 澄清: 该类的实例仅在运行时创建,而不是在编译时创建 代码可以在Web服务器应用程序或任何桌面或设备应用程序中运行 该类仅用于在运行时将秘密存储在内存中,没有计划对其进行持久化(出于持久性考虑,可以/应该使用经典的加密技术) 事实: 为了在Java应用程序中实现安全性,应设置一个SecurityManager实例,在该实例中可以根据需要覆盖检查方法 该应用程序可以使用安全的类加载器加载不受信任的代码,并为其加载的类分配保护域。该域不应包含RuntimePermission(\“ setSecurityManager \”)。 不受信任的代码可以尝试更改SecurityManager,但是由于安全类加载程序未授予setSecurityManager权限,因此将引发SecurityException。 解决的问题: 关于执行环境,我们需要区分两种情况: 受控环境:我们开始启动应用程序,该应用程序将使用不受信任的代码来试图破坏我们的“安全”。 如果我们设置适当的SecurityManager来禁用反射并限制对任何已加载的不受信任代码的权限,则我们的机密是安全的。 不受控制的环境:黑客开始使用不可信的代码尝试破坏我们的“安全”的应用程序。 黑客可以使用自己的安全管理器和Secure Class loader创建自己的应用程序。它可以从类路径中加载我们的代码,并像执行我们自己的应用程序一样执行它。在这种情况下,他可能会破坏保险柜。 如一个单独的问题中所述,sun.misc.Unsafe无法破坏安全管理器     
已邀请:
不,它与其他Java代码是不安全的。可以从
Safe
的实例中检索您的秘密,如下所示:
Field field = safe.getClass().getDeclaredField(\"secret\");
field.setAccessible(true);
String secret = (String) field.get(safe);
更新:如果您控制要隐藏秘密的其他Java代码的加载,则可以使用自定义
SecurityManager
ClassLoader
以防止对其进行访问。您需要控制运行的环境,例如您限制访问的服务器。 但是,您编辑的问题提到该代码可以在任何台式机或设备上运行。在那种情况下,您实际上无能为力,无法保护机密不受可能做任何事情的其他进程的侵害。即使您在内存中对其进行加密,另一个过程也可以截获该密钥,甚至截获该密钥时还会截获纯文本秘密。 如果您无法控制环境,则需要安全的东西,那么您可能需要考虑采用其他方法。也许您可以避免将秘密完全存储在内存中?     
这种“安全性”是可笑的。 它在哪里运行?在我的桌面上?我使用调试器连接到JVM,并以明文形式查看所有机密。 或者,我将代码放在其旁边,并使用反射来转储内容。 或者,我通过BCEL注入自己的代码修改,然后修改Safe的构造函数以将“ secret”值转储到文件中。 或者我只是简单地将整个程序包放入具有相同名称的我的程序包,方法是将其放入引导程序类加载器中。 或者,我什至可以修改和编译Java源代码以获得修改后的JVM。 或者...我的人可以列出从运行时实例中提取值的数十种方法! 任何安全设计中的真正问题是:谁是攻击者?什么是威胁模型?没有回答这个话题是没有意义的。     
您可以设置“很难”的秘密访问权限,但不能使其成为不可能。有一句话(我相信Bruce Schneier):针对随便的用户,任何东西都行得通。对坚定的饼干没有任何作用。     
http://code.google.com/p/joe-e/是Java的对象功能子集,旨在允许可分解的安全性-程序的一部分即使在其他部分时也保留其安全性的能力该程序的代码是由攻击者控制,破坏或操纵的。 就是说,允许有效的JVM使用其他语言工具来扩展语言的语义,例如在运行时附加调试器的能力。可以使用“ 5”调用外壳程序访问的代码可以将调试器附加到许多现有的JVM上,并且可以解决“ 6”的访问限制,即使JVM已设置为使正常反射遵守字段“ 6”的情况。 Joe-E禁止对信息隐藏进行大量反思性滥用,这可能会使情况复杂化,当然也不允许未经过滤地访问
Runtime
。     
我认为您可以做到,但是最终将安全问题推到了其他地方。是否有任何理由无法使用(为简单起见)您喜欢的对称密钥算法对“秘密”进行加密? gimmeTheSecret()方法将必须带有一个附加参数,即用于解密机密的密钥。 当然,问题就变成了该秘密密钥需要由用户或安全地将其存储在某处的机器来知道和输入。您可以使用某种硬件安全模块,具体取决于数据的敏感程度和要花费的数量!     
  有人质疑相关性   我在这里提出的问题。虽然   我在问一个普遍的问题   为了触发公开对话,   有一个非常具体的应用   到这个班级:      如果我想解密一些消息,我   需要将私钥数据加载到   类。如果我无法阻止其他Java   代码访问它,那么它是   无法创建安全的系统。   当然,如果我想解密   讯息,我宁愿在   上课比泄露秘密,但是   仍然,必须保留保险柜   牢不可破。 首先,在安全性上坚不可摧。仅通过在键盘上随机写入内容,我几乎不可能随机找到您的加密密钥。如果您的钥匙很复杂,将使您成为一个倒霉的人,但这是可能的。 第二点,使用公共/私有密钥值对保护客户端和服务器之间的通信确实很普遍,并且如果操作正确,它可以完美地工作。但是,必须了解这确实保护了两台计算机之间的通信。它不能保护计算机本身。整个事情基于计算机彼此之间的完全信任。 第三点,当您对计算机具有物理访问权限时,您可以使用计算机进行任何所需的操作,尤其是其中包括监视程序执行的所有操作。以及计算机的所有内容。可以对内容进行加密,但是在使用时不再加密。这是公钥/私钥系统的主要问题:私钥存储在某个地方,因此您必须确保该位置安全。 如果您信任通信中涉及的计算机,则此流程对于您而言是完全可以接受的。例如,当您连接到银行帐户时就是这种情况。 银行计算机受银行信任,并且提供的对外部单词的访问实际上受到限制和控制。它们是“安全的”。 如果您放弃自己的私钥或访问银行凭证。您会受到损害,但这是您的责任和问题。因为不必折衷就可以妥协,所以您会尽力避免这种情况。很好,因为您是完全掌控计算机的人。 但是,可以说您是通过公用计算机或其他人的计算机访问银行的。然后,简单的键盘记录程序就可以记录您输入密码时所进行的按键和鼠标移动。 客户端的安全性基于您对客户端的信任。如果您可以信任他,那么这是完美的,它会起作用。如果不能,那么它就坏了。     
如果您需要运行不受信任的代码,最好的方法是在单独的JVM中运行它。这样,不信任的代码可以受到最严格的限制,例如,如果您的CPU失控或JVM崩溃,甚至可以将其杀死。该代码只能进行的访问是通过您为它提供的一种使用反射实现的,无论您做什么,都不会使您访问另一个JVM中的类。 您甚至可以为您的应用程序构建监狱或迷宫。这可以允许不受信任的代码在看似真实的系统中运行,但实际上并非如此。 (这样做的目的是将您捆绑在一起,使黑客足够长的时间来查看他们在做什么) 您甚至可以在自己的虚拟机中运行JVM,这样看来您已经具有完整的(但是虚拟的)系统来“入侵”。可以保存虚拟机进行分析,并非常轻松地将其擦除到预设状态。 最终的解决方案是将不受信任的代码放在自己的虚拟LAN上。 (他们确实分析了stuxnet蠕虫。)     
不,您不能以这种方式在课程中嵌入秘密。如果您对它进行了编译,那么我可以在生成的类文件上运行
strings
,它可能就在其中。 如果您仅尝试验证它,则可以执行此操作的一种方法是在其中存储哈希。一旦要验证输入,就可以对其进行哈希处理并比较哈希值。     
您可以使用proguard或类似工具从.class文件中对其进行混淆。您也可以在JAR上签名,以便其他软件包无法访问它。签署您的JAR也可能会有所帮助。     
假设传递给方法调用的信息是安全的,则密钥是一个很好的解决方案。密钥不需要存储在应用程序中的任何位置,因此,信息不能仅通过Java访问。如果您想要一种无需他人提供密钥即可与他人共享秘密的方法,这将变得很有趣,这就是下面的shareSecret方法的目的。但是,管理它变得棘手。一种过程可能是: 1)秘密搜寻者请求访问,输入存储的临时密钥 2)秘密管理员通过其密钥授予访问权限,删除临时密钥,并创建一个对临时密钥起作用的临时安全对象。 3)秘密搜寻者输入临时密钥和永久密钥,删除临时安全对象,并创建可以使用该永久密钥访问的新的永久安全对象。 同样,假设传递给方法调用的参数是安全的,则上述过程的主要问题是有人可能劫持了1和2之间的临时密钥,并使用它查看步骤2和3之间的临时密钥。它比将其存储在纯文本字符串中更难破解。
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...
    }
}
    
这个问题还有另一个角度:Java提供的权限。在控制环境的环境中,可以为使用安全类加载器加载的类分配一组权限。 Java提供了3个与安全性实现相关的权限对象:   PrivateCredentialPermission SecurityPermission AuthPermission 当实施密码系统时,这些可以帮助改善和控制对功能的访问。它们必须足以确保系统安全。     
为其他偶然发现此旧线程的人添加评论。 有时,您需要的只是一个\'signed \'值,而不是\'encrypted \'值。例如,(已编码的)许可证令牌需要签名,但不需要加密。在这些情况下,您可以分发PUBLIC密钥,这不是任何人都可以看到的问题。您仍然受到保护,因为没有其他人可以创建签名的令牌。 您还可以使用公共密钥将加密的消息发送到服务器。 当然,这不会阻止知识渊博且坚定的攻击者-请参阅其他响应。在这种情况下,有人可以简单地用自己的密钥替换您的公钥,尤其是。如果您不验证证书链。但是,如果您可以使用签名而不是加密,则可以省去很多麻烦。     

要回复问题请先登录注册