hmac — 加密消息签名和验证

hmac — 加密消息签名和验证

1.概述

它的全称叫做Hash-based Message Authentication Code: 哈希消息认证码,从名字中就可以看出来这个hmac基于哈希函数的,并且还得提供一个秘钥key,它的作用就是用来保证消息的完整性,不可篡改。基本思想就是,将消息用一个哈希函数加上一个秘钥key生成一个摘要,比如现在很流行的JWT就是基于hmac实现的。

2.签名消息

2.1.默认MD5算法加密

new() 函数创建一个用于计算消息签名的新对象。此示例使用默认的 MD5 散列算法。

import hmac

digest_maker = hmac.new(b'secret-shared-key-goes-here')

with open('lorem.txt', 'rb') as f:
    while True:
        block = f.read(1024)
        if not block:
            break
        digest_maker.update(block)

digest = digest_maker.hexdigest()
print(digest)

运行时,代码读取数据文件并为其计算 HMAC 签名

4bcb287e284f8c21e87e14ba2dc40b16

2.2.可选算法加密

尽管默认的 hmac 算法是 MD5,但那不是最安全的方法。MD5 摘要算法有一些缺点,例如碰撞?(两个不同的消息产生了不同的哈希值)。 SHA-1 被公认为是比较健壮的,应该使用它。

import hmac
import hashlib

digest_maker = hmac.new(
    b'secret-shared-key-goes-here',
    b'',
    hashlib.sha1,
)

with open('hmac_sha.py', 'rb') as f:
    while True:
        block = f.read(1024)
        if not block:
            break
        digest_maker.update(block)

digest = digest_maker.hexdigest()
print(digest)

new() 函数接受三个参数值,第一个是密钥,共享于两个通信的端点之间,所以两个端点都使用相同的值。第二个参数是初始化消息值。如果需要认证的消息内容非常小,例如时间戳或者 HTTP POST,那么整个消息体可以传入 new() 而不用 update() 方法。最后一个参数是要使用的摘要算法。默认的是 hashlib.md5,例子中使用的是 hashlib.sha1。

dcee20eeee9ef8a453453f510d9b6765921cf099

2.3.二进制摘要

前一个例子使用了 hexdigest() 方法生成可打印的摘要字符串。这个摘要字符串是由 digest() 方法生成的值的不同表现形式而已,这个值可能包括包括不可打印字符,包括 NUL。一些 web 服务(Google checkout, Amazon S3)使用了二进制摘要的 base64 版本而不是 hexdigest。

import base64
import hmac
import hashlib

with open('lorem.txt', 'rb') as f:
    body = f.read()

hash = hmac.new(
    b'secret-shared-key-goes-here',
    body,
    hashlib.sha1,
)

digest = hash.digest()
print(base64.encodestring(digest))

base64 编码的字符串以换行符结尾,当将字符串嵌入到 HTTP 头或者其他敏感的上下文中时,经常需要将这个换行符删除

b'olW2DoXHGJEKGU0aE9fOwSVE/o4=\n'

3.消息签名的应用

HMAC 算法应该用于任何公共网络服务,并且任何数据都应该存储在安全性很重要的地方。例如,当数据被通过管道或者 socket 发送的时候,数据应该被签名,然后在使用之前验签。此处给出的扩展示例位于文件 hmac_pickle.py。

第一步是创建一个函数计算一个字符串的摘要,以及一个简单的类,用于实例化并通过通信通道传递。

import hashlib
import hmac
import io
import pickle
import pprint

def make_digest(message):
    "Return a digest for the message."
    hash = hmac.new(
        b'secret-shared-key-goes-here',
        message,
        hashlib.sha1,
    )
    return hash.hexdigest().encode('utf-8')

class SimpleObject:
    """Demonstrate checking digests before unpickling.
    """

    def __init__(self, name):
        self.name = name

    def __str__(self):
        return self.name

接下来,创建一个 BytesIO 缓冲池代表一个 socket 或者管道。例子中使用了一个原生的,但是很容易解析的,格式化的数据流。首先数据的摘要和长度被写入,后面紧跟了一个换行符。对象的序列化形式由 pickle 生成。真实的系统可能不希望依赖于长度值,因为如果摘要错误,则长度也是错误的。某些不太可能出现在实际数据中的终结符序列可能更加合适。

然后示例程序中往数据流中写入了两个对象。第一个是使用正确的摘要值写入的。

# 使用缓冲区模拟可写套接字或者管道
out_s = io.BytesIO()

# 往流中写入一个有效的对象
#  digest\nlength\npickle
o = SimpleObject('digest matches')
pickled_data = pickle.dumps(o)
digest = make_digest(pickled_data)
header = b'%s %d\n' % (digest, len(pickled_data))
print('WRITING: {}'.format(header))
out_s.write(header)
out_s.write(pickled_data)

