2015-02-11 2 views
3

Как я могу заставить 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() может лучше назвать вверх по течению, чтобы подтвердить подпись входящего файла с помощью открытого ключа отправителя, а затем, если подпись на файл проверяется, используя другой метод для дешифрования файла с использованием личного ключа получателя. Это верно? Если да, то как мне реструктурировать код для этого?

+0

Wow - это намного сложнее, чем я думал это было бы!!! Одна вещь, которую я могу сказать ориентировочно - это не проблема с завершением gpg, потому что вы получаете те же результаты, если отправляете почту с помощью сгенерированного ключа bouncycastle (я знаю, что вы изо всех сил пытались сделать это cfhttp: //tinyurl.com/p55uxd8), но я *** могу *** проверить это и отправить сообщение с помощью сгенерированного ключа BC (Java), который Enigmail расшифровывает, но этот код показывает ту же проблему.Проблема заключается в том, что код находит пару сигнатур и ключей, но затем поток ввода, по-видимому, расходуется. Расследования продолжаются ... –

+2

Эти вопросы могут быть лучше подобраны в списке рассылки bouncy-dev, поэтому, если вы не получите ответ здесь ... –

+0

Конечно - код в конце будет работать хорошо - вам нужно понять, что 'keyIn' в этом случае является * открытым ключом отправителя * (или, строго говоря, открытый ключ того ключа, который использовался, чтобы подписать письмо, я думаю). Это отличается от 'keyIn' в вашем другом коде, который является частным (секретным) ключом получателя. Обратите внимание, что в соответствии с моим комментарием к ответу вы получите только текстовый бит своего письма - вам нужно рекурсивно извлечь вложения и расшифровать, если это ваша конечная цель ... –

ответ

0

Это просто означает, что содержимое было подписано, а затем зашифровано, предусмотренная подпрограмма не знает, как с этим бороться, но, по крайней мере, говорит вам об этом. Протокол PGP представляет собой серию пакетов, некоторые из которых могут быть обернуты в другие (например, сжатые данные также могут переносить подписанные данные или просто литеральные данные, их также можно использовать для генерации зашифрованных данных, фактическое содержимое всегда отображается в литеральных данных).

Если вы посмотрите на метод verifyFile в SignedFileProcessor в пакете примеров OpenPGP Bouncy Castle, вы увидите, как обрабатывать данные подписи и получать литеральные данные, содержащие фактическое содержимое.

Я также рекомендовал бы взглянуть на RFC 4880, чтобы вы знали, как работает протокол. Протокол очень невелик, и оба GPG, BC и различные продукты там отражают это - это говорит о том, что ослабление означает, что если вы попытаетесь вырезать и вставить свой путь к решению, вы получите катастрофу. Это не сложно, но здесь требуется понимание.

+0

Спасибо. Я очень хочу понять. Но мне нужно больше деталей. Можете ли вы показать код, чтобы исправить это? Я хорошо разбираюсь в примерах рабочих кодов. Этот материал сложный. Ваши абзацы кажутся эзотерическими без рабочего кода, чтобы проиллюстрировать, что вы имеете в виду. – CodeMed

+0

Я добавил код для 'SignedFileProcessor.verifyFile()' в мое редактирование в конце моего OP. – CodeMed

+0

Проблема здесь в понимании спецификации PGP (argh!). Теперь я отправил сообщение, которое закодировано, но не подписано, и использовало (слегка модифицированную) версию вашего кода для его декодирования. Однако, когда сообщение подписано, поток объектов PGP, который вы хотите получить (с объектами LiteralData), эффективно «вложен» в объект CompressedData. Нам нужно полностью понять, что вы хотите сделать - у вас уже есть часть вложения вашей электронной почты из сырой электронной почты или вы хотите полностью расшифровать письмо именно так, как оно поступит? –

4

если кому-то интересно узнать, как зашифровать и расшифровать GPG файлы, используя Надувной замок OpenPGP библиотека, проверить код ниже Java:

Ниже являются 4 метода вы будете нуждаться:

Ниже метод будет читать и импортировать свой секретный ключ из .asc файла:

public static PGPSecretKey readSecretKeyFromCol(InputStream in, long keyId) throws IOException, PGPException { 
in = PGPUtil.getDecoderStream(in); 
PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(in, new BcKeyFingerprintCalculator()); 

PGPSecretKey key = pgpSec.getSecretKey(keyId); 

if (key == null) { 
    throw new IllegalArgumentException("Can't find encryption key in key ring."); 
} 
return key; 
} 

метод ниже будет читать и импортировать открытый ключ из .asc файла:

@SuppressWarnings("rawtypes") 
public static PGPPublicKey readPublicKeyFromCol(InputStream in) throws IOException, PGPException { 
    in = PGPUtil.getDecoderStream(in); 
    PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(in, new BcKeyFingerprintCalculator()); 
    PGPPublicKey key = null; 
    Iterator rIt = pgpPub.getKeyRings(); 
    while (key == null && rIt.hasNext()) { 
     PGPPublicKeyRing kRing = (PGPPublicKeyRing) rIt.next(); 
     Iterator kIt = kRing.getPublicKeys(); 
     while (key == null && kIt.hasNext()) { 
      PGPPublicKey k = (PGPPublicKey) kIt.next(); 
      if (k.isEncryptionKey()) { 
       key = k; 
      } 
     } 
    } 
    if (key == null) { 
     throw new IllegalArgumentException("Can't find encryption key in key ring."); 
    } 
    return key; 
} 

Ниже 2 методов расшифровывать и шифровать GPG файлов:

public void decryptFile(InputStream in, InputStream secKeyIn, InputStream pubKeyIn, char[] pass) throws IOException, PGPException, InvalidCipherTextException { 
    Security.addProvider(new BouncyCastleProvider()); 

    PGPPublicKey pubKey = readPublicKeyFromCol(pubKeyIn); 

    PGPSecretKey secKey = readSecretKeyFromCol(secKeyIn, pubKey.getKeyID()); 

    in = PGPUtil.getDecoderStream(in); 

    JcaPGPObjectFactory pgpFact; 


    PGPObjectFactory pgpF = new PGPObjectFactory(in, new BcKeyFingerprintCalculator()); 

    Object o = pgpF.nextObject(); 
    PGPEncryptedDataList encList; 

    if (o instanceof PGPEncryptedDataList) { 

     encList = (PGPEncryptedDataList) o; 

    } else { 

     encList = (PGPEncryptedDataList) pgpF.nextObject(); 

    } 

    Iterator<PGPPublicKeyEncryptedData> itt = encList.getEncryptedDataObjects(); 
    PGPPrivateKey sKey = null; 
    PGPPublicKeyEncryptedData encP = null; 
    while (sKey == null && itt.hasNext()) { 
     encP = itt.next(); 
     secKey = readSecretKeyFromCol(new FileInputStream("PrivateKey.asc"), encP.getKeyID()); 
     sKey = secKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass)); 
    } 
    if (sKey == null) { 
     throw new IllegalArgumentException("Secret key for message not found."); 
    } 

    InputStream clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey)); 

    pgpFact = new JcaPGPObjectFactory(clear); 

    PGPCompressedData c1 = (PGPCompressedData) pgpFact.nextObject(); 

    pgpFact = new JcaPGPObjectFactory(c1.getDataStream()); 

    PGPLiteralData ld = (PGPLiteralData) pgpFact.nextObject(); 
    ByteArrayOutputStream bOut = new ByteArrayOutputStream(); 

    InputStream inLd = ld.getDataStream(); 

    int ch; 
    while ((ch = inLd.read()) >= 0) { 
     bOut.write(ch); 
    } 

    //System.out.println(bOut.toString()); 

    bOut.writeTo(new FileOutputStream(ld.getFileName())); 
    //return bOut; 

} 

