避免明文密码的一个简单算法
2020-07-23
认证过程,不应该是客户端发送密码到服务端,然后服务端检查密码是否正确。因为,
- 密码绝不应该明文存储。
- 密码绝不应该明文在网络传输。
这里有一个简单的算法处理。客户端存 password,然后在服务端存 hash(password)。
认证的时候,由服务端发送一个 seed 给客户端。
然后客户端那边计算出 hash(seed, hash(password)),把这个结果返回服务端。
服务端这边比对一下结果,就 OK 了。
可以看到,存储方面,服务端是没有存储明文的。传输方面,也没有明文的密码在网络上传输。 不过这个简单算法里有个 hash 碰撞问题,因为不同的 key 在 hash 之后可能得到相同的值,有可能出现中间的密码用错了,但最后还是认证成功。
然后说一下混淆,利用 xor 两次之后会回到原来的值。
A xor B = C
A xor C = B
如果客户端和服务端之间约定一个魔数 A,客户端发送 A xor B,那么原始数据 B 混郩一次,变成了 C 在网络发送。服务端收到 C 以后,用魔数 A 做一个 xor 可以恢复出混淆前的数据 B。 这个魔数 A 选啥呢? 其实不需要是一个特定的数据,只要是客户端和服务端都知道的数据就行。
最后看一下 mysql_native_password
的实现模式:
// The new authentication is performed in following manner:
// SERVER: public_seed=create_random_string()
// send(public_seed)
// CLIENT: recv(public_seed)
// hash_stage1=sha1("password")
// hash_stage2=sha1(hash_stage1)
// reply=xor(hash_stage1, sha1(public_seed,hash_stage2)
// // this three steps are done in scramble()
// send(reply)
// SERVER: recv(reply)
// hash_stage1=xor(reply, sha1(public_seed,hash_stage2))
// candidate_hash2=sha1(hash_stage1)
// check(candidate_hash2==hash_stage2)
// // this three steps are done in check_scramble()
其实道理是一样的,其中 hash 方法选用的是 sha1(sha1(password)),然后魔数 A 选的是 sha1(seed, hash(password))。