Требуются открытые / закрытые ключи RSA для шифрования в Java и расшифровки в Python - PullRequest
0 голосов
/ 10 апреля 2020

У нас есть одна система, написанная на Java, которая будет записывать зашифрованные файлы, которые должны быть расшифрованы системой Python. Я пытаюсь выяснить, какие ключи мне нужны, чтобы они могли использоваться как Java, так и Python API, и как их генерировать. Планируется использовать ключ publi c в Java для шифрования файла и закрытый ключ в Python для его расшифровки.

Я попытался сгенерировать ключи RSA с gpg --generate-key и в файл брони получить файл, который выглядит как:

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

и создать ключ publi c из того, который выглядит следующим образом:

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

Я могу разобрать ключ publi c файл с Bouncy Castle в Java с PGPUtil.getDecoderStream(), получая PGPPublicKeyRingCollection и PGPPublicKey, которые можно преобразовать в java.security.PublicKey.

На стороне Python я пробовал использовать оба API cryptography.hazmat и PyCrypto, но не могут понять, как импортировать файл закрытого ключа. Когда я пытаюсь

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

И затем прочитав это через API Bouncy Castle, например:

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 класса, я пробовал их оба, но получил одинаковый результат.

Не похоже, что это должно быть так сложно, поэтому я пытаюсь выяснить, чего мне не хватает. Спасибо за любую помощь.

Ответы [ 2 ]

0 голосов
/ 15 апреля 2020

В итоге я понял это, хотел задокументировать это для всех, кто сталкивался с той же проблемой.

Для начала, как упомянул Президент, ключи PGP не всегда поддерживаются в программных c крипто API и так что, вероятно, не лучший выбор. Наиболее широко используемыми являются ключи RSA, например, написанные OpenSSL, эта статья дает хорошее объяснение.

После того, как у вас есть ключи, вам нужно выяснить, какой API-интерфейс для используйте в Java и Python. Как отмечалось выше, можно просто загрузить ключ с помощью простых Java API. На стороне Python находится cryptography, который кажется относительно низким уровнем, PyCrypto, который является более высоким уровнем, но устарел с 2014 года, и PyCryptodome, который является форком PyCrypto, который более актуален. Для своего решения я выбрал PyCryptodome.

. Тогда важно понимать, что алгоритм, то есть RSA, является лишь одним из многих факторов для шифрования, есть также алгоритм ha sh, padding и т. Д. 1031 *. Вот выдержка из java do c на 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 мне понадобится, в моем случае модуль PKCS1_v1_5 в PyCryptodome.

Это привело к этому решению Python, которое я перефразировал, чтобы опустить некоторые детали, указанные c для моего случая, но должно дать вам достаточно для разработки вашего собственного решения.

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]
0 голосов
/ 10 апреля 2020

К сожалению, в программном обеспечении криптографии с ключом c существует много разных форматов для одной и той же основы c. Большинство пакетов пытаются поддерживать самые популярные из них. Одним из наиболее распространенных форматов для ключей publi c является формат SubjectPublicKeyInfo или SPKI . Как определено в RF C 5280, формат представляет собой кодирование DER структуры SubjectPublicKeyInfo Asn.1, двоичное кодирование, которое неудобно для некоторых приложений. Используя кодирование base64 и перенос строк верхнего и нижнего колонтитула, мы получаем так называемое кодирование "PEM" publi c keys . Именно этот формат производит Pycryptodome. Это может быть относительно легко обработано в Java с помощью провайдера Bouncycastle (B C) и библиотек pkix. Только немного сложнее справиться с этим только с помощью стандартных Java SE классов.

Вот несколько фрагментов кода, показывающих, как анализировать данные PEM SPKI для получения объектов-ключей publi c в Java.

Использование Java SE, B C prov и B C 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);
...