public static void encryptFile(OutputStream out, String fileName, PGPPublicKey encKey) throws IOException, NoSuchProviderException, PGPException { 
    Security.addProvider(new BouncyCastleProvider()); 

    ByteArrayOutputStream bOut = new ByteArrayOutputStream(); 

    PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(PGPCompressedData.ZIP); 

    PGPUtil.writeFileToLiteralData(comData.open(bOut), PGPLiteralData.BINARY, new File(fileName)); 

    comData.close(); 

    PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.TRIPLE_DES).setSecureRandom(new SecureRandom())); 

    cPk.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(encKey)); 

    byte[] bytes = bOut.toByteArray(); 

    OutputStream cOut = cPk.open(out, bytes.length); 

    cOut.write(bytes); 

    cOut.close(); 

    out.close(); 
} 

Теперь здесь, как вызвать/запустить выше:

try { 
     decryptFile(new FileInputStream("encryptedFile.gpg"), new FileInputStream("PrivateKey.asc"), new FileInputStream("PublicKey.asc"), "yourKeyPassword".toCharArray()); 

     PGPPublicKey pubKey = readPublicKeyFromCol(new FileInputStream("PublicKey.asc")); 

     encryptFile(new FileOutputStream("encryptedFileOutput.gpg"), "fileToEncrypt.txt", pubKey); 




    } catch (PGPException e) { 
     fail("exception: " + e.getMessage(), e.getUnderlyingException()); 
    } 
+0

Спасибо и +1 за то, что нашли время, чтобы добавить понимание этого старого вопроса. – CodeMed

Смежные вопросы