【问题标题】:exchange public/private key in PKCS#1 OAEP encryption/decryption在 PKCS#1 OAEP 加密/解密中交换公钥/私钥
【发布时间】:2012-06-13 14:34:36
【问题描述】:

我对RSA只有一些非常初级的理论知识。

在阅读有关如何在实践中使用它的不同来源时,PKCS#1 OAEP 似乎是一件好事。

对于测试实现,我使用 Python 和 PyCrypto。例如。 this 是使用 PKCS#1 OAEP 的示例。

使用公钥加密然后使用私钥解密可以正常工作。例如。公众可以使用私钥向 X 人发送一些数据。

根据我对 RSA 工作原理的基本了解,我认为我可以互换公钥/私钥,即我可以使用公钥进行加密,使用私钥进行解密。例如。 X 人可以用自己的私钥加密一些数据,公众可以使用公钥对其进行解密。如果解密工作正常,这可以证明数据来自 X 人。

当我尝试使用公钥解密时,PyCrypto 报错。

通过阅读 PyCrypto 源代码,在 _RSAKey._decrypt 函数 (here) 中,似乎密钥对象本身知道它是私钥还是公钥并且它们之间存在差异(令我惊讶的是,再次基于我对 RSA 的基本理解)。

从那里看来,我可以破解解密函数,以便它使用公钥。或者有些不同:我可以在关键对象中交换公共指数 e 和私有指数 d

但这一切似乎不打算以这种方式使用/黑客攻击。所以我想在这里问一下我的误解。

另外,出于好奇,我生成了一些密钥 (RSA.generate(2048)) 并查看了 ned。在所有情况下,nd 都非常大,而e 在所有情况下恒定 (65537)(我没想到会这样)。

从这一切看来,我真的不应该只是交换 ed

所以我想我应该使用其他方法进行签名,例如 PKCS1_PSS。


一些加密/解密代码,如果有人感兴趣的话:

def randomString(l):
    import random
    return ''.join(chr(random.randint(0, 0xFF)) for i in range(l))

def genkeypair():
    from Crypto.PublicKey import RSA
    key = RSA.generate(2048)
    pubkey = key.publickey().exportKey("DER")
    privkey = key.exportKey("DER")
    return (pubkey,privkey)

def encrypt(v, rsapubkey):
    from Crypto.PublicKey import RSA
    rsakey = RSA.importKey(rsapubkey)
    from Crypto.Cipher import PKCS1_OAEP
    rsa = PKCS1_OAEP.new(rsakey)
    import binstruct
    from array import array
    aeskey = randomString(32)
    iv = randomString(16)
    from Crypto.Cipher import AES
    aes = AES.new(aeskey, AES.MODE_CBC, iv)
    data = binstruct.varEncode(v)
    data += array("B", (0,) * (-len(data) % 16))
    out = binstruct.strEncode(rsa.encrypt(aeskey + iv))
    out += array("B", aes.encrypt(data))
    return out

def decrypt(stream, rsaprivkey):
    from array import array
    from StringIO import StringIO
    if isinstance(stream, array): stream = stream.tostring()
    if isinstance(stream, str): stream = StringIO(stream)
    from Crypto.PublicKey import RSA
    rsakey = RSA.importKey(rsaprivkey)
    from Crypto.Cipher import PKCS1_OAEP
    rsa = PKCS1_OAEP.new(rsakey)
    import binstruct
    aesdata = binstruct.strDecode(stream)
    aesdata = rsa.decrypt(aesdata)
    aeskey = aesdata[0:32]
    iv = aesdata[32:]
    from Crypto.Cipher import AES
    aes = AES.new(aeskey, AES.MODE_CBC, iv)
    class Stream:
        buffer = []
        def read1(self):
            if len(self.buffer) == 0:
                nextIn = stream.read(16)
                self.buffer += list(aes.decrypt(nextIn))
            return self.buffer.pop(0)
        def read(self, n):
            return "".join([self.read1() for i in range(n)])
    v = binstruct.varDecode(Stream())
    return v

binstruct 是一个可以编码/解码树数据结构的小模块 - 类似于 JSON/BSON。)

那是我认为我也可以将encrypt 与私钥一起使用,并将decrypt 与公钥一起使用。


可以在here in binstruct找到(希望)正确签名/身份验证的最终实现。

【问题讨论】:

  • “当我尝试使用公钥解密时 PyCrypto 报错”。请更具体,并请出示您的代码。
  • @GregS:它在_RSAKey._decrypt 中引发TypeError("No private key")。正如您从链接代码中看到的那样。这就是我所说的。看来解密时需要私钥。
  • 如果您只有基本的密码学知识,那么以不应该使用的方式使用 API 是行不通的。我有 10 年的经验,即使我以不应该的方式使用加密库时也非常小心 - 我尽量避免这样做。
  • @Albert ImportError: 没有名为 binstruct 的模块。你发的链接失效了。有没有替代链接?请分享。

标签: encryption cryptography rsa public-key-encryption


【解决方案1】:

您对交换公钥和私钥角色的一般理解是正确的。最后,RSA是基于这样的事实

m^(ed) congruent m (mod n)

通常称为 RSA 加密的通常是操作

m^e mod n,

将消息提升到 e 次方,其中 e 是公钥。

然后解密

(m^e)^d mod n,

将加密消息提升到 d 次方,其中 d 是私钥。现在由于求幂规则和乘法是可交换的事实(这些仍然在模算术中成立),我们有这个

