OPENVPN的验证方式很灵活,可以通过ldap,mysql,也可以进一步扩展,实现使用Google Authenticator实现二次验证的方式来实现。
Google Authenticator
其原理并不复杂:
客户端和服务器事先协商好一个密钥K,用于一次性密码的生成过程,此密钥不被任何第三方所知道。此外,客户端和服务器各有一个计数器C,并且事先将计数值同步。
进行验证时,客户端对密钥和计数器的组合(K,C)使用HMAC(Hash-based Message Authentication Code)算法计算一次性密码,公式如下:
HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))
上面采用了HMAC-SHA-1,当然也可以使用HMAC-MD5等。HMAC算法得出的值位数比较多,不方便用户输入,因此需要截断(Truncate)成为一组不太长十进制数(例如6位)。计算完成之后客户端计数器C计数值加1。用户将这一组十进制数输入并且提交之后,服务器端同样的计算,并且与用户提交的数值比较,如果相同,则验证通过,服务器端将计数值C增加1。如果不相同,则验证失败。
客户端和服务器事先协商好一个密钥K,用于一次性密码的生成过程,此密钥不被任何第三方所知道。此外,客户端和服务器各有一个计数器C,并且事先将计数值同步。
进行验证时,客户端对密钥和计数器的组合(K,C)使用HMAC(Hash-based Message Authentication Code)算法计算一次性密码,公式如下:
HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))
上面采用了HMAC-SHA-1,当然也可以使用HMAC-MD5等。HMAC算法得出的值位数比较多,不方便用户输入,因此需要截断(Truncate)成为一组不太长十进制数(例如6位)。计算完成之后客户端计数器C计数值加1。用户将这一组十进制数输入并且提交之后,服务器端同样的计算,并且与用户提交的数值比较,如果相同,则验证通过,服务器端将计数值C增加1。如果不相同,则验证失败。(链接:https://www.zhihu.com/question/20462696/answer/19670601)
示例代码
使用python实现的代码如下,这段代码还是实现了验证,生成,生成二维码,生成二维码图像base64编码等功能。
AuthCode.py
import base64
import hashlib
import hmac
import time
import datetime
import qrcode
import random as _random
from PIL import Image
import io
'''
'''
class AuthCode:
def byte_secret(self, secret):
# print(self)
missing_padding = len(secret) % 8
if missing_padding != 0:
secret += '=' * (8 - missing_padding)
return base64.b32decode(secret, casefold=True)
def int_to_bytestring(self, i, padding=8):
# print(self)
result = bytearray()
while i != 0:
result.append(i & 0xFF)
i >>= 8
return bytes(bytearray(reversed(result)).rjust(padding, b'\0'))
# 根据约定的密钥计算当前动态密码
def generate_otp(self, secret, timestamp=None):
if not timestamp:
timestamp = datetime.datetime.now()
for_time = timestamp
i = time.mktime(for_time.timetuple())
intput = int(i / 30)
digest = hashlib.sha1
digits = 6
if intput < 0:
raise ValueError('input must be positive integer')
hasher = hmac.new(self.byte_secret(secret), self.int_to_bytestring(intput), digest)
hmac_hash = bytearray(hasher.digest())
offset = hmac_hash[-1] & 0xf
code = ((hmac_hash[offset] & 0x7f) << 24 |
(hmac_hash[offset + 1] & 0xff) << 16 |
(hmac_hash[offset + 2] & 0xff) << 8 |
(hmac_hash[offset + 3] & 0xff))
str_code = str(code % 10 ** digits)
while len(str_code) < digits:
str_code = '0' + str_code
return str_code
# 随机生成一个base32密钥
def random_base32(self, length=32, random=_random.SystemRandom(),
chars=None):
# print(self)
if chars is None:
chars = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567')
return ''.join(
random.choice(chars)
for _ in range(length)
)
# 生成二维码地址
def getQRCodeGoogleUrl(self, name, secret):
"""
:param self:
:param name: 用户名
:param secret: 秘钥
:return:
"""
# print(self)
return "otpauth://totp/" + name + "?secret=%s" % secret
# 生成二维码base64格式
def genQr64(self, url):
# print(self)
qr = qrcode.make(url)
buf = io.BytesIO()
qr.save(buf)
img_buf = buf.getvalue()
img_stream = base64.b64encode(img_buf)
return img_stream
def save_base64_qr_to_image(self, base64_str, filename="qrcode.png"):
# print(self)
# 将 base64 字符串解码为二进制数据
image_data = base64.b64decode(base64_str)
# 使用 io.BytesIO 将二进制数据转换为图片对象
image = Image.open(io.BytesIO(image_data))
# 保存图片
image.save(filename)
return filename
def get_code(self, sKey):
"""
使用说明
使用random_base32 生成 密钥 sKey
使用generate_otp(sKey) 生成 code 时间戳默认为当前,一般情况要传递30s 60s 90s 120s 之前 共计五个时间戳来生成code
"""
# print(self)
now = datetime.datetime.now()
now_30s = now - datetime.timedelta(seconds=30)
now_60s = now - datetime.timedelta(seconds=60)
now_90s = now - datetime.timedelta(seconds=90)
now_120s = now - datetime.timedelta(seconds=120)
code_list = [self.generate_otp(sKey, now),
self.generate_otp(sKey, now_30s),
self.generate_otp(sKey, now_60s),
self.generate_otp(sKey, now_90s),
self.generate_otp(sKey, now_120s)]
return code_list
def verify_code(self, two_code, sKey):
"""
验证二次登录验证码
"""
if two_code in self.get_code(sKey):
return True
else:
return False
if __name__ == "__main__":
# # 获取秘钥
# Key = AuthCode().random_base32()
# print("google_auth:" + Key)
#
# # 获取二维码
# u = AuthCode().getQRCodeGoogleUrl("glen", Key)
# #
# base64str = AuthCode().genQr64(u)
# # print(AuthCode().save_base64_qr_to_image(base64str))
# # 获取code阵列
# print(AuthCode().get_code(Key))
# # 验证方法
code = '468916'
key = 'IJ725J46SAJ6J2PJZ2C4R6Y5MD3MBI7R'
code_list = AuthCode().get_code(key)
print(code_list)
if code in code_list:
print("ok")
else:
print('fail')