我们有一个用Java编写的系统,它将编写需要由Python系统解密的加密文件。我试图弄清楚Java和Python API都可以使用哪种密钥,以及如何生成它们。计划是使用Java中的公钥来加密文件,使用Python中的私钥来解密文件
我尝试用gpg --generate-key
生成RSA密钥,在armor文件中得到一个文件,看起来像:
-----BEGIN PGP PRIVATE KEY BLOCK-----
... encoded key ...
-----END PGP PRIVATE KEY BLOCK-----
并创建一个公钥,看起来像:
-----BEGIN PGP PUBLIC KEY BLOCK-----
... encoded key ...
-----END PGP PUBLIC KEY BLOCK-----
我可以用PGPUtil.getDecoderStream()
用Java中的Bouncy Castle解析公钥文件,得到一个PGPPublicKeyRingCollection
和一个可以转换为java.security.PublicKey
的PGPPublicKey
。
在Python方面,我尝试过同时使用cryptography.hazmat
和PyCrypto
api,但不知道如何导入私钥文件。当我尝试时
from Crypto.PublicKey import RSA
RSA.importKey(open('/path/to/private/key/file').read())
我得到RSA key format is not supported
。
我一直在阅读不同类型的密钥和算法,但我认为包含这样一个密钥的ASCII文件应该可以工作,但显然我缺少了一些东西。
我还尝试了另一种方法,使用PyCrypto
生成一个新密钥,类似于:
from Crypto.PublicKey import RSA
key = RSA.generate(2048)
f = open('/tmp/private.pem','wb')
f.write(key.exportKey('PEM'))
f.close()
f = open('/tmp/public.pem','wb')
f.write(key.publickey().exportKey('PEM'))
f.close
然后通过Bouncy Castle的API这样阅读:
PemReader reader = new PemReader(new FileReader("/tmp/public.pem"));
Object publicKey = RSAPublicKey.getInstance(reader.readPemObject().getContent());
但这给了我:
java.lang.IllegalArgumentException: illegal object in getInstance: org.bouncycastle.asn1.DLSequence
at org.bouncycastle.asn1.ASN1Integer.getInstance(Unknown Source)
at org.bouncycastle.asn1.pkcs.RSAPublicKey.<init>(Unknown Source)
Bouncy Castle提供了两个RSAPublicKey
类,我尝试了它们,但得到了相同的结果。
这似乎不应该这么难,所以我正在努力弄清楚我错过了什么。谢谢你的帮助。
我最终弄清楚了这一点,想为遇到同样问题的人记录下来。
首先,正如总统所提到的,PGP密钥在编程加密API中并没有得到普遍支持,因此可能不是一个很好的选择。最广泛使用的似乎是RSA密钥,比如OpenSSL编写的密钥,本文对此进行了很好的解释。
在那里,一旦你有了密钥,你就需要弄清楚在Java和Python中使用哪种API。如上所述,可以简单地用普通的Java API加载一个密钥。在Python方面,有cryptography
,它似乎是相对较低的级别,PyCrypto
,它是较高级别的,但自2014年以来就过时了,PyCryptodome
是PyCrypto
的一个分支,它是最新的。对于我的解决方案,我选择了PyCryptodome
。
然后,重要的是要认识到,算法(即RSA(只是加密的众多因素之一,还有哈希算法、填充等。以下是com.sun.crypto.provider.RSACipher
上的java文档摘录:
/**
* RSA cipher implementation. Supports RSA en/decryption and signing/verifying
* using both PKCS#1 v1.5 and OAEP (v2.2) paddings and without padding (raw RSA).
* Note that raw RSA is supported mostly for completeness and should only be
* used in rare cases.
*
* Objects should be instantiated by calling Cipher.getInstance() using the
* following algorithm names:
* . "RSA/ECB/PKCS1Padding" (or "RSA") for PKCS#1 v1.5 padding.
* . "RSA/ECB/OAEPwith<hash>andMGF1Padding" (or "RSA/ECB/OAEPPadding") for
* PKCS#1 v2.2 padding.
* . "RSA/ECB/NoPadding" for rsa RSA.
* ...
在我的例子中,我使用的Java工具包是用Cipher.getInstance("RSA")
(YMMV(创建密码,基于此和上面的注释,我知道我需要哪个Python模块,在我的情况下是PyCryptodome
中的PKCS1_v1_5
模块。
这就产生了这个Python解决方案,我对它进行了解释,省略了一些特定于我的案例的细节,但应该会给你足够的时间来开发自己的解决方案。
import base64
from Crypto.PublicKey import RSA
from Crypto.Cipher import AES, PKCS1_v1_5
# The public key is not needed for this POC but this demonstrates how to load it
pub_key = RSA.importKey(open('openssl-public.pem').read())
priv_key = RSA.importKey(open('openssl-private.pem').read())
# The public key extracted from the private key should match the imported public key,
# could implement that as a double check
# priv_key.publickey().export_key()
# Need to use the PKCS1_v1_5 module to match "PKCS#1 v1.5" in the Java RSA class
cipher_rsa = PKCS1_v1_5.new(priv_key)
meta = # get the content key x-amz-key, IV x-amz-iv and the unencrypted content length x-amz-unencrypted-content-length
# Base64 decode the iv and key
iv = base64.b64decode(meta['x-amz-iv'])
key = base64.b64decode(meta['x-amz-key'])
# Decrypt the key
decrypted_key = cipher_rsa.decrypt(key, 'An error has occurred')
# Create an AES cipher using the content key and IV. This must match
# how the data was encoded
cipher_aes = AES.new(decrypted_key, AES.MODE_CBC, iv)
encryptedFile = # get the encrypted file
# Need to read the encrypted file as binary 'rb'
# The decrypted file may be padded
length = meta['x-amz-unencrypted-content-length']
decryptedContent = cipher_aes.decrypt(open(encryptedFile,mode='rb').read())[:length]
不幸的是,在公钥加密软件中,对于相同的基本内容有许多不同的格式。大多数软件包都试图支持最流行的软件包。公钥的一种更普遍的格式是SubjectPublicKeyInfo
或SPKI格式。如RFC 5280中所定义的,该格式是SubjectPublicKeyInfo
Asn.1结构的DER编码,这是一种二进制编码,对于某些应用来说是不方便的。通过采用base64编码并用页眉和页脚行进行包装;PEM";公钥的编码。Pycryptodome产生的就是这种格式。通过使用Bouncycastle(BC(提供程序和pkix库,在Java中可以相对容易地处理它。只使用标准JavaSE类处理它只会稍微困难一些。
以下是一些代码片段,展示了如何解析PEM SPKI数据以在Java中生成公钥对象。
使用Java SE、BC prov和BC pkix:
File pemPubFile = new File("/tmp/public.pem");
PEMParser pemParser = new PEMParser(new FileReader(pemPubFile));
SubjectPublicKeyInfo spki = (SubjectPublicKeyInfo) pemParser.readObject();
PublicKey publicKey = new JcaPEMKeyConverter().getPublicKey(spki);
System.out.println(publicKey);
仅使用Java SE:
List < String > pemLines = Files.readAllLines(pemPubFile.toPath());
String b64Data = pemLines.stream().reduce("", new BinaryOperator < String > () {@Override
public String apply(String accum, String element) {
return accum.concat(element.trim());
}
});
// Delete the -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY-----
b64Data = b64Data.replace("-----BEGIN PUBLIC KEY-----", "");
b64Data = b64Data.replace("-----END PUBLIC KEY-----", "");
byte[] der = Base64.getDecoder().decode(b64Data);
PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(der));
System.out.println(publicKey);