在Python中使用SHA256为字节字符串签名

在Python中使用SHA256为字节字符串签名,python,encryption,cryptography,sha256,python-cryptography,Python,Encryption,Cryptography,Sha256,Python Cryptography,目前,我有一些代码使用本机OpenSSL二进制文件使用SHA256算法对字节字符串进行签名,该代码调用外部进程,发送参数,并将结果接收回Python代码 目前的代码如下: signed_digest_proc = subprocess.Popen( ['openssl', 'dgst', '-sha256', '-sign', tmp_path], stdin=subprocess.PIPE, stdout=subprocess.PIPE ) signed_digest_

目前,我有一些代码使用本机OpenSSL二进制文件使用SHA256算法对字节字符串进行签名,该代码调用外部进程,发送参数,并将结果接收回Python代码

目前的代码如下:

signed_digest_proc = subprocess.Popen(
    ['openssl', 'dgst', '-sha256', '-sign', tmp_path],
    stdin=subprocess.PIPE,
    stdout=subprocess.PIPE
)
signed_digest_proc.stdin.write(original_string)
signed_digest, _ = signed_digest_proc.communicate()

base64.encodestring(signed_digest).decode().replace('\n', '')
original\u string
太大时,我可能会对结果产生问题(我认为是由于与外部进程的通信),这就是为什么我尝试将其更改为仅使用Python的解决方案:

import hmac, hashlib
h = hmac.new(bytes(key_pem(), 'ASCII'), original_string, hashlib.sha256)
result = base64.encodestring(h).decode().replace('\n', '')
这将导致与第一个字符串完全不同的字符串


在不调用外部进程的情况下实现原始代码的方法是什么?

您使用的
openssl
命令有三个功能:

  • 使用SHA256创建数据的散列
  • 如果使用RSA,请使用PKCS#1.5将消息填充到特定长度
  • 使用您提供的私钥对(填充的)哈希进行签名。这将取决于密钥的类型以及使用的算法
hmac
模块不具有相同的功能

您需要安装一个加密软件包,就像复制
openssldgst-sign
所做的一样<代码>加密使用OpenSSL作为后端,因此它将产生相同的输出

那么你可以

  • 将密钥加载到。这将返回所用算法的正确对象类型
  • 使用密钥对消息进行签名;每个键类型都有一个
    sign()
    方法,如果您愿意,此方法将负责对消息进行哈希处理。请参见示例

    但是,您需要为不同的
    .sign()
    方法提供不同类型的配置。只有RSA、DSA和椭圆曲线密钥才能用于创建签名摘要

您必须在类型之间切换才能获得正确的签名:

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa, utils
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding

# configuration per key type, each lambda takes a hashing algorithm
_signing_configs = (
    (dsa.DSAPrivateKey, lambda h: {
        'algorithm': h}),
    (ec.EllipticCurvePrivateKey, lambda h: {
        'signature_algorithm': ec.ECDSA(h)}),
    (rsa.RSAPrivateKey, lambda h: {
        'padding': padding.PKCS1v15(),
        'algorithm': h
    }),
)

def _key_singing_config(key, hashing_algorithm):
    try:
        factory = next(
            config
            for type_, config in _signing_configs
            if isinstance(key, type_)
        )
    except StopIteration:
        raise ValueError('Unsupported key type {!r}'.format(type(key)))
    return factory(hashing_algorithm)

def sign(private_key, data, algorithm=hashes.SHA256()):
    with open(private_key, 'rb') as private_key:
        key = serialization.load_pem_private_key(
            private_key.read(), None, default_backend())

    return key.sign(data, **_key_singing_config(key, algorithm))
如果需要散列大量数据,您可以先将数据散列成块,然后只传递摘要和特殊信息:

这将使用从二进制文件中读取64KB的数据块

演示;我使用的是在/tmp/test_RSA.pem中生成的RSA密钥。使用命令行为Hello world!生成签名摘要:

或者使用Python代码:

>>> signature = sign(keyfile, b'Hello world!')
>>> import base64
>>> print(base64.encodebytes(signature).decode())
R1bRhzEr+ODNThyYiHbiUackZpx+TCviYR6qPlmiRGd28wpQJZGnOFg9tta0IwkTHetvITcdggXe
iqUqepzzT9rDkIw6CU7mlnDRcRu2g76TA4Uyq+0UzW8Ati8nYCSxWyu09YWaKazOQgIQW3no1e1Z
4HKdN2LtZfRTvATk7JB9/nReKlXgRjVdwRdE3zl5x3XSPlaMwnSsCVEhZ8N7Gf1xJf3huV21RKaX
Zw5zMypHGBIXG5ngyfX0+aznYEvex1uBrtZQwUGuS7/RuHw67WDIN36aXAK1sRP5Q5CzgeMicD8d
9wr8St1w7WtYLXzYHwzvHWcVy7kPtfIzR4R0vQ==
虽然行长度不同,但两个输出的base64数据显然是相同的

或者,使用随机二进制数据生成的文件,大小为32kb:

