Как я могу заставить BouncyCastle расшифровать сообщение с зашифрованным GPG?Получение BouncyCastle для дешифрования зашифрованного GPG сообщения
Я создал пару ключей GPG в командной строке CentOS 7, используя gpg --gen-key
. Я выбрал RSA RSA как типы шифрования, и я экспортировал ключи с помощью gpg --export-secret-key -a "User Name" > /home/username/username_private.key
и gpg --armor --export 66677FC6 > /home/username/username_pubkey.asc
Я могу импортировать username_pubkey.asc
в удаленном клиенте Thunderbird другой учетной записи электронной почты и успешно отправить зашифрованное электронное письмо [email protected] Но когда мой Java/BouncyCastle код, работающий на mydomain.com пытается расшифровать данные GPG-закодирован, он дает следующее сообщение об ошибке:
org.bouncycastle.openpgp.PGPException:
Encrypted message contains a signed message - not literal data.
Если вы посмотрите на код ниже, вы увидите это соответствует линии в котором говорится, PGPUtils.decryptFile()
else if (message instanceof PGPOnePassSignatureList) {throw new PGPException("Encrypted message contains a signed message - not literal data.");
Исходный код для этого пришел из the blog entry at this link, хотя я сделал небольшие изменения, чтобы получить его для компиляции в Eclipse, Luna с Java 7. пользователь, связанного блога сообщили ту же ошибку, и автор блога ответил, сказав, что он не работает с GPG. Итак, как мне исправить это, чтобы он работал с GPG?
код дешифровки Java начинается, когда GPG-кодированной-файл и секретный ключ GPG передаются в Tester.testDecrypt()
следующим образом:
Tester.java содержит:
public InputStream testDecrypt(String input, String output, String passphrase, String skeyfile) throws Exception {
PGPFileProcessor p = new PGPFileProcessor();
p.setInputFileName(input);//this is GPG-encoded data sent from another email address using Thunderbird
p.setOutputFileName(output);
p.setPassphrase(passphrase);
p.setSecretKeyFileName(skeyfile);//this is the GPG-generated key
return p.decrypt();//this line throws the error
}
PGPFileProcessor.java включает в себя:
public InputStream decrypt() throws Exception {
FileInputStream in = new FileInputStream(inputFileName);
FileInputStream keyIn = new FileInputStream(secretKeyFileName);
FileOutputStream out = new FileOutputStream(outputFileName);
PGPUtils.decryptFile(in, out, keyIn, passphrase.toCharArray());//error thrown here
in.close();
out.close();
keyIn.close();
InputStream result = new FileInputStream(outputFileName);//I changed return type from boolean on 1/27/15
Files.deleteIfExists(Paths.get(outputFileName));//I also added this to accommodate change of return type on 1/27/15
return result;
}
PGPUtils.java включает в себя:
/**
* decrypt the passed in message stream
*/
@SuppressWarnings("unchecked")
public static void decryptFile(InputStream in, OutputStream out, InputStream keyIn, char[] passwd)
throws Exception
{
Security.addProvider(new BouncyCastleProvider());
in = org.bouncycastle.openpgp.PGPUtil.getDecoderStream(in);
//1/26/15 added Jca prefix to avoid eclipse warning, also used https://www.bouncycastle.org/docs/pgdocs1.5on/index.html
PGPObjectFactory pgpF = new JcaPGPObjectFactory(in);
PGPEncryptedDataList enc;
Object o = pgpF.nextObject();
//
// the first object might be a PGP marker packet.
//
if (o instanceof PGPEncryptedDataList) {enc = (PGPEncryptedDataList) o;}
else {enc = (PGPEncryptedDataList) pgpF.nextObject();}
//
// find the secret key
//
Iterator<PGPPublicKeyEncryptedData> it = enc.getEncryptedDataObjects();
PGPPrivateKey sKey = null;
PGPPublicKeyEncryptedData pbe = null;
while (sKey == null && it.hasNext()) {
pbe = it.next();
sKey = findPrivateKey(keyIn, pbe.getKeyID(), passwd);
}
if (sKey == null) {throw new IllegalArgumentException("Secret key for message not found.");}
InputStream clear = pbe.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey));
//1/26/15 added Jca prefix to avoid eclipse warning, also used https://www.bouncycastle.org/docs/pgdocs1.5on/index.html
PGPObjectFactory plainFact = new JcaPGPObjectFactory(clear);
Object message = plainFact.nextObject();
if (message instanceof PGPCompressedData) {
PGPCompressedData cData = (PGPCompressedData) message;
//1/26/15 added Jca prefix to avoid eclipse warning, also used https://www.bouncycastle.org/docs/pgdocs1.5on/index.html
PGPObjectFactory pgpFact = new JcaPGPObjectFactory(cData.getDataStream());
message = pgpFact.nextObject();
}
if (message instanceof PGPLiteralData) {
PGPLiteralData ld = (PGPLiteralData) message;
InputStream unc = ld.getInputStream();
int ch;
while ((ch = unc.read()) >= 0) {out.write(ch);}
} else if (message instanceof PGPOnePassSignatureList) {
throw new PGPException("Encrypted message contains a signed message - not literal data.");
} else {
throw new PGPException("Message is not a simple encrypted file - type unknown.");
}
if (pbe.isIntegrityProtected()) {
if (!pbe.verify()) {throw new PGPException("Message failed integrity check");}
}
}
/**
* Load a secret key ring collection from keyIn and find the private key corresponding to
* keyID if it exists.
*
* @param keyIn input stream representing a key ring collection.
* @param keyID keyID we want.
* @param pass passphrase to decrypt secret key with.
* @return
* @throws IOException
* @throws PGPException
* @throws NoSuchProviderException
*/
public static PGPPrivateKey findPrivateKey(InputStream keyIn, long keyID, char[] pass)
throws IOException, PGPException, NoSuchProviderException
{
//1/26/15 added Jca prefix to avoid eclipse warning, also used https://www.bouncycastle.org/docs/pgdocs1.5on/index.html
PGPSecretKeyRingCollection pgpSec = new JcaPGPSecretKeyRingCollection(PGPUtil.getDecoderStream(keyIn));
return findPrivateKey(pgpSec.getSecretKey(keyID), pass);
}
/**
* Load a secret key and find the private key in it
* @param pgpSecKey The secret key
* @param pass passphrase to decrypt secret key with
* @return
* @throws PGPException
*/
public static PGPPrivateKey findPrivateKey(PGPSecretKey pgpSecKey, char[] pass)
throws PGPException
{
if (pgpSecKey == null) return null;
PBESecretKeyDecryptor decryptor = new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass);
return pgpSecKey.extractPrivateKey(decryptor);
}
Полный код всех трех файлов Java можно найти на сайте для обмена файлами by clicking on this link.
Полная трассировка стека для ошибки может быть найдена by clicking on this link.
Для справки, инструкции GUI для шифрования с помощью удаленного отправителя Thunderbird суммированы в следующем скриншоте:
Я прочитал много проводок и ссылок по этому поводу. В частности, this other SO posting looks similar, но отличается. Мои ключи используют RSA RSA, но другая публикация - нет.
EDIT # 1
По предложению @ DavidHook, я прочитал SignedFileProcessor, и я начинаю читать гораздо больше RFC 4880. Тем не менее, мне нужен фактический рабочий код для изучения, чтобы понять это. Большинство людей, которые находят это через поисковые запросы Google, также нуждаются в рабочем коде для иллюстрации примеров.
Для справки, метод SignedFileProcessor.verifyFile()
, рекомендованный @DavidHook, выглядит следующим образом. Как это можно настроить для устранения проблем в коде выше?
private static void verifyFile(InputStream in, InputStream keyIn) throws Exception {
in = PGPUtil.getDecoderStream(in);
PGPObjectFactory pgpFact = new PGPObjectFactory(in);
PGPCompressedData c1 = (PGPCompressedData)pgpFact.nextObject();
pgpFact = new PGPObjectFactory(c1.getDataStream());
PGPOnePassSignatureList p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
PGPOnePassSignature ops = p1.get(0);
PGPLiteralData p2 = (PGPLiteralData)pgpFact.nextObject();
InputStream dIn = p2.getInputStream();
int ch;
PGPPublicKeyRingCollection pgpRing = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(keyIn));
PGPPublicKey key = pgpRing.getPublicKey(ops.getKeyID());
FileOutputStream out = new FileOutputStream(p2.getFileName());
ops.initVerify(key, "BC");
while ((ch = dIn.read()) >= 0){
ops.update((byte)ch);
out.write(ch);
}
out.close();
PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
if (ops.verify(p3.get(0))){System.out.println("signature verified.");}
else{System.out.println("signature verification failed.");}
}
EDIT # 2
Метод SignedFileProcessor.verifyFile()
рекомендованный @DavidHook практически идентичен методу PGPUtils.verifyFile()
в моем коде выше, за исключением того, что PGPUtils.verifyFile()
делает копию extractContentFile
и называет PGPOnePassSignature.init()
вместо PGPOnePassSignature.initVerify()
, Это может быть связано с различием в версии. Кроме того, PGPUtils.verifyFile()
возвращает логическое значение, в то время как SignedFileProcessor.verifyFile()
дает SYSO для двух логических значений и возвращает void после SYSO.
Если я интерпретирую @ комментарии JRichardSnape в правильно, это означает, что метод verifyFile()
может лучше назвать вверх по течению, чтобы подтвердить подпись входящего файла с помощью открытого ключа отправителя, а затем, если подпись на файл проверяется, используя другой метод для дешифрования файла с использованием личного ключа получателя. Это верно? Если да, то как мне реструктурировать код для этого?
Wow - это намного сложнее, чем я думал это было бы!!! Одна вещь, которую я могу сказать ориентировочно - это не проблема с завершением gpg, потому что вы получаете те же результаты, если отправляете почту с помощью сгенерированного ключа bouncycastle (я знаю, что вы изо всех сил пытались сделать это cfhttp: //tinyurl.com/p55uxd8), но я *** могу *** проверить это и отправить сообщение с помощью сгенерированного ключа BC (Java), который Enigmail расшифровывает, но этот код показывает ту же проблему.Проблема заключается в том, что код находит пару сигнатур и ключей, но затем поток ввода, по-видимому, расходуется. Расследования продолжаются ... –
Эти вопросы могут быть лучше подобраны в списке рассылки bouncy-dev, поэтому, если вы не получите ответ здесь ... –
Конечно - код в конце будет работать хорошо - вам нужно понять, что 'keyIn' в этом случае является * открытым ключом отправителя * (или, строго говоря, открытый ключ того ключа, который использовался, чтобы подписать письмо, я думаю). Это отличается от 'keyIn' в вашем другом коде, который является частным (секретным) ключом получателя. Обратите внимание, что в соответствии с моим комментарием к ответу вы получите только текстовый бит своего письма - вам нужно рекурсивно извлечь вложения и расшифровать, если это ваша конечная цель ... –