#5 哈希:数字摘要
Python 哈希 密码学 2021-10-01开发时,有时候我们需要将任意字符串映射成一个数字或字符串,这就是哈希。
coding in a complicated world
开发时,有时候我们需要将任意字符串映射成一个数字或字符串,这就是哈希。
Authenticator
密码就算是身份验证器。
加密密钥,比如 SSH 的公钥私钥(密钥对)
One-time Password, 缩写: OTP, 又叫做一次性密码
我们常用的的短信验证码就是一种非常方便快捷的动态密码形式。
早些年一些网络服务,比如谷歌、网易通行证等,可能会提供一个动态口令表,包含十几个密码,可以保存为图片,或者纯文本,上面的密码可以逐个使用,每个密码只能用一次。
简单来说就是根据密钥和计数器来生成一个一次性密码。
除了记住密钥之外,你还要记住这是第几次使用。
MAC[(19 × 8) + 4:(19 × 8) + 7] × 8
MAC[i × 8 + 1:i × 8 + (4 × 8) − 1]
Multi-factor authentication, 缩写: MFA
Two-factor authentication, 缩写: 2FA
Google 身份验证器是一款 TOTP 与 HOTP 的两步验证软件令牌,此软件用于 Google 的认证服务。此项服务所使用的算法已列于 RFC 6238 和 RFC 4226 中。
Google 身份验证器给予用户一个六位到八位的一次性密码用于进行登录 Google 或其他站点时的附加验证。其同样可以给第三方应用生成口令,例如密码管理员或网络硬盘。先前版本的 Google 身份验证器开放源代码,但之后的版本以专有软件的形式公开。
谷歌验证器基于 TOTP,但是更进一步简化,以约定代替了协商过程。
此外:
虽然不是很大的创新,而且这个软件验证器实现很简单,但是免费、开放(不需要做任何谷歌服务绑定),加上谷歌的强大影响力,这个软件验证器被很多系统采用。
最后,其他提供动态口令的应用都需要来兼容谷歌身份验证器。
PS: RedHat 开发并维护了开源的 FreeOTP 分支项目。
PS: 微软也有一个 Microsoft Authenticator,阿里云 APP 中有一个 虚拟MFA 功能,都是一个意思。
微软家的为自己提供 8 位密码,别人家的就 6 位,区别对待(虽然感觉好像也并没有什么影响)
阿里云 MFA 是需要手机 APP 登录进去之后才能使用的。
import base64
import hashlib
import hmac
import time
DEFAULT_INTERVAL = 30 # Google Authenticator: 30 秒
DEFAULT_HASH = hashlib.sha1
def get_hotp_token(secret: str, counter: int, hash_algorithm=DEFAULT_HASH, length=6):
padding_len = 8 - len(secret) % 8
if padding_len != 8:
assert 1 <= padding_len <= 7
secret += '=' * padding_len
key = base64.b32decode(secret, True)
message = (counter & 0xffffffffffffffff).to_bytes(8, 'big')
mac = hmac.new(key, message, hash_algorithm).digest()
loc = mac[-1] & 0x0F
token = (int.from_bytes(mac[loc:loc+4], 'big') & 0x7fffffff) % (10 ** length)
return token
def get_totp_token(secret, interval=DEFAULT_INTERVAL):
time_counter = int(time.time()) // interval
return get_hotp_token(secret, time_counter)
if __name__ == '__main__':
print('%06d' % get_totp_token(sys.argv[1]))
TLS, Transport Layer Security, 传输层安全性协议
SSL, Secure Sockets Layer, 安全套接层
协议 | 发布时间 | 状态 | 说明 |
---|---|---|---|
SSL 1.0 | 未公布 | 未公布 | |
SSL 2.0 | 1995 年 | 2011 年弃用 | |
SSL 3.0 | 1996 年 | 2015 年弃用 | |
TLS 1.0 | 1999 年 | 2021 年弃用 | RFC 2246 |
TLS 1.1 | 2006 年 | 2021 年弃用 | RFC 4346 |
TLS 1.2 | 2008 年 | RFC 5246 | |
TLS 1.3 | 2018 年 | RFC 8446 |
现在主流的是 TLS 1.2 和 TLS 1.3。
相比之下,TLS 1.3 安全性更好,性能也更好。
搜索一下 tls1.2 tls1.3 difference
或者 tls1.2 tls1.3 performance
就能看到很多相关比较。
SSL/TLS 的本质就是非对称加密。
安全传输层协议(TLS)用于在两个通信应用程序之间提供保密性和数据完整性。
该协议由两层组成:
- TLS 记录协议(TLS Record)
- TLS 握手协议(TLS Handshake)
握手协议(handshake protocol)
密钥规格变更协议(change cipher spec protocol)
应用数据协议(application data protocol)
警报协议(alert protocol)。
HTTP/2 规范并没有要求使用 TLS,但是主流浏览器开发商全部选择在 TLS 之上实现 HTTP/2。
微软宣布从 2022 年 9 月 13 日起, Internet Explorer 和 EdgeHTML( WebView 控件的呈现引擎) 将默认禁用 TLS 1.0 和 1.1。禁用 TLS 1.0 和 1.1 的工作早在 2020 年提出,但当时新冠病毒在全球爆发,大量开发者居家办公,微软只能推迟淘汰 TLS 1.0 和 1.1 的时间。
注意:不是弃用,仍然可以通过配置打开相关支持。
在 Python 2.7.8 (July 2, 2014),或 Python 3.4 (March 17, 2014) 以上版本,标准库 hashlib 有 pbkdf2_hmac
:
import base64
import hashlib
import random
import secrets
def pbkdf2(password, salt, iterations, dklen=0, digest=None):
if digest is None:
digest = hashlib.sha256
if not dklen:
dklen = None
password = password.encode('utf-8')
salt = salt.encode('utf-8')
return hashlib.pbkdf2_hmac(
digest().name, password, salt, iterations, dklen)
### pbkdf2_sha256 ###
ALGORITHM = 'pbkdf2_sha256'
ITERATIONS = 30000
DIGEST = hashlib.sha256
def encode(password, salt, iterations=None):
assert password is not None
assert salt and '$' not in salt
if not iterations:
iterations = ITERATIONS
hash = pbkdf2(password, salt, iterations, digest=DIGEST)
hash = base64.b64encode(hash).decode('ascii').strip()
return "%s$%d$%s$%s" % (ALGORITHM, iterations, salt, hash)
def decode(encoded):
algorithm, iterations, salt, hash = encoded.split('$', 3)
assert algorithm == ALGORITHM
return {
'algorithm': algorithm,
'hash': hash,
'iterations': int(iterations),
'salt': salt,
}
def verify(password, encoded):
algorithm, iterations, salt, hash = encoded.split('$', 3)
assert algorithm == ALGORITHM
encoded_2 = encode(password, salt, int(iterations))
return secrets.compare_digest(encoded.encode('utf-8'),
encoded_2.encode('utf-8'))
salt_length = 32
# salt = bytes([random.randint(0, 255) for i in range(salt_length + 20)])
salt = random.randbytes(salt_length + 20)
salt = salt.replace(b'$', b'').decode('latin')[:salt_length]
# print(repr(salt))
a = encode('123456', salt)
print(repr(a))
print(decode(a))
# 'pbkdf2_sha256$30000$x\x82¨\x07Ó[Ám¬9V¬\x13·sÉ\x1eEܱ3\x04Ü\x07\x05BÁfM\x9fV×$/jqvasjfZX6c9xCytBVqddAJFve2DXcLWClTAsZvl48='
print(verify('234567', a))
print(verify('123456', a))
https://docs.djangoproject.com/en/2.0/topics/auth/passwords/
在 Django 中,密码哈希存储在 auth_user 表中的 password 字段中。该字段的值包含了算法名称、迭代次数、盐值和哈希值,它们都是使用特定的格式进行编码的。例如,一个密码哈希的值可能如下所示:
pbkdf2_sha256$150000$V7v0fjbhMIhq$Gd/0XuOqK3ib6NBNGVwIKGXcFTUiNbTzdTNN8RiW24E=
用美元符号切割,得到 4 部分:
pbkdf2_sha256
算法名称150000
迭代次数V7v0fjbhMIhq
盐Gd/0XuOqK3ib6NBNGVwIKGXcFTUiNbTzdTNN8RiW24E=
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
'django.contrib.auth.hashers.BCryptPasswordHasher',
]
默认使用的是 PBKDF2PasswordHasher
:
PBKDF2 是基于密钥的密码派生函数,它使用一个伪随机函数(PRF)和一个盐值来从给定密码派生出一个密钥。
PBKDF2 算法的核心是迭代的使用 PRF 函数,将每一次迭代的输出与前一次迭代的输出进行异或,生成最终的派生密钥。
大概逻辑如下:
import hashlib
import binascii
import os
class PBKDF2PasswordHasher:
algorithm = 'pbkdf2_sha256'
iterations = 100000
def salt(self):
return binascii.hexlify(os.urandom(16)).decode()
def encode(self, password, salt):
dk = hashlib.pbkdf2_hmac('sha256', password.encode(), salt.encode(), self.iterations)
return '%d$%s$%s' % (self.iterations, salt, binascii.hexlify(dk).decode())
def verify(self, password, encoded):
iterations, salt, dk = encoded.split('$')
iterations = int(iterations)
hashed_password = self.encode(password, salt)
return hashed_password == encoded
from django.contrib.auth.hashers import make_password
password = '123456'
hashed_password = make_password(password)
from django.contrib.auth.hashers import check_password
is_correct_password = check_password(password, hashed_password)