如何在 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);
}
我必须在远程 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);
}