需要 public/private 个 RSA 密钥以在 Java 中加密并在 Python 中解密

Need public/private RSA keys for encrypting in Java and decrypting in Python

我们有一个用 Java 编写的系统,它将写入需要由 Python 系统解密的加密文件。我试图弄清楚我需要什么样的密钥可以被 Java 和 Python API 使用,以及如何生成它们。计划是用Java中的public密钥加密文件,用Python中的私钥解密

我尝试使用 gpg --generate-key 生成 RSA 密钥,并在 armor 文件中得到一个如下所示的文件:

-----BEGIN PGP PRIVATE KEY BLOCK-----
... encoded key ...
-----END PGP PRIVATE KEY BLOCK-----

并从中创建一个 public 密钥,如下所示:

-----BEGIN PGP PUBLIC KEY BLOCK-----
... encoded key ...
-----END PGP PUBLIC KEY BLOCK-----

我可以用 Java 和 PGPUtil.getDecoderStream() 中的 Bouncy Castle 解析 public 密钥文件,得到一个 PGPPublicKeyRingCollection 和一个可以转换的 PGPPublicKey java.security.PublicKey.

在 Python 方面,我尝试同时使用 cryptography.hazmatPyCrypto 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类,我都试过了,结果一样。

它似乎不应该这么难所以我想弄清楚我错过了什么。感谢您的帮助。

不幸的是,在 public 密钥加密软件中,相同的基本内容有许多不同的格式。大多数软件包都试图支持最受欢迎的软件包。 public 键的一种更普遍的格式是 SubjectPublicKeyInfo or SPKI format. As defined in RFC 5280, the format is a the DER encoding of the SubjectPublicKeyInfo Asn.1 structure, a binary encoding which is inconvenient for some applications. By employing base64 encoding and wrapping with header and footer lines we arrive at the so-called "PEM" encoding of public keys。 Pycryptodome 就是这种格式。通过使用 Bouncycastle (BC) 提供程序和 pkix 库,可以在 Java 中相对轻松地处理它。只用标准的 Java SE 类.

来处理它只是有点困难

这里有一些代码片段展示了如何解析 PEM SPKI 数据以在 Java 中生成 public 个关键对象。

使用 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);

我最终解决了这个问题,想为任何 运行 遇到相同问题的人记录下来。

首先,正如总统提到的那样,PGP 密钥在编程加密 API 中并未得到普遍支持,因此可能不是一个很好的选择。最广泛使用的似乎是RSA密钥,例如OpenSSL编写的密钥,this文章给出了很好的解释。

从那里获得密钥后,您需要确定在 Java 和 Python 中使用哪些 API。如上所述,可以简单地加载带有普通 Java API 的密钥。在 Python 一侧有 cryptography 似乎是相对较低的级别,PyCrypto 级别较高但自 2014 年以来已经过时,PyCryptodome 是 [= 的分支13=] 这是最新的。对于我的解决方案,我选择了 PyCryptodome.

然后重要的是要认识到算法,即 RSA 只是加密的众多因素之一,还有哈希算法、填充等。这是 java 文档的摘录com.sun.crypto.provider.RSACipher:

/**
 * 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]