锁定老帖子 主题:一种简单的给MD5加盐算法
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (3)
|
|
---|---|
作者 | 正文 |
发表时间:2012-10-23
lonelybug 写道 另外,我没明白你要说啥,如果干扰算法公开都可以重构md5库反差,那你做的是什么?如果salt公开了那还叫作salt?我实在没搞懂你的逻辑。 拜托仔细读一读我的帖子吧,求您了。RSA算法听着您的这种逻辑都得哭了。 |
|
返回顶楼 | |
发表时间:2012-10-23
lonelybug 写道 我对你的“心宽”表达非常“敬仰”。 String salt = String.valueOf(i1) + String.valueOf(i2); if (salt.length() < 16) { for (int i = 0; i < 16 - salt.length(); i++) { salt += "0"; } } 这段代码,你知道如果你用+的话会产生多少次String的拷贝么? 读读thinking in java你就不会这么心宽的人为可以忽略不计。 另外,我没明白你要说啥,如果干扰算法公开都可以重构md5库反差,那你做的是什么?如果salt公开了那还叫作salt?我实在没搞懂你的逻辑。 /** * 生成含有随机盐的密码 */ public static String generate(String password) { int i1 = new Random().nextInt(99999999); int i2 = new Random().nextInt(99999999); String salt = String.valueOf(i1) + String.valueOf(i2); if (salt.length() < 16) { int len = salt.length(); for (int i = 0; i < 16 - len; i++) { salt += "0"; } } password = md5Hex(password + salt); char[] cs1 = password.toCharArray(); char[] cs2 = salt.toCharArray(); char[] cs = new char[48]; for (int i = 0; i < 48; i += 3) { cs[i] = cs1[i / 3 * 2]; cs[i + 1] = cs2[i / 3]; cs[i + 2] = cs1[i / 3 * 2 + 1]; } return new String(cs); } 1 为什么随机2个8位数字然后相加,然后前面补0?直接生成 1*~9*(其中*表示15位数字)的不就行了。 2 password先toCharArray再用下标读。为什么不直接用String.charAt(int index)方法? |
|
返回顶楼 | |
发表时间:2012-10-24
最后修改:2012-10-24
wyuch 写道 lonelybug 写道 wyuch 写道 lonelybug 写道 作为一个加密函数,你这程序写的性能未免有点低。
首先你Salt补零的过程用循环就会是一个问题。你为什么不初始化一个用16个‘0’的字符串初始化一个StringBuffer,然后再作后面的工作就可以了。 对于后面48位密码产生也是同样的道理。 还有基本上你这种加密没什么意义,因为48位密码的混合顺序是固定的(人为的),而不是consistent的随机产生。 原来是有工具方法StringUtil.leftPad来补齐的,随手改成了不依赖于别的工具函数的写法了。 你要用StringBuffer,为什么不用StringBuilder呢?其实,你将这个函数改成StringBuffer实现还是StringBuilder实现,还是用+号,性能差异都可以忽略不计。 这也不是加密,只是在摘要中加入干扰项,增加反查的难度的。我前面已经说过了,只要干扰算法是公开的,人家都可以用一样的方式重新构造MD5库再反查。 我对你的“心宽”表达非常“敬仰”。 String salt = String.valueOf(i1) + String.valueOf(i2); if (salt.length() < 16) { for (int i = 0; i < 16 - salt.length(); i++) { salt += "0"; } } 这段代码,你知道如果你用+的话会产生多少次String的拷贝么? 读读thinking in java你就不会这么心宽的人为可以忽略不计。 另外,我没明白你要说啥,如果干扰算法公开都可以重构md5库反差,那你做的是什么?如果salt公开了那还叫作salt?我实在没搞懂你的逻辑。 public static String generate1(String password) { int i1 = new Random().nextInt(99999999); int i2 = new Random().nextInt(99999999); String salt = String.valueOf(i1) + String.valueOf(i2); int len = salt.length(); if (len < 16) { for (int i = 0; i < 16 - len; i++) { salt += "0"; } } password = md5Hex(password + salt); char[] cs1 = password.toCharArray(); char[] cs2 = salt.toCharArray(); char[] cs = new char[48]; for (int i = 0; i < 48; i += 3) { cs[i] = cs1[i / 3 * 2]; char c = cs2[i / 3]; cs[i + 1] = c; cs[i + 2] = cs1[i / 3 * 2 + 1]; } return new String(cs); } public static String generate2(String password) { int i1 = new Random().nextInt(99999999); int i2 = new Random().nextInt(99999999); String salt = String.valueOf(i1) + String.valueOf(i2); int len = salt.length(); if (len < 16) { StringBuffer sb = new StringBuffer(salt); for (int i = 0; i < 16 - len; i++) { sb.append("0"); } salt = sb.toString(); } password = md5Hex(password + salt); char[] cs1 = password.toCharArray(); char[] cs2 = salt.toCharArray(); char[] cs = new char[48]; for (int i = 0; i < 48; i += 3) { cs[i] = cs1[i / 3 * 2]; cs[i + 1] = cs2[i / 3]; cs[i + 2] = cs1[i / 3 * 2 + 1]; } return new String(cs); } public static String generate3(String password) { int i1 = new Random().nextInt(99999999); int i2 = new Random().nextInt(99999999); String salt = String.valueOf(i1) + String.valueOf(i2); int len = salt.length(); if (len < 16) { StringBuilder sb = new StringBuilder(salt); for (int i = 0; i < 16 - len; i++) { sb.append("0"); } salt = sb.toString(); } password = md5Hex(password + salt); char[] cs1 = password.toCharArray(); char[] cs2 = salt.toCharArray(); char[] cs = new char[48]; for (int i = 0; i < 48; i += 3) { cs[i] = cs1[i / 3 * 2]; cs[i + 1] = cs2[i / 3]; cs[i + 2] = cs1[i / 3 * 2 + 1]; } return new String(cs); } /** * 获取十六进制字符串形式的MD5摘要 */ public static String md5Hex(String src) { try { MessageDigest md5 = MessageDigest.getInstance("MD5"); byte[] bs = md5.digest(src.getBytes()); return new String(new Hex().encode(bs)); } catch (Exception e) { return null; } } public static void main(String[] args) { long t = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { generate1("admin"); } System.out.println("使用加号:\t" + (System.currentTimeMillis() - t)); t = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { generate2("admin"); } System.out.println("使用StringBuilder:\t" + (System.currentTimeMillis() - t)); t = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { generate3("admin"); } System.out.println("使用StringBuffer:\t" + (System.currentTimeMillis() - t)); } 关于加号: 说了随手改的,不是每个人都有洁癖见不得加号,而且你有洁癖也拜托实事求是公允一点嘛。最受不了的是,你还不仔细读人家的帖子就开始批评了,说半天都白说了。关于加号性能,我说可以忽略不计是有根据的,就比如上面这个程序里。我实际测试了三次,得分都相差无几。如下所示: 第一次: 使用加号: 11203 StringBuilder: 11422 StringBuffer: 11406 第二次: 使用加号: 11203 StringBuilder: 11391 StringBuffer: 11453 第三次 使用加号: 11203 StringBuilder: 11312 StringBuffer: 11485 原因何在?一是因为主要是运算是MD5,字符串运算所占比较非常小。二是因为加号不是每次都执行(90%的情况下随机出来的结果都已经是2个8位数了),而StringBuffer每次都new了实例,导致自身的性能优势被抵消甚至处于劣势了。额,细心的同学肯定发现了,我太“宽心”地让new StringBuffer每次都执行,可以改造成根据需要才new嘛,好吧,那样StringBuffer会相对于加号取得2%左右的性能优势。 那么对于大量的字符串连接,StringBuffer到底有多大优势呢,我也写了个测试例子如下: public static void main(String[] args) { main1(); main2(); main3(); } public static void main1() { long t = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { String str = ""; for (int j = 0; j < 16; j++) { str += "0"; } } System.out.println("使用加号:\t" + (System.currentTimeMillis() - t)); } public static void main2() { long t = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { StringBuilder sb = new StringBuilder(16); for (int j = 0; j < 16; j++) { sb.append("0"); } } System.out.println("StringBuilder:\t" + (System.currentTimeMillis() - t)); } public static void main3() { long t = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { StringBuffer sb = new StringBuffer(16); for (int j = 0; j < 16; j++) { sb.append("0"); } sb.toString(); } System.out.println("StringBuffer:\t" + (System.currentTimeMillis() - t)); } 第一次: 使用加号: 3484 StringBuilder: 578 StringBuffer: 1422 第二次: 使用加号: 3532 StringBuilder: 562 StringBuffer: 1406 第三次: 使用加号: 3578 StringBuilder: 562 StringBuffer: 1407 根据测试结果,每个方法每次测试都执行了100万*16=1600万次的字符串连接操作,最慢的加号费时不过3.5秒,StringBuffer要快一些,只需要1.5秒,StringBuilder性能最好,只需要不到0.6秒。 总结陈词如下: 1、lonelybug同学说的对,对于大量的字符串连接操作StringBuffer相对加号有性能优势。 2、不过也得分场合,对于少量的字符串连接,您也用不着担心,确实是可以忽略不计的,说不定您不小心没把new语句放到if时面时StringBuffer反而处于劣势了。 3、一个应用每天就算有一千万次登录,PasswordUtil里面的加号运算所费CPU时间全部加一起都不超过1秒。 4、对于局部变量,StringBuffer已经不推荐使用了,JDK1.5以后建议使用StringBuilder,因为StringBuilder不需要同步,所以相对需要同步的StringBuffer有性能优势。lonelybug用不着把StringBuffer当个宝了。 5、建议lonelybug同学您先动手再发言,不要看了几本书,就想着四处找人毛病,因为别人用了个加句就犯了你什么大忌似的,寻章摘句老雕虫不好玩。 你做的测试叫做100万次,不叫做1600万次,因为,每次登录时调用的是整个函数,不是只是单独的一次补零操作。 为了更好的测试你的算法是否真的有效率,我觉得除了时间复杂度方面,我们还需要在空间复杂度上面测试一下。 下面的截图是我在我的i7 3500k 8G内存 heap开1G的情况下跑了你的算法1亿次--我相信很多网站每天的登陆次数远远超过1000万次,比如renren,face*book,weibo,twitter等,当然,作为一个基础工具类的加密算法,我相信很多其他方面也会应用到,比如产生ID或者session key(Open API应用场景),所以,我假设1亿次。 第一个图是用StringBuilder,第二图是+号方法。 很简单可以看出,为了一个加密功能Heap基本要用掉300MB-400MB,而StringBuilder稳定在50MB一下,我相信很多网站对于你的算法所消耗的Heap是不太能承受的。 |
|
返回顶楼 | |
发表时间:2012-10-24
wyuch 写道 lonelybug 写道 另外,我没明白你要说啥,如果干扰算法公开都可以重构md5库反差,那你做的是什么?如果salt公开了那还叫作salt?我实在没搞懂你的逻辑。 拜托仔细读一读我的帖子吧,求您了。RSA算法听着您的这种逻辑都得哭了。 可惜你做的不是RSA算法,公钥型加密算法和你这个加Salt的MD5有着本质的区别。我不需要再读一遍你的帖子。 我觉得你倒是可以从新看看RSA的介绍文章。 |
|
返回顶楼 | |
发表时间:2012-10-24
另外看着LZ带有很强烈的感情色彩的渲染回复贴,我觉得LZ其实适合一些艺术方面的创作,因为软件设计更需要的是客观和理智的分析。
哪怕是一点点地性能损失或者提升,对于很多系统来说都随着时间的延续带来一些难以预测影响。 PS:我依然觉得LZ强大的内心可以把很多有所谓变得无所谓让人深深的敬佩。 |
|
返回顶楼 | |
发表时间:2012-10-24
lonelybug 写道 wyuch 写道 lonelybug 写道 另外,我没明白你要说啥,如果干扰算法公开都可以重构md5库反差,那你做的是什么?如果salt公开了那还叫作salt?我实在没搞懂你的逻辑。 拜托仔细读一读我的帖子吧,求您了。RSA算法听着您的这种逻辑都得哭了。 可惜你做的不是RSA算法,公钥型加密算法和你这个加Salt的MD5有着本质的区别。我不需要再读一遍你的帖子。 我觉得你倒是可以从新看看RSA的介绍文章。 比喻知道吧?你不需要再读一遍,因为你不需要读就可以根据你的强大内心发表评论。也不知道你的“如果salt公开了那还叫作salt”是从哪本书上看到的。 |
|
返回顶楼 | |
发表时间:2012-10-24
lonelybug 写道 另外看着LZ带有很强烈的感情色彩的渲染回复贴,我觉得LZ其实适合一些艺术方面的创作,因为软件设计更需要的是客观和理智的分析。
哪怕是一点点地性能损失或者提升,对于很多系统来说都随着时间的延续带来一些难以预测影响。 PS:我依然觉得LZ强大的内心可以把很多有所谓变得无所谓让人深深的敬佩。 “因为软件设计更需要的是客观和理智的分析”,非常赞同,所以你可以多耐心一点读懂别人帖子再发言。 |
|
返回顶楼 | |
发表时间:2012-10-24
最后修改:2012-10-24
lonelybug 写道 你做的测试叫做100万次,不叫做1600万次,因为,每次登录时调用的是整个函数,不是只是单独的一次补零操作。 为了更好的测试你的算法是否真的有效率,我觉得除了时间复杂度方面,我们还需要在空间复杂度上面测试一下。 下面的截图是我在我的i7 3500k 8G内存 heap开1G的情况下跑了你的算法1亿次--我相信很多网站每天的登陆次数远远超过1000万次,比如renren,face*book,weibo,twitter等,当然,作为一个基础工具类的加密算法,我相信很多其他方面也会应用到,比如产生ID或者session key(Open API应用场景),所以,我假设1亿次。 第一个图是用StringBuilder,第二图是+号方法。 很简单可以看出,为了一个加密功能Heap基本要用掉300MB-400MB,而StringBuilder稳定在50MB一下,我相信很多网站对于你的算法所消耗的Heap是不太能承受的。 额,知道换成StringBuilder了。 “你做的测试叫做100万次,不叫做1600万次”。我是说1600万次字符串连接呀,你老是看不清楚。 算法中没有任何地方会请求超过10K的内存,所以我觉得你可以分析一下这样一段简单代码为什么会使用300多M的内存。我的看法:这是因为你的heap相对于这个算法开得很高,GC可以较长一段时间不需要收集,导致看起来算法占用的内存很高,实际上算法运行需要的内存撑死不过几M。加号和StringBuilder这种空间方面的表象实际上依然是一个加号多创建了对象的问题,在考虑内存回收的附加因素后,依然可以归结为时间复杂度。 一个比较好的测试方法时加一个JVM参数-Xmx10m,让两种方法都在10M heap里运行1亿次。我的测试结果(10万次)如下: 使用加号: 11093 使用StringBuilder: 10891 使用StringBuffer: 11000 是不是可以忽略不计?“我相信很多网站对于你的算法所消耗的Heap是不太能承受的。”这很显然是一个想当然的判断。 你关于我的性格的结论是不是有点下得太早了?欢迎你继续发言。 |
|
返回顶楼 | |
发表时间:2012-10-24
lonelybug 写道 使用加号: 11093 使用StringBuilder: 10891 使用StringBuffer: 11000 是不是可以忽略不计?“我相信很多网站对于你的算法所消耗的Heap是不太能承受的。”这很显然是一个想当然的判断。 你关于我的性格的结论是不是有点下得太早了?欢迎你继续发言。 我是在看不下去了...你怎么测试的呢..我这里用你这段代码 public class TestString { public static void main(String[] args) { main1(); main2(); main3(); } public static void main1() { long t = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { String str = ""; for (int j = 0; j < 16; j++) { str += "0"; } } System.out.println("使用加号:\t" + (System.currentTimeMillis() - t)); } public static void main2() { long t = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { StringBuilder sb = new StringBuilder(16); for (int j = 0; j < 16; j++) { sb.append("0"); } } System.out.println("StringBuilder:\t" + (System.currentTimeMillis() - t)); } public static void main3() { long t = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { StringBuffer sb = new StringBuffer(16); for (int j = 0; j < 16; j++) { sb.append("0"); } sb.toString(); } System.out.println("StringBuffer:\t" + (System.currentTimeMillis() - t)); } } 100万结果: 使用加号: 1266 StringBuilder: 221 StringBuffer: 600 1000万结果: 使用加号: 18662 StringBuilder: 2534 StringBuffer: 3597 16个字符相加..如果将来不满足改成32个字符...效率..算了 我再试试(以上测试都是在-Xmx10m情况下) 当前JVM占用的内存总数6 使用加号: 38734 StringBuilder: 5215 StringBuffer: 6444 当前JVM占用的内存总数6 cpu占用率很高... +的情况下..会创建16个字符串+一个空字符,并且16个字符串是递增大小的.就光创建对象..是StringBuilde的16倍.别说.虚拟机把字符串放到常量池这种.高深的问题了..程序的优化..不应当指望虚拟机来做.当内存小的时候.然后频繁gc..这么明显一个问题..明明错了..你就改了就可以了.有必要.争论这个么...还有..大家也没时间深入研究你的内心思维..理解难免有偏差..还有StringBuilder 和StringBuffer ..你没必要这么诋毁人家.就算StringBuffer线程安全.效率不高.最坏情况也和+差不多的.. |
|
返回顶楼 | |
发表时间:2012-10-24
wyuch 写道 lonelybug 写道 使用加号: 11093 使用StringBuilder: 10891 使用StringBuffer: 11000 是不是可以忽略不计?“我相信很多网站对于你的算法所消耗的Heap是不太能承受的。”这很显然是一个想当然的判断。 你关于我的性格的结论是不是有点下得太早了?欢迎你继续发言。 我是在看不下去了...你怎么测试的呢..我这里用你这段代码 public class TestString { public static void main(String[] args) { main1(); main2(); main3(); } public static void main1() { long t = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { String str = ""; for (int j = 0; j < 16; j++) { str += "0"; } } System.out.println("使用加号:\t" + (System.currentTimeMillis() - t)); } public static void main2() { long t = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { StringBuilder sb = new StringBuilder(16); for (int j = 0; j < 16; j++) { sb.append("0"); } } System.out.println("StringBuilder:\t" + (System.currentTimeMillis() - t)); } public static void main3() { long t = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { StringBuffer sb = new StringBuffer(16); for (int j = 0; j < 16; j++) { sb.append("0"); } sb.toString(); } System.out.println("StringBuffer:\t" + (System.currentTimeMillis() - t)); } } 100万结果: 使用加号: 1266 StringBuilder: 221 StringBuffer: 600 1000万结果: 使用加号: 18662 StringBuilder: 2534 StringBuffer: 3597 16个字符相加..如果将来不满足改成32个字符...效率..算了 我再试试(以上测试都是在-Xmx10m情况下) 当前JVM占用的内存总数6 使用加号: 38734 StringBuilder: 5215 StringBuffer: 6444 当前JVM占用的内存总数6 cpu占用率很高... +的情况下..会创建16个字符串+一个空字符,并且16个字符串是递增大小的.就光创建对象..是StringBuilde的16倍.别说.虚拟机把字符串放到常量池这种.高深的问题了..程序的优化..不应当指望虚拟机来做.当内存小的时候.然后频繁gc..这么明显一个问题..明明错了..你就改了就可以了.有必要.争论这个么...还有..大家也没时间深入研究你的内心思维..理解难免有偏差..还有StringBuilder 和StringBuffer ..你没必要这么诋毁人家.就算StringBuffer线程安全.效率不高.最坏情况也和+差不多的.. 。。。。。。。 他们是在讨论PasswordUtil执行多次的效率问题,和你的不是一个吧,你用错了代码吧。 我觉得既然是评论,就得把人家的意思看懂,不然大家讨论起来风马牛不相及,边上看的人也累。 |
|
返回顶楼 | |