python - 如何将旧的 python 代码从 m2crypto 迁移到密码学(摘要的 RSA 签名)

标签 python cryptography m2crypto

嗨,我有旧的遗留代码(python2),它为摘要创建签名文件。 (在我的例子中,这个摘要始终是一个 40 个字符的字符串,只有 0-9a-f 字符)。

它使用 m2crypto

我还有一个函数,用于验证摘要和签名文件是否匹配。

现在我必须使用密码学而不是 m2crypto 重新实现签名代码(并稍后切换到 python3) 然而,生成的签名必须可以使用旧的 m2crypto 代码进行验证。 (进行验证的代码在机器上运行。 我现在无法更新,但是签名必须迁移到密码学(和 python3)

附上我有一个完整的 self 解释示例。 我猜问题出在填充中的某个地方,它与 m2crypto 和加密解决方案不同。但我不知道如何继续。

旧的签名代码是:

def old_sign(digest):
    import M2Crypto
    rsa = M2Crypto.RSA.load_key("k.key")
    return rsa.sign(digest, "sha1")

验证签名的代码是:

def old_check_signature(digest, signature):
    import M2Crypto
    rsa = M2Crypto.RSA.load_pub_key("k.pub")
    try:
        rsa.verify(digest, signature, algo="sha1")
        return True
    except M2Crypto.RSA.RSAError as exc:
        args = exc.args
        if len(args) < 1:
            raise
        return False

我不知道 m2crypto 使用了哪种填充,也不知道填充的所有可能变体。

我尝试了两种不同的加密填充

第一次尝试:

def new_sign1(digest):
    from cryptography.hazmat.backends import default_backend
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import padding
    from cryptography.hazmat.primitives.asymmetric import rsa

    key_data = open("k.key").read()
    key =  serialization.load_pem_private_key(
        key_data, password=None, backend=default_backend())
    return key.sign(
        digest,
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA1()),
            salt_length=padding.PSS.MAX_LENGTH,
            ),
        hashes.SHA1())

第二次尝试

def new_sign2(digest):
    from cryptography.hazmat.backends import default_backend
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import padding
    from cryptography.hazmat.primitives.asymmetric import rsa

    key_data = open("k.key").read()
    key =  serialization.load_pem_private_key(
        key_data, password=None, backend=default_backend())

    return key.sign(digest, padding.PKCS1v15(), hashes.SHA1())


根据保罗的反馈我也尝试了

def new_sign3(digest):
    from cryptography.hazmat.backends import default_backend
    from cryptography.hazmat.primitives import hashes, serialization
    from cryptography.hazmat.primitives.asymmetric import padding
    from cryptography.hazmat.primitives.asymmetric import rsa
    from cryptography.hazmat.primitives.asymmetric import utils

    key_data = open("k.key", "rb").read()
    key =  serialization.load_pem_private_key(
        key_data, password=None, backend=default_backend())

    return key.sign(
        digest,
        padding.PKCS1v15(),
         utils.Prehashed(hashes.SHA1()),
        )

失败的原因是 ValueError:提供的数据长度必须与哈希算法的摘要大小相同。

如果声明了上述所有函数,以下代码可以重现错误:

dig = "f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0"
sigs = [old_sign(dig), new_sign1(dig), new_sign2(dig)]
print([old_check_signature(dig, sig) for sig in sigs])

输出为[True, False, False],这意味着只有使用 m2crypto 创建的签名才是正确的,使用密码学创建的签名无法使用 m2crypto 进行验证。

如果您想测试代码并且没有 key 文件和公钥文件,您可以使用以下代码片段来创建一个:

KEY = """
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAusZYCLS8RPUJ3fhod3cr2foK++t3R30Eiqstq+B5vIIrcJN1
2HnKfU1o3m9cwEJOezHxuI7Ks8/YVt/bGy6HyY4i3bk5AS9BK4gEptmUlldsvV+l
hOLel884dwhUk2ZrEpCq7YvSpMBb7ORKcUwcTu9tyuSHTlVpabYADTStebDWwp0+
heeRWN/zG5CN0zO4BScRNTpnw/TAqBTytuWgo/YFhBX6a4U3fKKASqynn5ZJYAg3
CDqdAe4BM2OZUv73UHWQsltC3xyKqsN6+gH7O7WoetxdMZsvYtEp4hvhUjgCrDSU
gSvtZD8BMxcPPO59w670DdVa3EPLWZzhGel18wIDAQABAoIBAB+DrAL8C/BOsDWF
3oqZzwpeiE/tcRjc3VFQhMpFfAT0qcO6/d1i32m5EALII4xFI9zhlnmfjlA8t7Ig
32V8umil1Pg4cofio0pnDvHgMJQVeEGTy+faJ9jRnCNpgmvEkjh1tIGUYBxwYJJe
CrmHMBeZipr7aGEtRDYUAXo48zRe+mO41ICkPeVRRSK0b5F9P7SOm+QnkN8rHmtm
MsCJ0jPJ1fG5Xi5BVVdjhQ6+S42tbm/A68Yx/uWSC3pt+ScIklgYF2owawVWDbWJ
Eekl0sNUTm1j1OlnyAEbb8R/VkYsLYfEwpgULTzkokBRq4NZxjCKY7VJRZ05neev
4JbdcOECgYEA46e+L/uvHUUzyhWgrNgHAdT7Eff4Imu7WdG+KQ7d7kNazMJFyMrk
1Ln66i/oPyiMwMXcACmEoA5Jl/wBk3sjKjLno9dvMM3amfwkBZav4FirQS+9Xk/l
EQAoc/WxsxlVywM00c20CgGqNu0hRBlM5C8zCoIFlt6G2Mwv58yd8hkCgYEA0geU
7FXh/xVnq4wU9bnTF0laPd4HlVYYNpRMjTdRwwIycrCynAlfEF+keUyOXhgYw5vm
loyICCqsNi5pw8tRPFyzBiESkLUL+ZTcfxswfgmhlTs71Zk72b8LCM3sRPeuUg2a
6GrNvycWRLfc+toq5NvSKnfVQS2tgMKJ0CtPIesCgYB+Za0H8SKaCsklY3qxXMQP
NVQs9tOTMON1jCmbnECGQGlSlG6wfE4u+g+hJPY60uXLRk/O2z5iq2wa8XVikBTH
IjpQUpXOsAy2QDMz0yVVV4XGDJ6ElbFmDgNn1rtR6DglHmOeNSrH/4KlOmWk7LMv
YjFhnS1DRcvy5POYLJhpSQKBgEILIEkwuF/92xuWcQDT7gzkg/vwVXIgIH0JJQlC
2/L2PebSqVdnmv0LFi0OZbYw3Zik7V1p01y+Dmj7L0biKClS/PhwbeYTCDDzHmLZ
qeX4IVdLyQThqnBOIqoiFqmZOLeUj6GF9Cynndj99/7pm5NbjDrOc8CLHIPgqHVN
KRUBAoGBAKEr1mg+Hjf+M/5ru1tQT6xTvlKW1rS4ioGB35XQf+n/OAO98SPQkwPQ
XfX5nKHif/TmDZygGeHYm96qCceOmzCL7LqaYOb1qTZLEk6L18eEtHwG+hXyMKuA
t2XLK89blAoPT+9x10KKp27IWe/W+QCD6LnrdjoN9BQ7fCnV5v3Q
-----END RSA PRIVATE KEY-----
"""

