我在理解RSA签名和验证的概念时遇到了一个小问题。 问题是我可以创建完美的密钥对(公钥和私钥)。
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
random = SecureRandom.getInstance("SHA1PRNG");
keyGen.initialize(2048, random);
KeyPair pair = keyGen.generateKeyPair();
myPrivateKey = pair.getPrivate();
myPublicKey = pair.getPublic();
签名和验证如下:
//Singing with private key
Signature s = Signature.getInstance("SHA1withRSA");
s.initSign(javaPrivateKey);
//Verifying with public key
Signature s = Signature.getInstance("SHA1withRSA");
s.initVerify(javaPublicKey);
当我打印myPrivateKey和myPublicKey时,我看到公钥和私钥的模数(n)和公有指数(e)相同。
我已经将公钥和私钥转换为base64和hex,并且得到了完全不同的不同值。但是,我无法使用base64或hex签署消息。我只能用从中得到的签名:
myPrivateKey = pair.getPrivate();
我知道需要使用每个人都可以看到的公钥进行验证。接收者验证消息后,接收者是否仅使用模数和指数?发送者需要共享公共密钥的哪一部分?键的模数和指数还是Base64或十六进制值?
答案 0 :(得分:1)
是的,接收方仅使用模数和指数;从数学上讲,没有其他组件可以用来验证RSA的签名。
使用大数(在软件中实施RSA时通常使用BigInteger
值)执行数学运算。为了执行任何类型的计算,实现必须重新生成这些数字。这些数字的传输方式与算法无关。
使用基于PKCS#1的某种东西对通用RSA公钥进行编码,后者使用ASN.1(定义结构)和BER / DER(定义该结构的编码)指定公钥格式。当然,不同的协议可以使用公共密钥的不同编码。例如,PGP使用完全不同的“包格式”对密钥进行编码。
但是,Java返回X.509(证书和CRL)规范中定义的SubjectPublicKeyInfo结构;除了模数和指数外,还包含一个算法标识符,以表明它是是RSA公钥。因此,此结构还可用于分配其他类型的键值。可以通过在getEncoded()
实例上调用RSAPublicKey
来检索它-假定该实例与提供的Oracle实例兼容-通常情况下。 Android的实现当然应该与此结构兼容。请注意,SubjectPublicKeyInfo结构在其内部包含PKCS#1公钥结构 。
要进行反向操作,您需要一个KeyFactory.getInstance("RSA")
并使用X509EncodedKeySpec
派生密钥,并使用给定的字节数组进行初始化。
如果,您需要文本字符串而不是二进制字符串,则可以将getEncoded()
返回的字节转换为64位和十六进制。当然,在这种情况下,您需要先对结果进行编码(即解码),然后才能对字节本身进行解码。
也可以自己编码模数和公共指数。您可以使用RSAPublicKey.getModulus()
和RSAPublicKey.getPublicExponent()
来检索它们。要将它们反转回RSAPublicKey
,可以使用KeyFactory.getInstance("RSA")
和RSAPublicKeySpec
。这样,您可以例如只创建一个字符串"(<modulus>, <exp>)"
并使用它来分发密钥。通常,您通常希望遵守预定义的标准。
此答案未涵盖的事实是,要使用公共密钥进行验证,您首先需要在公共密钥中建立 trust 。如果您不能信任公共密钥,那么您将不知道是谁创建了公共密钥。在这种情况下,您也不能相信验证操作的结果;签名可能是用对手的密钥对创建的。不过,对于这个答案来说,深入研究公钥基础结构(PKI / PKIX)有点太多。
同样:SHA-1不再被认为是安全的,尤其是用于签名生成/验证。您可能希望至少使用SHA256withRSA
,或者使用稍微更高级,更安全的使用PSS的RSA方案。 2048位太小,无法舒适使用;如果方案允许,建议使用4096位密钥。