如何在 JAVA 中签署和验证文件

How to sign and verify the file in JAVA

我必须在远程 SFTP 服务器中放置一个文件,然后我必须使用私钥对文件进行签名,然后他们将使用 public 密钥对其进行验证。我从响应文件中收到 "PGP Signature verification failed" 错误。

所以我尝试验证来自 JAVA 的标志。不过,我从签名验证方法中得到的值是错误的。

如有任何帮助,我们将不胜感激。

这是我整理的代码。

public class 签名并验证 {

static final KeyFingerPrintCalculator FP_CALC = new BcKeyFingerprintCalculator();
private static File publicKeyFile = new File("\publicSign.asc");
private static File privateKeyFile = new File("\privateSign.asc");
private static final BouncyCastleProvider provider = new BouncyCastleProvider();

static {
    Security.addProvider(provider);
}

public static void signFile(String fileName, PGPSecretKey secretKey, String secretPwd, boolean armor, OutputStream out)
        throws PGPException {
    BCPGOutputStream bOut = null;
    OutputStream lOut = null;
    InputStream fIn = null;
    try {
        OutputStream theOut = armor ? new ArmoredOutputStream(out) : out;

        PGPPrivateKey pgpPrivKey = secretKey.extractPrivateKey(
                new JcePBESecretKeyDecryptorBuilder().setProvider(provider).build(secretPwd.toCharArray()));
        PGPSignatureGenerator sGen = new PGPSignatureGenerator(
                new JcaPGPContentSignerBuilder(secretKey.getPublicKey().getAlgorithm(), PGPUtil.SHA1)
                        .setProvider(provider));

        sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);

        Iterator<String> it = secretKey.getPublicKey().getUserIDs();
        if (it.hasNext()) {
            PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();

            spGen.setSignerUserID(false, (String) it.next());
            sGen.setHashedSubpackets(spGen.generate());
        }

        bOut = new BCPGOutputStream(theOut);

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

        PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator();

        lOut = lGen.open(bOut, PGPLiteralData.BINARY, "filename", new Date(), new byte[2048]);

        fIn = new BufferedInputStream(new FileInputStream(fileName));
        byte[] buf = new byte[2048];
        int ch;

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

        lGen.close();

        sGen.generate().encode(bOut);

        theOut.close();
    } catch (Exception e) {
        throw new PGPException("Error in sign", e);
    } finally {
        try {
            if (bOut != null) {
                bOut.close();
            }
            if(lOut != null) {
                lOut.close();
            }
            if(fIn != null) {
            fIn.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public static boolean verifyFile(InputStream lin, PGPPublicKey publicKey) throws PGPException {
    try {
        InputStream in = PGPUtil.getDecoderStream(lin);

        JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(in);

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

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

        PGPOnePassSignatureList p1 = (PGPOnePassSignatureList) pgpFact.nextObject();


        PGPOnePassSignature ops = p1.get(0);

        PGPLiteralData p2 = (PGPLiteralData) pgpFact.nextObject();

        InputStream dIn = p2.getInputStream();
        int ch;

        ops.init(new JcaPGPContentVerifierBuilderProvider().setProvider(provider), publicKey);

        while ((ch = dIn.read()) >= 0) {
            ops.update((byte) ch);
        }

        PGPSignatureList p3 = (PGPSignatureList) pgpFact.nextObject();

        return ops.verify(p3.get(0));

    } catch (Exception e) {
        throw new PGPException("Error in verify", e);
    }
}

static PGPSecretKey readSecretKey(InputStream input) throws IOException, PGPException {
    PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(input),
            new JcaKeyFingerprintCalculator());

    //
    // 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<PGPSecretKeyRing> keyRingIter = pgpSec.getKeyRings();
    while (keyRingIter.hasNext()) {
        PGPSecretKeyRing keyRing = keyRingIter.next();

        Iterator<PGPSecretKey> keyIter = keyRing.getSecretKeys();
        while (keyIter.hasNext()) {
            PGPSecretKey key = keyIter.next();

            if (key.isSigningKey()) {
                return key;
            }
        }
    }

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

private static PGPPublicKey readPublicKeyFromCol(InputStream in) throws IOException, PGPException {
    PGPPublicKeyRing pkRing = null;
    PGPPublicKeyRingCollection pkCol = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(in), FP_CALC);
    println("key ring size=" + pkCol.size());
    Iterator<PGPPublicKeyRing> it = pkCol.getKeyRings();
    while (it.hasNext()) {
        pkRing =  it.next();
        Iterator<PGPPublicKey> pkIt = pkRing.getPublicKeys();
        while (pkIt.hasNext()) {
            PGPPublicKey key =  pkIt.next();
            println("Encryption key = " + key.isEncryptionKey() + ", Master key = " + key.isMasterKey());
            if (key.isEncryptionKey())
                return key;
        }
    }
    return null;
}

public static void main(String[] args) throws Exception {
    println("Inside Class..");

    String fileName = "\fileToBeSigned.xml";
    String secretKey = "Passphrase";
    String outFileName = "\signedFile.xml";

    OutputStream out = new BufferedOutputStream(new FileOutputStream(outFileName));

    InputStream lin = new BufferedInputStream(new FileInputStream(outFileName));

    PGPSecretKey pgpSec = readSecretKey(new BufferedInputStream(new FileInputStream(privateKeyFile)));
    signFile(fileName, pgpSec, secretKey, true, out);

    PGPPublicKey encKey = readPublicKeyFromCol(new FileInputStream(publicKeyFile));

    Boolean lverify = verifyFile(lin, encKey);

    println("result is ::" + lverify);
    out.close();
    lin.close();

}

private static void println(String msg) {
    System.out.println(msg);
}

}

首先,签名者正在寻找任何可签名的密钥,而验证者正在寻找任何可加密的密钥。根据您生成密钥的方式和时间,这些可能不相同:使用子密钥进行加密(仅)被认为是一种好的做法,并且至少二十年来一直是默认设置 - 有时也一个用于数据签名的 different 子密钥(仅保留主密钥用于密钥签名又名 'certifying')。但是从技术上讲,拥有一个具有数据签名和加密功能的 RSA 主密钥是可能的(并且还可以选择进行认证,尽管它未被使用);在 GPG 中,这可以通过在 'expert' 模式下生成来完成,或者在最新版本中通过生成后编辑来完成。由于您没有向我们展示您的 secret/private 密钥的详细信息——而且除非这些是仅供测试的密钥,否则您不应该向我们展示您可以妥协的结果——因此无法说明您的情况。如果您实际上使用不同的密钥来尝试签名和验证,那么它当然永远无法正常工作。

一般来说,收件人应该使用消息中的 keyid 指定的密钥:发件人使用任何具有加密能力的公钥进行加密,在消息中识别该密钥,并且接收方使用发送方选择的密钥的一半私钥进行解密;发送方使用任何可签名的私钥进行签名,在消息中识别该密钥,接收方使用发送方选择的密钥的一半公钥进行验证。这将需要重新组织您的代码以仅在读取签名包后选择验证密钥。现在我只是在 verifyFile 中添加了一个 sig.getKeyID() == publicKey.getKeyID() 的支票。

这在 signFile

中留下了更严重的数据处理错误
    byte[] buf = new byte[2048];
    int ch;
    while ((ch = fIn.read(buf)) >= 0) {
        lOut.write(ch);
        sGen.update(buf, 0, ch);
    }

您计算了来自输入文件的所有数据的签名,但是您将放入消息中每个缓冲数据仅一个字节;请参阅 OutputStream.write(int) 的 javadoc。由于验证者使用消息中的数据,该数据现在与已签名的数据完全不同,签名不应该也不会验证。以及消息对接收者无用。相反做

  lOut.write(buf,0,ch);

或像在 verifyFile

中那样切换到一次处理一个字节
  int ch; // no byte[] buf
  while( (ch = fIn.read()) >= 0 ){
    lOut.write((byte)ch); // cast not needed but clarifies intent
    sig.update((byte)ch); 
  }