m congruent (m^e)^d congruent m^(ed) congruent m^(de) congruent (m^d)^e,

因此,如果您以相反的顺序应用这些操作,您会得到相同的结果。

您假设反转导致数字签名是正确的,因为每个人都可以使用公钥 e 验证(“解密”)签名,因此只有使用对应的私钥d。

事实证明,PyCrypto 只是试图防止您在这里误认为另一个,OpenSSL 或 Ruby OpenSSL 允许您 for example 两者兼而有之:public_encrypt/public_decrypt 和 private_encrypt/private_decrypt。

理论讲了这么多,现在来看看为什么有充分的理由不让你互换使用它们。我刚才描述的通常被称为“教科书 RSA”,它仍然远非安全。需要注意其他事项以使结果在实践中可用。这就是为什么在 PyCrypto 中有一个专用的signature package - 这有效地完成了您所描述的事情,而且还处理了我提到的事情。虽然了解这些东西是如何工作的对我们的理解很有好处,但我们应该始终在实践中使用这些包,因为它们已经犯了并修复了我们在推出自己的包时可能会引入的错误。

至于为什么 e 总是 65537。它实际上不一定是一个固定值,但通常选择它是一个非常小的数字,其二进制表示中的 1 尽可能少(65537 是 10001)。在过去,也选择了 e=3 或 e=17,但在实践中被认为不安全,因为它们可以通过简单地获取密文的第 3 或第 17 根来进行攻击。如果 e=3 且 m=3,则 3^3 为 27,并且考虑到密文为 27,无论模数 n(通常大得多)如何,都不需要天才就能算出 m 为 3。所以危险在于密文,即使在取幂之后,也不会跨越“模边界”,因此允许我们简单地取 e-th 根来得到原始消息。对于 1024 - 4096 位的典型模数,这不再是 e=65537 的问题。

二进制表示中很少有 1 也有利于快速计算 m^e。模幂运算通常使用Multiply and Square 算法实现,对于带有几个 1 的小 e 来说性能最好。为什么选择这种方式而不是相反,例如有一个小的 d 和几个 1?对于初学者来说,d 会更容易猜到。第二个优点是,使用数字签名,您通常对文档进行一次签名,但经常对其进行验证。这意味着 m^d 只执行一次,但 m^e 经常执行,因此您可以让常见任务执行得最好,而允许稀有任务执行得较差。

编辑:

您问我是否可以进一步解释像 RSA-PSS 这样的方案为了安全而做了什么。

比较 OAEP 的加密功能和 PSS 的签名功能时,两者看起来非常相似。事实上它们是,它们都在过程中引入了随机化,这允许在某些假设下 OAEPPSS 的可证明安全性。我还发现这个paper 很有帮助。与老式 PKCS 1.5 加密和签名相比,可证明的安全性是一个很大的优势,在相同的假设下,它可以被证明是不可证明的安全性(关键点:没有确定性方案可以,随机化是必不可少的)。提议的签名和加密方案之间的一个明显区别是签名方案总是要求首先对要签名的消息进行哈希处理。这不仅在效率方面有意义,而且还可以防止一些原本可能发生的攻击。我想这导致了为什么我们应该始终使用签名方案进行签名和加密方案进行加密的要点:建议的方案附带安全证明,而我们的手工方案没有。

密码学家发明这些方案是为了让我们这些凡人的生活变得更轻松 - 它们为我们提供了理想的工具,通过将选项数量减少到最低限度来避免滥用或误用。例如,即使您设法使用 RSA-OAEP 提出了一个好的签名方案,使用它的人可能不知道为什么他们应该在应用签名之前先散列他们的消息。 RSA-PSS 甚至不可能发生这种滥用。

您还询问了一些好的阅读材料。虽然这是一个非常主观的话题,但我真的很喜欢这些:

实用方面:

  • Applied Cryptography - 仍然是经典,值得一读。一些安全人员说这很危险,因为它使人们相信他们知道的足够多,可以编写自己的加密货币。但我想我们都是成年人,不是吗?感觉“外面有什么”还是很棒的

  • Cryptography Engineering - 有一些很好的实用建议,还提到了实现加密代码时的注意事项。

  • Handbook of Applied Cryptography - 它是免费的,并且仍然有很多好的建议,尤其是在实现方面。

理论方面:

安全不仅仅是密码学:

除此之外,我尝试通过阅读有关新攻击、新技术等的最新论文来了解最新信息。我发现r/netsec 非常有帮助,并且在 Twitter 上关注研究人员和从业者,他们会定期发布有趣的材料。

最后,如果您有时间,请参加 Coursera 和 Udacity 上的密码学课程!我想他们会在接下来的几周内重新开始,他们真的很棒,我相信你不会后悔的。他们有很多有趣的实践练习,很好地说明了攻击密码学实现的各种方法。

【讨论】:

  • 谢谢,很有帮助! --“需要注意额外的事情[..]。[签名包]还需要注意我提到的事情”。我认为您没有明确提到这些事情,或者我没有得到它们。什么额外的东西?特别是,为什么使用 RSASSA-PSS 而不是 RSAES-OAEP(使用私钥加密)进行签名? -- 你能推荐一些关于如何从一些理论教科书 RSA 知识到实际使用的进一步阅读资源吗? (因为即使对于大多数库,我也大多只看到琐碎的例子。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-27
  • 1970-01-01
  • 2015-08-23
  • 2013-04-06
  • 1970-01-01
  • 2010-12-20
相关资源
最近更新 更多