使用hmac进行socket连接认证,避免计时攻击(timing attacks)。

认证和加密

这里需要注意认证和加密。连接认证是握手期间判别握手方是谁,如果只有认证,认证成功之后的通信消息是非加密的。而加密是连接建立后数据通信进行算法加密。两种是不一样的。

hmac和计时攻击

hmac认证算法是基于哈希函数包括MD5、SHA-1等,具体可以自行选择。

给定key计算message的hmac摘要,

1
2
3
4
5
6
import hashlib
import hmac
authkey = b"qweryt"
msg = b'\x1f\xd3\\?\xca\xd7(?m \x98`9\xa7;T'
dsg = hmac.digest(authkey, msg, hashlib.md5)
print(dsg)

对比hmac摘要是否相同,

1
2
hmac.compare_digest(dsg, dsg) # True
hmac.compare_digest(dsg, dsg + b"a") # False

hmac.compare_digest(a, b)是一个字符串比较函数有别于普通的字符串比较函数。普通的字符串比较函数原理如下,

1
2
3
4
5
6
7
8
def basic_compare(text1, text2):
if len(text1) != len(text2):
return False

for s1, s2 in zip(text1, text2):
if s1 != s2:
return False
return True

逐字符比较,一定遇到不一致就返回False。简单想一下,从安全角度来看,这个比较泄漏字符的匹配信息,两个字符串匹配越短,返回时间越短,这意味着可以逐位置尝试猜测匹配的字符串。我们称它为计时攻击(timing attacks)。

hmac.compare_digest(a, b)函数可以避免遭到计时攻击(timing attacks)。我们来看看其实现,

1
2
3
4
5
6
7
8
9
def compare_digest(text1, text2):
# 这里攻击者容易猜测字符串长度
if len(text1) != len(text2):
return False
r = 0
# 这样的实现字符匹配长度独立于判断时间长度
for x, y in zip(text1, text2):
r |= ord(x) ^ ord(y)
return r == 0

这样实现可以避免攻击者通过逐个位置比较获得判别时间的差异进而获得真实字符串。

连接认证

连接认证的过程:

  • 连接建立后,服务器给客户端发送一个随机的字节消息,并计算authkey和这个字节消息的hmac值(摘要)
  • 客户端接收服务器发送的随机的字节消息,并计算authkey和这个字节消息的hmac值(摘要),并把这个值发送回服务器
  • 服务器接收到摘要后,对比自己计算的hmac摘要是否一致。如果一致则认证通过。

这里authkey是双方都知道的字节序列,用来表征自己的身份。

实现

这里socket编程不详细展开,直接把hmac认证引入socket编程中。

服务器端的实现,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import hmac
import os
import hashlib
from socket import socket, AF_INET, SOCK_STREAM

def server_hmac_authenticate(conn, authkey):
message = os.urandom(64)
conn.send(message)
dsg = hmac.digest(authkey, message, hashlib.md5)
resp = conn.recv(len(dsg))
return hmac.compare_digest(dsg, resp)

def run_server(address):
server = socket(AF_INET, SOCK_STREAM)
server.bind(address)
server.listen(5)
while True:
conn, addr = server.accept()
print("accept {}...".format(addr))
if not server_hmac_authenticate(conn, authkey=b"querty"):
conn.close()
continue
print("authenticate is ok...")
while True:
msg = conn.recv(8192)
if not msg:
break
conn.sendall(msg)

if __name__ == "__main__":
run_server(("localhost", 8080))

客户端的实现,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import hashlib
import hmac
import time
import random
from socket import socket, AF_INET, SOCK_STREAM

def client_hmac_authenticate(conn, authkey):
msg = conn.recv(64)
dsg = hmac.digest(authkey, msg, hashlib.md5)
conn.send(dsg)

def run_client(address):
authkey = b"querty"
client = socket(AF_INET, SOCK_STREAM)
client.connect(address)
client_hmac_authenticate(client, authkey)
while True:
client.send(b"hmac")
resp = client.recv(1024)
print(resp)
time.sleep(1)

if __name__ == "__main__":
run_client(("localhost", 8080))

总结

这里提供了一种简便的基于hmac的socket认证方法。基于这种方法可以扩展到进程间通信的认证上,以此可以展开更多的应用。

参考

[1] https://en.wikipedia.org/wiki/HMAC

[2] https://docs.python.org/3/library/hmac.html

[3] https://en.wikipedia.org/wiki/Timing_attack

转载请包括本文地址:https://allenwind.github.io/blog/5981
更多文章请参考:https://allenwind.github.io/blog/archives/