加盐hash保存密码的正确方式

Hash算法是一种单向的函数。它可以把任意数量的数据转换成固定长度的‘指纹’,这个过程是不可逆的。而且只要输入发生改变,哪怕只有一个bit,输出的hash值也会有很大不同。

加盐

查表和彩虹表的方式之所以有效是因为每一个密码都是通过同样的方式来进行hash的。如果两个用户是用了同样的密码,那么他们的密码hash也一定相同。我们可以通过让每一个hash随机化,同一个密码hash两次,得到的不同的hash来避免这种攻击。

具体的操作就是给密码加一个随机的前缀或者后缀,然后再进行hash。这个随机的后缀或者前缀称为‘盐’。所以盐一般都是跟hash一起保存在数据库里,或者成为hash字符串的一部分。

常见的使用盐的错误实现是:一个盐在多个hash中是用或者是用的盐很短。

java.security.SecureRandom

每一个用户,每一个密码都要使用不同的盐。用户每次创建账户或者修改密码都要使用一个新的随机盐。永远不要重复使用盐。盐的长度要足够,一个经验就是盐至少要跟hash函数输出的长度一致。盐应该跟hash一起存储在用户信息表里。

存储一个密码:

1. 是用CSPRNG生成一个长的随机盐
2. 将密码和盐拼接在一起,使用标准的加密hash函数比如SHA256进行hash
3. 将盐和hash记录在用户数据库中

验证一个密码:

1. 从数据库中取出用户的盐和hash
2. 将用户输入的密码和盐按相同方式拼接在一起,使用相同的hash函数进行hash
3. 比较计算出的hash跟存储的hash是否相同。如果相同则密码正确,反之则密码错误。

当用户忘记密码的时候,我应该怎样让他们重置

大多数网站是用绑定的email来进行密码找回。通过生成一个随机的、只使用一次的token,这个token必须跟账户绑定,然后把密码重置的链接发送到用户邮箱中。当用户点击密码重置链接的时候,提示他们输入新的密码。需要注意token一定要绑定到用户以免攻击者使用发送给自己的token来修改别人的密码。

token一定要设置成15分钟后或者使用一次后作废。当用户登录或者请求了一个新的token的时候,之前发送的token都作废。

Reference

http://drops.wooyun.org/papers/1066

2015/10/25

ThreadLocal 那点事儿

2015/10/25

Java JDBC

JDBC在getConnection之前为什么要调用Class.forName

获取一个数据库连接的通用模板如下:

String driver = "oracle.jdbc.OracleDriver";
String url = "jdbc:oracle:thin:@127.0.0.1:1521:orcl";
String user = "scott";
String password = "ticmy";
Class.forName(driver);
Connection conn = DriverManager.getConnection(url, user, password);
  • 里面有个Class.forName(driver),这句话有什么作用?

  • Class.forName做了什么。

假设一个类以前从来没有被装进内存过,Class.forName(String className)这个方法会做以下几件事情:

1、装载。将字节码读入内存,并产生一个与之对应的java.lang.Class类对象

2、连接。这一步会验证字节码,为static变量分配内存,并赋默认值(0或null),并可选的解析符号引用(这里不理解没关系)

3、初始化。为类的static变量赋初始值,假如有static int a = 1;这个将a赋值为1的操作就是这个时候做的。除此之外,还要调用类的static块。(这一步是要点)

Class.forName(String className)方法会将这三步都做掉,如下面的例子:

package com.ticmy.oracle;

public class TestClinit {
    public static void main(String[] args) throws Exception {
        Class.forName("com.ticmy.oracle.ABC");
    }
}
class ABC {
    private static int a = getNum();
    static {
        System.out.println("this is static block");
    }
    public static int getNum() {
        System.out.println("getNum");
        return 1;
    }
}

程序的运行结果是:

getNum
this is static block

Class.forName(driver)的根本目的就是为了调用DriverManager.registerDriver。

Reference:

http://www.ticmy.com/?p=249

2015/10/25

Nagle算法和Delayed ACK

Nagle算法和Delayed ACK

Nagle算法

某个应用程序不断的发出小单位的数据,且某些只占1字节大小。因为TCP封包有40字节的报头,这导致了41字节大小的封包中只有1字节的可用数据。

if 有新資料要傳送
   if 訊窗大小 >= MSS and 可傳送的資料 >= MSS
     立刻傳送完整MSS大小的segment
   else
    if 管線中有尚未確認的資料,即之前发送的数据未收到ACK
      在下一個確認(ACK)封包收到前,將資料排進緩衝區队列
    else
      立即傳送資料  

TCP delayed acknowledgment

several ACK responses may be combined together into a single response, reducing protocol overhead.

Probelm

If Nagle's algorithm is being used by the sending party, data will be queued by the sender until an ACK is received. If the sender does not send enough data to fill the maximum segment size, then the transfer will pause up to the ACK delay timeout.

Reference

2015/10/25

Java LinkedList源码阅读

LinkedList

LinkedList是通过双向链表实现的。

它的顺序访问会非常高效,而随机访问效率比较低

private transient Entry<E> header = new Entry<E>(null, null, null);

// 获取LinkedList的最后一个元素
public E getLast()  {
      if (size==0)
          throw new NoSuchElementException();

      // 由于LinkedList是双向链表;而表头header不包含数据。
      // 因而,这里返回表头header的前一个节点所包含的数据。
      return header.previous.element;
  }

 // 获取双向链表中指定位置的节点
 private Entry<E> entry(int index) {
     if (index < 0 || index >= size)
         throw new IndexOutOfBoundsException("Index: "+index+
                                             ", Size: "+size);
     Entry<E> e = header;
     // 获取index处的节点。
     // 若index < 双向链表长度的1/2,则从前先后查找;
     // 否则,从后向前查找。
     if (index < (size >> 1)) {
         for (int i = 0; i <= index; i++)
             e = e.next;
     } else {
         for (int i = size; i > index; i--)
             e = e.previous;
     }
     return e;
 }


 // 从LinkedList末尾向前查找,删除第一个值为元素(o)的节点
 // 从链表开始查找,如存在节点的值为元素(o)的节点,则删除该节点
 public boolean removeLastOccurrence(Object o) {
     if (o==null) {
         for (Entry<E> e = header.previous; e != header; e = e.previous) {
             if (e.element==null) {
                 remove(e);
                 return true;
             }
         }
     } else {
         for (Entry<E> e = header.previous; e != header; e = e.previous) {
             if (o.equals(e.element)) {
                 remove(e);
                 return true;
             }
         }
     }
     return false;
 }
2015/10/25