3

目前,我有一些代码使用原生 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_string太大时,我可能会遇到结果问题(我认为是与外部进程的通信),这就是为什么我试图将其更改为仅 Python 的解决方案:

import hmac, hashlib
h = hmac.new(bytes(key_pem(), 'ASCII'), original_string, hashlib.sha256)
result = base64.encodestring(h).decode().replace('\n', '')

这导致与第一个完全不同的字符串。

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

4

1 回答 1

6

您使用的openssl命令做了三件事:

  • 使用 SHA256 创建数据的哈希
  • 如果使用 RSA,则使用 PKCS#1 1.5 将消息填充到特定长度
  • 使用您提供的私钥对(填充的)哈希进行签名。这取决于所使用的算法的密钥类型。

hmac模块不提供相同的功能。

你需要安装一个加密包,比如cryptography复制什么openssl dgst -signcryptography使用 OpenSSL 作为后端,因此它会产生相同的输出。

然后你可以

  • load_pem_private_key()用函数加载密钥。这将为所使用的算法返回正确类型的对象。
  • 使用密钥对消息进行签名;每个键类型都有一个sign()方法,如果你愿意,这个方法会为你处理散列消息。例如,参见RSA的签名部分。

    但是,您需要为不同的.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))

如果您需要散列大量数据,您可以先自己散列数据,分块,然后只传入摘要和特殊util.Prehashed()对象

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

    hasher = hashes.Hash(algorithm, default_backend())
    for chunk in data_iterable:
        hasher.update(chunk)
    digest = hasher.finalize()
    prehashed = utils.Prehashed(algorithm)

    return key.sign(digest, **_key_singing_config(key, prehashed))

with open(large_file, 'rb') as large_file:
    signature = sign_streaming(private_key_file, iter(lambda: large_file.read(2 ** 16), b''))

这使用该iter()函数以 64 KB 的块从二进制文件中读取数据。

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

$ echo -n 'Hello world!' | openssl dgst -sign /tmp/test_rsa.pem -sha256 | openssl base64
R1bRhzEr+ODNThyYiHbiUackZpx+TCviYR6qPlmiRGd28wpQJZGnOFg9tta0IwkT
HetvITcdggXeiqUqepzzT9rDkIw6CU7mlnDRcRu2g76TA4Uyq+0UzW8Ati8nYCSx
Wyu09YWaKazOQgIQW3no1e1Z4HKdN2LtZfRTvATk7JB9/nReKlXgRjVdwRdE3zl5
x3XSPlaMwnSsCVEhZ8N7Gf1xJf3huV21RKaXZw5zMypHGBIXG5ngyfX0+aznYEve
x1uBrtZQwUGuS7/RuHw67WDIN36aXAK1sRP5Q5CzgeMicD8d9wr8St1w7WtYLXzY
HwzvHWcVy7kPtfIzR4R0vQ==

或使用 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==
于 2018-03-05T18:36:50.643 回答