$ dd if=/dev/urandom of=/tmp/random_data.bin bs=16k count=2
2+0 records in
2+0 records out
32768 bytes transferred in 0.002227 secs (14713516 bytes/sec)
$ cat /tmp/random_data.bin | openssl dgst -sign /tmp/test_rsa.pem -sha256 | openssl base64
b9sYFdRzpBtJTan7Pnfod0QRon+YfdaQlyhW0aWabia28oTFYKKiC2ksiJq+IhrF
tIMb0Ti60TtBhbdmR3eF5tfRqOfBNHGAzZxSaRMau6BuPf5AWqCIyh8GvqNKpweF
yyzWNaTBYATTt0RF0fkVioE6Q2LdfrOP1q+6zzRvLv4BHC0oW4qg6F6CMPSQqpBy
dU/3P8drJ8XCWiJV/oLhVehPtFeihatMzcZB3IIIDFP6rN0lY1KpFfdBPlXqZlJw
PJQondRBygk3fh+Sd/pGYzjltv7/4mC6CXTKlDQnYUWV+Rqpn6+ojTElGJZXCnn7
Sn0Oh3FidCxIeO/VIhgiuQ==
在Python中处理相同的文件:

>>> with open('/tmp/random_data.bin', 'rb') as random_data:
...     signature = sign_streaming('/tmp/test_rsa.pem', iter(lambda: random_data.read(2 ** 16), b''))
...
>>> print(base64.encodebytes(signature).decode())
b9sYFdRzpBtJTan7Pnfod0QRon+YfdaQlyhW0aWabia28oTFYKKiC2ksiJq+IhrFtIMb0Ti60TtB
hbdmR3eF5tfRqOfBNHGAzZxSaRMau6BuPf5AWqCIyh8GvqNKpweFyyzWNaTBYATTt0RF0fkVioE6
Q2LdfrOP1q+6zzRvLv4BHC0oW4qg6F6CMPSQqpBydU/3P8drJ8XCWiJV/oLhVehPtFeihatMzcZB
3IIIDFP6rN0lY1KpFfdBPlXqZlJwPJQondRBygk3fh+Sd/pGYzjltv7/4mC6CXTKlDQnYUWV+Rqp
n6+ojTElGJZXCnn7Sn0Oh3FidCxIeO/VIhgiuQ==

您使用
openssl
做了两件事,这两件事都没有使用HMAC签名。您正在生成一个常规的SHA256摘要,然后还要对摘要进行签名。要签名,请查看
加密
包,并根据您拥有的私钥类型(DSA、RSA等)选择一种算法。然后阅读签名部分。例如,对于RSA有。谢谢,您的示例代码非常清楚,现在我得到了与原始OpenSSL实现相同的结果。
$ dd if=/dev/urandom of=/tmp/random_data.bin bs=16k count=2
2+0 records in
2+0 records out
32768 bytes transferred in 0.002227 secs (14713516 bytes/sec)
$ cat /tmp/random_data.bin | openssl dgst -sign /tmp/test_rsa.pem -sha256 | openssl base64
b9sYFdRzpBtJTan7Pnfod0QRon+YfdaQlyhW0aWabia28oTFYKKiC2ksiJq+IhrF
tIMb0Ti60TtBhbdmR3eF5tfRqOfBNHGAzZxSaRMau6BuPf5AWqCIyh8GvqNKpweF
yyzWNaTBYATTt0RF0fkVioE6Q2LdfrOP1q+6zzRvLv4BHC0oW4qg6F6CMPSQqpBy
dU/3P8drJ8XCWiJV/oLhVehPtFeihatMzcZB3IIIDFP6rN0lY1KpFfdBPlXqZlJw
PJQondRBygk3fh+Sd/pGYzjltv7/4mC6CXTKlDQnYUWV+Rqpn6+ojTElGJZXCnn7
Sn0Oh3FidCxIeO/VIhgiuQ==
>>> with open('/tmp/random_data.bin', 'rb') as random_data:
...     signature = sign_streaming('/tmp/test_rsa.pem', iter(lambda: random_data.read(2 ** 16), b''))
...
>>> print(base64.encodebytes(signature).decode())
b9sYFdRzpBtJTan7Pnfod0QRon+YfdaQlyhW0aWabia28oTFYKKiC2ksiJq+IhrFtIMb0Ti60TtB
hbdmR3eF5tfRqOfBNHGAzZxSaRMau6BuPf5AWqCIyh8GvqNKpweFyyzWNaTBYATTt0RF0fkVioE6
Q2LdfrOP1q+6zzRvLv4BHC0oW4qg6F6CMPSQqpBydU/3P8drJ8XCWiJV/oLhVehPtFeihatMzcZB
3IIIDFP6rN0lY1KpFfdBPlXqZlJwPJQondRBygk3fh+Sd/pGYzjltv7/4mC6CXTKlDQnYUWV+Rqp
n6+ojTElGJZXCnn7Sn0Oh3FidCxIeO/VIhgiuQ==