open("k.key", "w").write(KEY)


PUB_KEY = """
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAusZYCLS8RPUJ3fhod3cr
2foK++t3R30Eiqstq+B5vIIrcJN12HnKfU1o3m9cwEJOezHxuI7Ks8/YVt/bGy6H
yY4i3bk5AS9BK4gEptmUlldsvV+lhOLel884dwhUk2ZrEpCq7YvSpMBb7ORKcUwc
Tu9tyuSHTlVpabYADTStebDWwp0+heeRWN/zG5CN0zO4BScRNTpnw/TAqBTytuWg
o/YFhBX6a4U3fKKASqynn5ZJYAg3CDqdAe4BM2OZUv73UHWQsltC3xyKqsN6+gH7
O7WoetxdMZsvYtEp4hvhUjgCrDSUgSvtZD8BMxcPPO59w670DdVa3EPLWZzhGel1
8wIDAQAB
-----END PUBLIC KEY-----
"""
open("k.pub", "w").write(PUB_KEY)


预先感谢您的任何建议。

我不经常使用SO, 我还有一个完整的 python 文件(102 行),它是完全独立的。如何在问题中在 SO 上共享此类文件

附录 2019-06-13 23:15:00 UTC

看来。 M2Crypto.RSA.sign()

最终调用以下 C 代码。 (来自文件 SWIG/_m2crypto_wrap.c 中的 m2crypto git 存储库。

我不太擅长 libssl,但也许其他人理解这一点 并可以告诉我如何在不使用 M2Crypto 的情况下“重现计算”(可能使用 openssl 命令行或 ctypes)。

PyObject *rsa_sign(RSA *rsa, PyObject *py_digest_string, int method_type) {
    int digest_len = 0;
    int buf_len = 0;
    int ret = 0;
    unsigned int real_buf_len = 0;
    char *digest_string = NULL;
    unsigned char * sign_buf = NULL;
    PyObject *signature;

    ret = m2_PyString_AsStringAndSizeInt(py_digest_string, &digest_string,
                                         &digest_len);
    if (ret == -1) {
        /* PyString_AsStringAndSize raises the correct exceptions. */
        return NULL;
    }

    buf_len = RSA_size(rsa);
    sign_buf = (unsigned char *)PyMem_Malloc(buf_len);
    ret = RSA_sign(method_type, (const unsigned char *)digest_string, digest_len,
                   sign_buf, &real_buf_len, rsa);

    if (!ret) {
        m2_PyErr_Msg(_rsa_err);
        PyMem_Free(sign_buf);
        return NULL;
    }

    signature =  PyBytes_FromStringAndSize((const char*) sign_buf, buf_len);

    PyMem_Free(sign_buf);
    return signature;
}

使用 subprocess.Popen 或 ctypes(直接攻击 libssl)的解决方法(在我的上下文中)是可以接受的。

最佳答案

M2Crypto 在调用 sign可能使用 PKCS1 填充,因此 PKCS1v15 示例接近您想要的。假设它默认为 PKCS1(而不是没有填充,这是非常不安全的,并且 加密 不支持),那么摘要可能就是问题所在。

digest 是否已经是您想要签名的哈希值,还是要哈希的数据?默认情况下,密码学会为您散列所提供的数据。如果数据已经被散列,那么您需要使用 Prehashed以避免这一步。

关于python - 如何将旧的 python 代码从 m2crypto 迁移到密码学(摘要的 RSA 签名),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56567695/

相关文章:

python - Pytables NumExpr 值错误 : too many inputs when querying with a lot of conditions

java - 使用加密文件并为它们提供 FileProvider

cryptography - 消息中的内部散列摘要是否加强了外部摘要?

python - 从 PKCS7 (signature&data) 中提取分离的 PKCS7 签名

Fedora 26 中 Python M2Crypto 安装失败

python - 在 M2Crypto 中访问 CSR 扩展堆栈

python - 我无法从浏览器访问scrapyd端口6800

python - 尝试超出顶级包的相对导入?

Python解析字符串中括号内的整数

python-3.x - Lenstras 椭圆曲线属性错误问题