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

明文保存password的程序在非常多方面easy造成password的泄漏。尽管用户输入的password一般时明文形式。可是应用程序必须保证password不是以明文形式存储的。

限制password泄漏危急的一个有效的方法是使用散列函数。它使得程序中能够间接的对用户输入的password和原来的password进行比較,而不须要保存明文或者对password进行解密后比較。这种方法使password泄漏的风险降到最低,同一时候没有引入其它缺点。

[加密散列函数]

散列函数产生的值称为哈希值或者消息散列,散列函数是计算可行函数,但反过来是计算不可行的。其实,password能够被编码为一个哈希值,但哈希值不能被解码成相应的password。两个password是否同样,能够通过推断它们的哈希值是否相等。

一个好的实践是对明文加盐之后再进行哈希值的计算。

盐是一个唯一的序列或者随机生成的数据片段。通常和哈希值一起存放。盐的作用是防止哈希值的暴力破解,前提是盐足够长以产生足够的熵(长度较短的盐值不足以减少暴力攻击的速度)。

每一个password必须有自己的唯一的盐,假设多个password共用同一个盐的话。两个用户可能会有同样的password。

散列函数和盐的长度的选择是安全和性能的一个均衡。

选择强度高的散列函数以增强暴力破解的难度的同一时候,也会添加password验证的时间。增强盐的长度能够增大暴力破解的难度,但同一时候会添加额外的存储空间。

Java的MessageDigest类提供了多种加密散列函数的实现。但要避免使用有缺陷的散列函数,比如MD5。而像SHA-1和SHA-2之类由美国国家安全局维护的散列函数,眼下是安全的。实践中。非常多应用使用SHA-256散列函数。由于这个函数兼顾了性能和安全。

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

以下的代码使用对称加密算法对存储在password.bin文件里的密码进行加密和解密操作。

public final class Password {

	private void setPassword(byte[] pass) throws Exception {
		// arbitrary encryption scheme
		byte[] encryted = encrypt(pass);
		clearArray(pass);

		// encrypted password to password.bin
		saveBytes(encrypted, "password.bin");
		clearArray(encrypted);
	}

	boolean checkPassword(byte[] pass) throws Exception {
		// load the encrypted password
		byte[] encrypted = loadBytes("password.bin");
		byte[] decrpyted = decrypt(encrypted);
		boolean arraysEqual = Arrays.equals(decrypted, pass);
		clearArray(decrypted);
		clearArray(pass);
		return arraysEqual;
	}

	private void clearArray(byte[] a) {
		for (int i=0; i<a.length; ++i) {
			a[i] = 0;
		}
	}
}

攻击者可能对上述bin文件进行解密。然后获得password,尤其当攻击者知道程序中使用的密钥和加密方式时。

password甚至必须不让系统管理员或者特权用户知道。

因此,使用加密方式对防止password泄漏危急的作用有限。

[不符合安全的代码演示样例]

以下代码基于MessageDigest类使用SHA-256散列函数来对照字符串。而不是使用明文。但它使用的是String对象来存储password。

public final class Password {

	private void setPassword(String pass) throws Exception {
		byte[] salt = generateSalt(12);
		MessageDigest msgDigest = MessageDigest.getInstance("SHA-256");
		// encode the string and salt
		byte[] hashVal = msgDigest.digest((pass + salt).getBytes());
		saveBytes(salt, "salt.bin");
		// save the hash value to password.bin
		saveBytes(hashVal, "password.bin");
	}

	boolean checkPassword(String pass) throws Exception {
		byte[] salt = loadBytes("salt.bin");
		MessageDigest msgDigest = MessageDigest.getInstance("SHA-256");
		// encode the string and salt
		byte[] hashVal1 = msgDigest.digest((pass + salt).getBytes());
		// load the hash value stored in password.bin
		byte[] hashVal2 = loadBytes("password.bin");
		return Arrays.equals(hashVal1, hashVal2);
	}

	private byte[] generateSalt(int n) {
		// generate a random byte array of length n
	}
}

即使攻击者知道程序使用SHA-256散列函数和12字节的盐,他还是不能从password.bin和salt.bin中获取获取真实的密码。

尽管上面的代码攻克了password被解密的问题,可是这段代码使用String对象来存放明文的password,因此。可參见“#00限制敏感数据的生命周期”进行改进。

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

