6

BouncyCastle PGP を使用してシングルパスで署名および暗号化されたメッセージを銀行が復号化できない理由を突き止めようと、2 週間ほど頭を悩ませてきました。この銀行は、復号化に McAfee E-Business Server 8.6 を使用しています。

データは銀行の公開鍵で暗号化され、当社の秘密鍵で署名されています。

暗号化に独自の公開鍵を使用することで、以下のコードで生成されたファイルの署名を復号化して検証することができました。Gnupg は、ファイルを問題なく復号化および検証できます。

ただし、銀行はファイルを復号化できません。最初に圧縮をオフにしてから、ASCII アーマーをオフにしようとしました。どちらのオプションも機能していないようで、どのオプションを試しても常に同じエラー メッセージが表示されます。

event 1: initial
event 13: BeginLex
event 8: Analyze
File is encrypted.  event 9: Recipients
Secret key is required to read it.
Key for user ID "XXXXXXXXX <XXXXXX@XXXX>"
event 6: Passphrase
event 23: Decryption

symmetric cipher used: CAST5
event 3: error -11391

event 2: final

Error decrypting file '/somepath/FILENAME'.
Corrupt data.
Bad packet

exitcode = 32

シングルパス署名と暗号化を行うために使用しているコードは次のとおりです。

public class PGPService {

    private static final Logger log = Logger.getLogger(PGPService.class);


    static {
        Security.addProvider(new BouncyCastleProvider());
    }


    /**
     * A simple routine that opens a key ring file and loads the first available key
     * suitable for signature generation.
     * 
     * @param input stream to read the secret key ring collection from.
     * @return a secret key.
     * @throws IOException on a problem with using the input stream.
     * @throws PGPException if there is an issue parsing the input stream.
     */
    @SuppressWarnings("rawtypes")
    private static PGPSecretKey readSecretKey(InputStream input) throws IOException, PGPException {
        PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(
        PGPUtil.getDecoderStream(input));

        // We just loop through the collection till we find a key suitable for encryption, in the real
        // world you would probably want to be a bit smarter about this.

        Iterator keyRingIter = pgpSec.getKeyRings();
        while (keyRingIter.hasNext()) {
            PGPSecretKeyRing keyRing = (PGPSecretKeyRing)keyRingIter.next();
            Iterator keyIter = keyRing.getSecretKeys();
            while (keyIter.hasNext()) {
                PGPSecretKey key = (PGPSecretKey)keyIter.next();
                if (key.isSigningKey()) {
                    return key;
                }
            }
        }

        throw new IllegalArgumentException("Can't find signing key in key ring.");
    }

    /**
     * Single pass signs and encrypts the given file to the given output file using the provided keys.
     * 
     * @param fileNameIn - The name and location of the source input file.
     * @param fileNameOut - The name and location of the destination output file.
     * @param privateKeyIn - The input stream for the private key file.
     * @param privateKeyPassword - The password for the private key file.
     * @param publicEncryptionKey - The public encryption key.
     * @param armoredOutput - Whether or not to ASCII armor the output.
     */
    @SuppressWarnings("rawtypes")
    public static void signAndEncrypt(String fileNameIn, String fileNameOut, InputStream privateKeyIn, String privateKeyPassword, PGPPublicKey publicEncryptionKey, boolean armoredOutput, boolean compress) {

        int bufferSize = 1<<16;
        InputStream input = null;
        OutputStream finalOut = null;
        OutputStream encOut = null;
        OutputStream compressedOut = null;
        OutputStream literalOut = null;
        PGPEncryptedDataGenerator encryptedDataGenerator = null;
        PGPCompressedDataGenerator compressedDataGenerator = null;
        PGPSignatureGenerator signatureGenerator = null;
        PGPLiteralDataGenerator literalDataGenerator = null;

        try {

            File output = new File(fileNameOut);
            OutputStream out = new FileOutputStream(output);
            if (armoredOutput) out = new ArmoredOutputStream(out);

            // ? Use BCPGOutputStreams ?

            // Init encrypted data generator
            encryptedDataGenerator = new PGPEncryptedDataGenerator(PGPEncryptedDataGenerator.CAST5, true, new SecureRandom(), "BC");
            encryptedDataGenerator.addMethod(publicEncryptionKey);
            finalOut = new BufferedOutputStream(out, bufferSize);
            encOut = encryptedDataGenerator.open(finalOut, new byte[bufferSize]);

            // Init compression
            if (compress) {
                compressedDataGenerator = new PGPCompressedDataGenerator(PGPCompressedData.ZLIB);
                compressedOut = new BufferedOutputStream(compressedDataGenerator.open(encOut));
            }

            // Init signature
            PGPSecretKey pgpSec = readSecretKey(privateKeyIn);
            PGPPrivateKey pgpPrivKey = pgpSec.extractPrivateKey(privateKeyPassword.toCharArray(), "BC");        
            signatureGenerator = new PGPSignatureGenerator(pgpSec.getPublicKey().getAlgorithm(), PGPUtil.SHA1, "BC");
            signatureGenerator.initSign(PGPSignature.CANONICAL_TEXT_DOCUMENT, pgpPrivKey);
            Iterator it = pgpSec.getPublicKey().getUserIDs();
            if (it.hasNext()) {
                PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
                spGen.setSignerUserID(false, (String)it.next());
                signatureGenerator.setHashedSubpackets(spGen.generate());
            }
            PGPOnePassSignature onePassSignature = signatureGenerator.generateOnePassVersion(false);
            if (compress) onePassSignature.encode(compressedOut);
            else onePassSignature.encode(encOut);

            // Create the Literal Data generator Output stream which writes to the compression stream
            literalDataGenerator = new PGPLiteralDataGenerator(true);
            if (compress) literalOut = literalDataGenerator.open(compressedOut, PGPLiteralData.BINARY, output.getName(), new Date(), new byte[bufferSize]);
            else literalOut = literalDataGenerator.open(encOut, PGPLiteralData.TEXT, fileNameIn, new Date(), new byte[bufferSize]);

            // Update sign and encrypt
            byte[] buffer = new byte[bufferSize];

            int bytesRead = 0;
            input = new FileInputStream(fileNameIn);
            while((bytesRead = input.read(buffer)) != -1) {
                literalOut.write(buffer,0,bytesRead);
                signatureGenerator.update(buffer,0,bytesRead);
                literalOut.flush();
            }

            // Close Literal data stream and add signature
            literalOut.close();
            literalDataGenerator.close();
            if (compress) signatureGenerator.generate().encode(compressedOut);
            else signatureGenerator.generate().encode(encOut);

        } catch (Exception e) {
            log.error(e);
            throw new RuntimeException(e);
        } finally {
            // Close all streams
            if (literalOut != null) try { literalOut.close(); } catch (IOException e) {}
            if (literalDataGenerator != null) try { literalDataGenerator.close(); } catch (IOException e) {}
            if (compressedOut != null) try { compressedOut.close(); } catch (IOException e) {}
            if (compressedDataGenerator != null) try {  compressedDataGenerator.close(); } catch (IOException e) {}
            if (encOut != null) try {  encOut.close(); } catch (IOException e) {}
            if (encryptedDataGenerator != null) try {  encryptedDataGenerator.close(); } catch (IOException e) {}
            if (finalOut != null) try {  finalOut.close(); } catch (IOException e) {}
            if (input != null) try {  input.close(); } catch (IOException e) {}
        }
    }

