【Java编码准则】の #00限制敏感数据的生命周期

当竞争对手的应用程序与我们的应用程序运行在同一个系统上时,我们的应用程序在内存中的敏感数据是很容易被竞争对手获取的。如果我们的应用程序符合下面几种情况之一,那么竞争对手可以获取到我们应用的敏感数据:

1)应用程序使用对象来存储敏感数据,而且在对象使用完后,对象的内容没有被清除或者对象没有被垃圾回收;

2)在操作系统运行内存管理任务或者执行休眠等功能时,应用程序的内存分页将被置换到磁盘上保存;

3)持有存储了操作系统缓存或者内存中敏感数据的buffer对象(例如BufferedReader);

4)基于反射的控制流,使得竞争对手可以使用规避措施逃过敏感变量生命周期的限制;

5)在调试信息/日志文件/环境变量或者线程和core dumps等方面泄漏了敏感数据。

包含敏感数据的内存在数据使用后没有及时清空,很容易导致敏感数据的泄漏。为了限制这种风险,应用程序必须尽量减小敏感数据的生命周期。

完美的防止内存数据的泄漏要求底层操作系统和Java虚拟机的支持。例如如果将内存数据置换到磁盘上是存在安全问题的,那么一个安全的操作系统就需要禁用数据的置换和系统的休眠。

[不符合安全要求的代码示例]

下面代码从控制台读取用户的用户名和密码并将密码保存在一个String对象中,这导致在垃圾回收器回收String所占用的内存之前,密码处于泄漏状态。

import java.io.Console;
import java.io.IOException;

public class Password {

	public static void main(String[] args) throws IOException {
		Console console = System.console();
		if (console == null) {
			System.out.println("No Console.");
			System.exit(1);
		}

		String username = console.readLine("Enter your user name:");
		String password = console.readLine("Enter your password:");
		if (!verify(username, password)) {
			throw new SecurityException("Invalid Credentials");
		}

	}

	// dummy verify method, always returns true
	private static final boolean verify(String username, String password) {
		return true;
	}

}

[符合安全要求的解决方案]

这个解决方案使用Console.readPassword()函数从控制台中获取密码

import java.io.Console;
import java.io.IOException;
import java.util.Arrays;

public class Password {

	public static void main(String[] args) throws IOException {
		Console console = System.console();
		if (console == null) {
			System.out.println("No Console.");
			System.exit(1);
		}

		String username = console.readLine("Enter your user name:");
		char[] password = console.readPassword("Enter your password:");
		if (!verify(username, password)) {
			throw new SecurityException("Invalid Credentials");
		}

		// Clear the password
		Arrays.fill(password, ' ');

	}

	// dummy verify method, always returns true
	private static final boolean verify(String username, char[] password) {
		return true;
	}

}

函数Console.readPassword()使得返回的数据是字节数组类型,而不是String对象。因此,开发者可以在数组数据使用完了之后清空数据,这个函数同时禁止了密码在控制台上面的显示。

[不符合安全要求的代码示例]

下面的代码使用BufferedReader对象包装InputStreamReader对象,这导致敏感数据可以直接在文件中读取到。

	void readData() throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("file")));

		// read from file
		String data = br.readLine();
	}

BufferdReader.readLine()函数将敏感数据以String对象的形式返回,这个对象在敏感数据用完之后还会存活很长的时间。而BufferedReader.read(char[], int, int)函数可以将敏感数据读取到一个char数组中,不过这需要开发者在使用完敏感数据后手动清除。类似的,如果使用BufferedReader来包装FileReader对象,也会存在同样的安全问题。

[符合安全要求的解决方案]

下面的代码使用直接分配的NIO buffer来从文件中读取敏感数据,在数据使用完后,可以立即清除它,而且敏感数据不会缓存在多个位置,仅存在于系统内存中。

	void readData() {
		ByteBuffer bb = ByteBuffer.allocateDirect(16*1024);
		try (FileChannel rdr = (new FileInputStream("file")).getChannel()) {
			while(rdr.read(bb) > 0) {
				// do something with the buffer
				bb.clear();
			}
		} catch(Throwable e) {
			// Handle error
		}
	}

需要注意的是,必须手动清除buffer里面的数据,因为这部分数据垃圾回收器是不会进行回收的。

——欢迎转载,请注明出处 http://blog.csdn.net/asce1885 ,未经本人同意请勿用于商业用途,谢谢——

【Java编码准则】の #00限制敏感数据的生命周期

时间: 2025-01-14 11:58:26

【Java编码准则】の #00限制敏感数据的生命周期的相关文章

【Java编码准则】の #13使用散列函数保存密码