以下的代码使用byte数组来存储明文password。

public final class Password {

	private void setPassword(byte[] pass) throws Exception {
		byte[] salt = generateSalt(12);
		byte[] input = appendArrays(pass, salt);
		MessageDigest msgDigest = MessageDigest.getInstance("SHA-256");
		// encode the string and salt
		byte[] hashVal = msgDigest.digest(input);
		clearArray(pass);
		clearArray(input);
		saveBytes(salt, "salt.bin");

		// save the hash value to password.bin
		saveBytes(hashVal, "password.bin");
		clearArray(salt);
		clearArray(hashVal);
	}

	boolean checkPassword(byte[] pass) throws Exception {
		byte[] salt = loadBytes("salt.bin");
		byte[] input = appendArrays(pass, salt);
		MessageDigest msgDigest = MessageDigest.getInstance("SHA-256");
		// encode the string and salt
		byte[] hashVal1 = msgDigest.digest(input);
		clearArray(pass);
		clearArray(input);

		// load the hash value stored in password.bin
		byte[] hashVal2 = loadBytes("password.bin");
		boolean arraysEqual = Arrays.equals(hashVal1, hashVal2);
		clearArray(hashVal1);
		clearArray(hashVal2);
		return arraysEqual;
	}

	private byte[] generateSalt(int n) {
		// generate a random byte array of length n
	}

	private byte[] appendArray(byte[] a, byte[] b) {
		// return a new array of a[] appended to b[]
	}

	private void clearArray(byte[] a) {
		for (int i=0; i<a.length; ++i) {
			a[i] = 0;
		}
	}
}

在setPassword()和checkPassword()函数中,明文password在使用后马上清空。

因此,攻击者在明文password清空后必须花费很多其它精力才干获取明文password。提供有保证的password清空是极具挑战的,它可能是平台相关的,甚至可能因为复制垃圾回收/动态分页/其它执行在Java语言之下的平台机制而变得不可行。

[适用性]

没有经过安全散列运算就进行存储的passwordeasy被非法的用户获取到。违背本条约将导致程序easy被非法利用。

像password管理器这种应用程序可能须要取得原始password并将其填写到第三方应用中。这一点是同意的,即使它违背了本条约。password管理器是由单一用户訪问的,并且通常是经过用户许可的才干够保存用户的password。同一时候依据要求进行password的展示。

因此,安全的决定因素取决于用户个人的能力而非程序的功能。

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

时间: 2024-08-25 11:33:37

【Java编码准则】の #13使用散列函数保存password的相关文章

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

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

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

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

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

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

【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编码准则】の #01限制内存中敏感数据的生命周期

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

【Java编码准则】の #12不要使用不安全或者强度弱的加密算法

安全性要求高的应用程序必须避免使用不安全的或者强度弱的加密算法,现代计算机的计算能力使得攻击者通过暴力破解能够攻破强度弱的算法.比如,数据加密标准算法DES是极度不安全的,使用类似EFF(Electronic Frontier Foundaton) Deep Crack的计算机在一天内能够暴力破解由DES加密的消息. [不符合安全要求的代码演示样例] 以下的代码使用强度弱的DES算法对字符串进行加密: SecretKey key = KeyGenerator.getInstance("DES&q

java编码规范

右括号") "与其后面的关键字之间,关键字与其后面的左括号"("或"{"之间,以及"}"与"{"之间,要以一个空格隔开:除". "外,所有二元操作符的前.后要加空格:在逗号后边加一个空格. 说明: 一个紧跟着括号的关键词应该被空格分开: 空白应该位于参数列表中逗号的后面: 所有的二元运算符,除了".",应该使用空格将之与操作数分开.一元操作符和操作数之间不应该加空格,

java编码规范及优化总结

一.java概述 1991 年Sun公司的James Gosling(詹姆斯·高斯林)等人开始开发名称为 Oak 的语言,希望用于控制嵌入在有线电视交换盒.PDA等的微处理器.1994年将Oak语言更名为Java. 二.Java的三种技术架构 J2EE:Java PlatformEnterprise Edition,开发企业环境下的应用程序,主要针对web程序开发: J2SE:Java PlatformStandard Edition,完成桌面应用程序的开发,是其它两者的基础: J2ME:Jav