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

【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 = http://www.mamicode.com/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 ,未经本人同意请勿用于商业用途,谢谢——