    @SuppressWarnings("rawtypes")
    private static PGPPublicKey readPublicKeyFromCol(InputStream in) throws Exception {
        PGPPublicKeyRing pkRing = null;
        PGPPublicKeyRingCollection pkCol = new PGPPublicKeyRingCollection(in);
        log.info("Key ring size = " + pkCol.size());
        Iterator it = pkCol.getKeyRings();
        while (it.hasNext()) {
            pkRing = (PGPPublicKeyRing) it.next();
            Iterator pkIt = pkRing.getPublicKeys();
            while (pkIt.hasNext()) {
                PGPPublicKey key = (PGPPublicKey) pkIt.next();
                log.info("Encryption key = " + key.isEncryptionKey() + ", Master key = " + 
                key.isMasterKey());
                if (key.isEncryptionKey()) {
                    // Find out a little about the keys in the public key ring.
                    log.info("Key Strength = " + key.getBitStrength());
                    log.info("Algorithm = " + key.getAlgorithm());
                    log.info("Bit strength = " + key.getBitStrength());
                    log.info("Version = " + key.getVersion());
                    return key;
                }
            }
        }
        return null;
    }

    private static PGPPrivateKey findSecretKey(InputStream keyIn, long keyID, char[] pass) 
            throws IOException, PGPException, NoSuchProviderException {
        PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(keyIn));
        PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID);
        if (pgpSecKey == null) {
            return null;
        }
        return pgpSecKey.extractPrivateKey(pass, "BC");
    }
}

何がこれを引き起こしているのでしょうか?Google を使用して多数のレポートを見つけましたが、解決策やフォローアップはありません。

4

1 に答える 1

7

この質問への答えはオンラインのどこにも見つけるのが非常に困難だったので、それが他の誰かに役立つことを期待して、私は自分の質問に答えています。

8.6 より前のバージョンの McAfee E-Business Server には、BouncyCastle PGP との互換性の問題があり、ほとんどの人はそれを機能させることができなかったようです. そのため、ベンダー/クライアント/銀行が 8.6 より前のバージョンの E-Business Server を使用している場合、SOL である可能性が非常に高く、別の暗号化パッケージを見つける必要があるかもしれません。

ソース: https://kc.mcafee.com/corporate/index?page=content&id=KB60816&cat=CORP_EBUSINESS_SERVER&actp=LIST

「Bouncy Castle v1.37 で暗号化されたファイルを復号化すると、アクセス違反エラー (UNIX プラットフォームでは SIGSEG) が発生する可能性があります。この問題は、このリリースで解決されています。」