明文保存密码的程序在很多方面容易造成密码的泄漏.虽然用户输入的密码一般时明文形式,但是应用程序必须保证密码不是以明文形式存储的. 限制密码泄漏危险的一个有效的方法是使用散列函数,它使得程序中可以间接的对用户输入的密码和原来的密码进行比较,而不需要保存明文或者对密码进行解密后比较.这个方法使密码泄漏的风险降到最低,同时没有引入其他缺点. [加密散列函数] 散列函数产生的值称为哈希值或者消息散列,散列函数是计算可行函数,但反过来是计算不可行的.事实上,密码可以被编码为一个哈希值,但哈希值不能被解码成

【Java编码准则】の #13使用散列函数保存password

明文保存password的程序在非常多方面easy造成password的泄漏.尽管用户输入的password一般时明文形式.可是应用程序必须保证password不是以明文形式存储的. 限制password泄漏危急的一个有效的方法是使用散列函数.它使得程序中能够间接的对用户输入的password和原来的password进行比較,而不须要保存明文或者对password进行解密后比較.这种方法使password泄漏的风险降到最低,同一时候没有引入其它缺点. [加密散列函数] 散列函数产生的值称为哈希值

【Java编码准则】の #01限制内存中敏感数据的生命周期

当竞争对手的应用程序与我们的应用程序执行在同一个系统上时,我们的应用程序在内存中的敏感数据是非常easy被竞争对手获取的.假设我们的应用程序符合以下几种情况之中的一个,那么竞争对手能够获取到我们应用的敏感数据: 1)应用程序使用对象来存储敏感数据,并且在对象使用完后.对象的内容没有被清除或者对象没有被垃圾回收: 2)在操作系统执行内存管理任务或者执行休眠等功能时.应用程序的内存分页将被置换到磁盘上保存: 3)持有存储了操作系统缓存或者内存中敏感数据的buffer对象(比如BufferedRead

【Java编码准则】の #02不要在客户端存储未加密的敏感信息

当构建CS模式的应用程序时,在客户端侧存储敏感信息(例如用户私要信息)可能导致非授权的信息泄漏. 对于Web应用程序来说,最常见的泄漏问题是在客户端使用cookies存放服务器端获取的敏感信息.Cookies是由web服务器创建的,它具有一个指定的有效时间,保存在客户端.当客户端连接上服务器端时,客户端使用cookies中存储的信息向服务器端进行认证,通过后服务器端返回敏感信息. 在XSS攻击下,Cookies不能保证敏感信息的安全.无论是通过XSS攻击,还是直接对客户端的攻击,攻击者一旦获取到

【Java编码准则】の #11不要使用Object.equals()来比较密钥值

java.lang.Object.equals()函数默认情况下是不能用来比较组合对象的,例如密钥值.很多Key类没有覆写equals()函数,因此,组合对象的比较必须单独比较里面的各个类型以保证正确性. [不符合安全要求的代码示例] 下面的代码使用equals()函数比较两个key值,key值即使具有相同的取值也可能会返回不相等,导致结果出错. private static boolean keysEqual(Key key1, Key key2) { if (key1.equals(key2

【Java编码准则】の #11不要使用Object.equals()来比較密钥值

java.lang.Object.equals()函数默认情况下是不能用来比較组合对象的,比如密钥值.非常多Key类没有覆写equals()函数,因此,组合对象的比較必须单独比較里面的各个类型以保证正确性. [不符合安全要求的代码演示样例] 以下的代码使用equals()函数比較两个key值,key值即使具有同样的取值也可能会返回不相等,导致结果出错. private static boolean keysEqual(Key key1, Key key2) { if (key1.equals(k

Java 多线程(三) 线程的生命周期及优先级

Java 多线程(三) 线程的生命周期及优先级 线程的生命周期 线程的生命周期:一个线程从创建到消亡的过程. 如下图,表示线程生命周期中的各个状态: 线程的生命周期可以分为四个状态: 1.创建状态: 当用new操作符创建一个新的线程对象时,该线程处于创建状态. 处于创建状态的线程只是一个空的线程对象,系统不为它分配资源. 2.可运行状态: 执行线程的start()方法将为线程分配必须的系统资源,安排其运行,并调用线程体——run()方法,这样就使得该线程处于可运行状态(Runnable). 这一

深入java虚拟机(二)——类的生命周期(上)类的加载和连接

类加载器,顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中.一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件).类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例.每个这样的实例用来表示一个 Java 类.通过此实例的 newInstance()方法就可以创建出该类的一个对象.实际的情况可能

深入java虚拟机(三)——类的生命周期(下)类的初始化

上接深入java虚拟机——深入java虚拟机(二)——类加载器详解(上),在上一篇文章中,我们讲解了类的生命周期的加载和连接,这一篇我们接着上面往下看. 类的初始化:在类的生命周期执行完加载和连接之后就开始了类的初始化.在类的初始化阶段,java虚拟机执行类的初始化语句,为类的静态变量赋值,在程序中,类的初始化有两种途径:(1)在变量的声明处赋值.(2)在静态代码块处赋值,比如下面的代码,a就是第一种初始化,b就是第二种初始化 [html] view plaincopyprint? public