【shiro】UsernamePasswordToken中char[]替代String的安全性

        shiro中UsernamePasswordToken类的源码中有一段注释很有意思。

* <p>Note that this class stores a password as a char[] instead of a String
 * (which may seem more logical).  This is because Strings are immutable and their
 * internal value cannot be overwritten - meaning even a nulled String instance might be accessible in memory at a later
 * time (e.g. memory dump).  This is not good for sensitive information such as passwords. For more information, see the
 * <a href="http://java.sun.com/j2se/1.5.0/docs/guide/security/jce/JCERefGuide.html#PBEEx">
 * Java Cryptography Extension Reference Guide</a>.</p>
        这个类的password字段用一个字符数组来存储,而不是一个似乎更符合逻辑的字符串。原因是java中字符串是不可变的,并且它里面的value(java.lang.String中的value数组)不能被归零化,这意味着内存中即便是一个为null的字符串实例在稍后一段时间中都是可见的(譬如dump内存),所以对于诸如password这种敏感信息而言不安全。

        这句话不太好看,但这句话透露了两个方向,一个是为什么要用char[]来代替String,另一个则是如果用String带来的问题是什么。


一、为什么用char[]代替String

        对于password而言,之所以用char[]代替String,源码中的clear()方法足以证明,因为char[]的password可以被归零化,当作完必要的逻辑之后,password就没有存在的必要,归零化后即便是dump当前的JVM内存,password也不会暴露。

 /**
     * Clears out (nulls) the username, password, rememberMe, and inetAddress.  The password bytes are explicitly set to
     * <tt>0x00</tt> before nulling to eliminate the possibility of memory access at a later time.
     */
    public void clear() {
        if (this.password != null) {
            for (int i = 0; i < password.length; i++) {
                this.password[i] = 0x00;
            }
            this.password = null;
        }

    }
        上面的代码逻辑,如果换成String改写,逻辑是这样的。
public void clear(){
		//password is string
		if(password != null)
			password = null;
	}	
        这两者有什么区别呢?password[i]=0x00的时候,操作的是同一片内存,可以这么粗略的理解,如果password[i]=0x01,那么归零后password[i]=0x00,相当于password的字符数组中的数据被抹除了,dump内存下来只能看到归零后的值;而对于String password="xx"或者String password=new String("xx");而言,password=null,此时这个"xx"是否会像password[i]一样归零化呢?答案是不会。为什么不会,这就是使用String带来的问题。

        还有一个点,String类中的value是一个final的char[],毫无疑问的是final修饰的char[],一样可以将char[i]归零化,但是纵观String类的源码,除非反射,否则不能修改这个value。

        因此使用String带来的问题是password=null之后,原始的密码还留在内存中,那么原始密码存在哪里?当执行String password=“xx"或String password=new String("xx")的时候发生了什么?


二、String带来的问题是什么

        String类在字节码中有一个私有化的字节码常量池String pool,是存储String对象的定长HashTable(参见: 点击打开链接)。String password="xx",这句代码在编译的时候会创建一个String对象并存放在字符串常量池中;String password=new String("xx"),这句代码则会先在常量池中查找,看是否能找到"xx".equals(池对象)(String类是重写了equals方法的,包含了比较String对象中的value[i]是否相等的逻辑),如果找到则传入String类的构造方法,如果找不到则会创建一个String对象然后再进入String构造方法。这也就意味着,无论是哪一种方式,都会在常量池中存在一个String对象"xx",因此无论最终password=null或任何其他值,对于"xx"对象而言,依旧还在内存中的,因此对于password这种安全敏感的数据来说,dump内存是可以看到的。

        进一步的验证信息。

1. String password="xx"

package cn.wxy.str;

public class StringDemo {
	public static void main(String[] args) {
		String password = "xx";
	}
}

        会首先在字节码常量池中找一个叫做xx的String对象,然后赋值给password,至于这个xx的String对象,结合java.lang.String的源码不难想象。

2. String password=new String("xx")

        在看字节码之前,先回顾一下java.lang.String的构造方法。

public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }
        先不着急讨论这个构造方法,结合字节码来看。

package cn.wxy.str;

public class StringDemo {
	public static void main(String[] args) {
		String password = new String("xx");
	}
}

        会先分配内存给password,然后从常量池中查找xx的String对象,赋值然后调用构造方法。

        也就是说,String password="xx"会在String的字节码常量池中创建一个对象,然后password来引用;而String password=new String("xx");会创建两个对象,先在字节码常量池中创建一个对象,然后将该对象的value和hash赋值给password,此时的password在堆内存中。如果"xx"是输入的密码,那么此时dump内存就会暴露。


三、隐式的String.intern()

 /**
     * Return
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值