幸いなことに、私たちの銀行は McAfee E-Business Server 8.6 を使用しています。ただし、これは方程式の一部にすぎません。非互換性の問題を解決するために、ファイルの復号化と検証を成功させる前に、圧縮と ASCII 防御の両方をオフにする必要がありました。私が投稿した元のコードを使用すると、E-Business Server 8.6 を使用するクライアントに対して次のように呼び出すことができます。

PGPService.signAndEncrypt(clearTextFileName, secureFileName, privKeyIn, privateKeyFilePassword, pubKeyIn, true, false, false);

もちろん、これは ASCII アーマーを使用できないことを意味しますが、これは問題になる場合とそうでない場合があります。そうである場合、BouncyCastle dev メーリング リストの David は、BouncyCastle を非パケット モードで使用することを提案しました。IE: ストリーム内のオープン コマンドにバイト バッファを渡さないでください。または、2 つのパスでファイルに署名して暗号化します。

次のように呼び出します。

public static void signFile(String fileNameIn, String fileNameOut, InputStream privKeyIn, String password, boolean armoredOutput) {

        OutputStream out = null;
        BCPGOutputStream bOut = null;
        OutputStream lOut = null;
        InputStream fIn = null;

        try {
            out = new FileOutputStream(fileNameOut);
            if (armoredOutput) {
                out = new ArmoredOutputStream(out);
            }
            PGPSecretKey pgpSec = readSecretKey(privKeyIn);
            PGPPrivateKey pgpPrivKey = pgpSec.extractPrivateKey(password.toCharArray(), "BC");        
            PGPSignatureGenerator sGen = new PGPSignatureGenerator(pgpSec.getPublicKey().getAlgorithm(), PGPUtil.SHA1, "BC");

            sGen.initSign(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);

            Iterator it = pgpSec.getPublicKey().getUserIDs();
            if (it.hasNext()) {
                PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
                spGen.setSignerUserID(false, (String)it.next());
                sGen.setHashedSubpackets(spGen.generate());
            }

            PGPCompressedDataGenerator cGen = new PGPCompressedDataGenerator(PGPCompressedData.ZLIB);
            bOut = new BCPGOutputStream(cGen.open(out));

            sGen.generateOnePassVersion(false).encode(bOut);

            File file = new File(fileNameIn);
            PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator();
            lOut = lGen.open(bOut, PGPLiteralData.BINARY, file);
            fIn = new FileInputStream(file);
            int ch = 0;

            while ((ch = fIn.read()) >= 0) {
                lOut.write(ch);
                sGen.update((byte) ch);
            }

            lGen.close();
            sGen.generate().encode(bOut);
            cGen.close();

        } catch (Exception e) {
            log.error(e);
            throw new RuntimeException(e);
        } finally {
            if (lOut != null) try { lOut.close(); } catch (IOException e) {}
            if (bOut != null) try { bOut.close(); } catch (IOException e) {}
            if (out != null) try { out.close(); } catch (IOException e) {}
            if (fIn != null) try { fIn.close(); } catch (IOException e) {}
        }
    }

続いて次のように呼び出します。

public static byte[] encrypt(byte[] data, InputStream pubKeyIn, boolean isPublicKeyArmored) {

        FileOutputStream fos = null;
        BufferedReader isr = null;

        try {
            if (isPublicKeyArmored) pubKeyIn = new ArmoredInputStream(pubKeyIn);
            PGPPublicKey key = readPublicKeyFromCol(pubKeyIn);
            log.info("Creating a temp file...");
            // Create a file and write the string to it.
            File tempfile = File.createTempFile("pgp", null);
            fos = new FileOutputStream(tempfile);
            fos.write(data);
            fos.close();
            log.info("Temp file created at: " + tempfile.getAbsolutePath());
            log.info("Reading the temp file to make sure that the bits were written...\n");
            isr = new BufferedReader(new FileReader(tempfile));
            String line = "";
            while ((line = isr.readLine()) != null ) {
                log.info(line + "\n");
            }
            int count = 0;
            for (java.util.Iterator iterator = key.getUserIDs(); iterator.hasNext();) {
                count++;
                log.info(iterator.next());
            }
            log.info("Key Count = " + count);
            // Encrypt the data.
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            _encrypt(tempfile.getAbsolutePath(), baos, key);
            log.info("Encrypted text length = " + baos.size());         
            tempfile.delete();
            return baos.toByteArray();
        } catch (PGPException e) {
            log.error(e);
            throw new RuntimeException(e);
        } catch (Exception e) {
            log.error(e);
            throw new RuntimeException(e);
        } finally {
            if (fos != null) try { fos.close(); } catch (IOException e) {}
            if (isr != null) try { isr.close(); } catch (IOException e) {}
        }
    }

このメソッドをテストして、非互換性の問題が解決されるかどうかを確認できなかったため、注意してください。しかし、これは選択肢がなく、E-Business Server 宛先に ASCII アーマーを使用する必要がある場合に試すことができる方法です。

BouncyCastle dev メーリング リストのアーカイブには、この道を進むことにした場合に役立つ情報がいくつかあります。具体的には、このスレッド: http://www.bouncycastle.org/devmailarchive/msg12080.html

于 2011-07-08T17:27:54.770 に答える