• 【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 ,未经本人允许请勿用于商业用途,谢谢——

  • 相关阅读:
    NHibernate 过滤器(第十五篇)
    NHibernate 存储过程 第十四篇
    NHibernate 操作视图 第十三篇
    NHibernate Linq查询 扩展增强 (第九篇)
    NHibernate 之数据操作 (第五篇)
    NHibernate之一级缓存(第十篇)
    jQueryEasyUI
    linux的systemctl 命令用法 转
    linux dig命令 转
    OPTAUTH 两步验证详解
  • 原文地址:https://www.cnblogs.com/mfmdaoyou/p/7256102.html
Copyright © 2020-2023  润新知