第二个写入流中的对象使用了一个无效的摘要值,摘要值是由其他一些数据生成的而不是序列化之后的值。

# 往流中写入一个无效的对象
o = SimpleObject('digest does not match')
pickled_data = pickle.dumps(o)
digest = make_digest(b'not the pickled data at all')
header = b'%s %d\n' % (digest, len(pickled_data))
print('\nWRITING: {}'.format(header))
out_s.write(header)
out_s.write(pickled_data)

out_s.flush()

记住数据是在 BytesIO 缓冲池中的,它可以再次被读出。首先读取带有摘要和数据长度的行。然后使用长度值读取剩下的数据。pickle.load() 可以直接从流中读取数据,但是它假设一个可信任的数据流,而且这个数据还不足够可信能够反序列化它。从流中读取序列化值为字符串,而不实际反序列化更安全

# 使用 BytesIO 模拟可读的 socket 或者管道
in_s = io.BytesIO(out_s.getvalue())

# 读取数据
while True:
    first_line = in_s.readline()
    if not first_line:
        break
    incoming_digest, incoming_length = first_line.split(b' ')
    incoming_length = int(incoming_length.decode('utf-8'))
    print('\nREAD:', incoming_digest, incoming_length)

一旦序列化数据被读取到内存,摘要值可以被重新计算,并且使用 compare_digest() 同传递过来的数据比较。如果摘要匹配,证明它是安全的去信任数据并且反序列化它。

    incoming_pickled_data = in_s.read(incoming_length)

    actual_digest = make_digest(incoming_pickled_data)
    print('ACTUAL:', actual_digest)

    if hmac.compare_digest(actual_digest, incoming_digest):
        obj = pickle.loads(incoming_pickled_data)
        print('OK:', obj)
    else:
        print('WARNING: Data corruption')

输出表示第一个对象被验证通过,第二个被视为「损坏」

python3 hmac_pickle.py

WRITING: b'f49cd2bf7922911129e8df37f76f95485a0b52ca 69\n'

WRITING: b'b01b209e28d7e053408ebe23b90fe5c33bc6a0ec 76\n'

READ: b'f49cd2bf7922911129e8df37f76f95485a0b52ca' 69
ACTUAL: b'f49cd2bf7922911129e8df37f76f95485a0b52ca'
OK: digest matches

READ: b'b01b209e28d7e053408ebe23b90fe5c33bc6a0ec' 76
ACTUAL: b'2ab061f9a9f749b8dd6f175bf57292e02e95c119'
WARNING: Data corruption

可以在定时攻击中使用简单的字符串或字节比较来比较两个摘要,以通过传递不同长度的摘要来暴露部分或全部秘密密钥。compare_digest() 实现了一个快速但是常量时间的比较函数去防止定时攻击。

在通联支付开放平台的API接口调用中,为了确保请求的安全性,可以采用HMAC加密方式进行签名验证HMAC(Hash-based Message Authentication Code)是一种利用哈希算法密钥进行消息认证的加密方法。以下是实现签名验证的详细步骤: 参考资源链接:[通联支付开放平台API调用及错误处理指南](https://wenku.csdn.net/doc/36joz8k3nc?spm=1055.2569.3001.10343) 1. 准备待签名的字符串:首先将所有请求参数按照参数名进行字典序排序(即按参数名的字母顺序排列),参数值之间用`&`连接,参数名与参数值之间用`=`连接,从而形成一个待签名的字符串。 2. 生成签名字符串:在待签名的字符串前拼接上`app_key`的值`&`字符,然后使用HMAC算法进行加密HMAC算法需要使用一个密钥,该密钥为`app_secret`(与`app_key`一起由AOP分配给应用),并选择一个合适的哈希函数,如SHA256。 3. 编码签名值:对HMAC加密后的二进制结果进行Base64编码,得到最终的签名值。 4. 发送请求:将Base64编码后的签名值作为`sign`参数添加到请求参数中,并通过HTTP请求发送至通联支付开放平台。 5. 服务器端验证:通联支付开放平台服务器收到请求后,将使用相同的`app_key``app_secret`,以及收到的其他请求参数,按照相同的步骤生成签名,并与请求中的签名进行比对,以此来验证请求的合法性。 使用HMAC加密方式进行签名验证,可以有效防止数据在传输过程中被篡改,并确保请求来源的合法性。需要注意的是,密钥`app_secret`不应暴露给客户端,应仅在服务器端使用,以保证整个验证过程的安全性。 通过《通联支付开放平台API调用及错误处理指南》中的详细说明,技术人员可以深入了解上述流程,并在实际操作中按照文档进行操作,确保支付请求的安全性完整性。 参考资源链接:[通联支付开放平台API调用及错误处理指南](https://wenku.csdn.net/doc/36joz8k3nc?spm=1055.2569.3001.10343)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值