Bouncy Castle Java PGP加密&解密
Bouncy Castle Java PGP encryption & decryption
我正在尝试使用 Bouncy Castle 在 Java 中实现 PGP 加密,同时使用他们提供的一些示例。
但是,如果我尝试解密我刚刚加密的消息,那是行不通的。密钥和解密方法似乎都可以,因为我可以使用外部工具加密(link)然后在这里成功解密。
public static void main(String[] args) throws PGPException, IOException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
Security.addProvider(new BouncyCastleProvider());
byte[] msg = ("Test blabla BLA").getBytes();
byte[] encData = encrypt(msg, publicKey);
System.out.println(new String(encData));
System.err.println("DECRYPTED");
System.out.println(decrypt(IOUtils.toInputStream(new String(encData), StandardCharsets.UTF_8), privateKey, passphrase));
System.err.println("DECRYPTED external");
String testEncryptedMsg = "-----BEGIN PGP MESSAGE-----\n" +
"Version: BCPG C# v1.6.1.0\n" +
"\n" +
"hQIMA4p9BgtBOg+BARAAiLdmGZBupsSkrji1qdC/y6KACuaWytcjVg3LWudWRlH8\n" +
"Ye1U0bz+HYc6mLt21RivAZ3p+lPXPzh/h0GVLW/aI2ngJracL+iEA7eam5/H3NQV\n" +
"2o0CR1mjw4xOf0XwDxaLrnivdeuWniXiZyFN7Ud7lmNG+EmQ0d5ZfR7KEnTJNjSe\n" +
"FpzwRffBG5lKB4CxF49jBp+Iupdv4A5JzHuDVBkypPMNhS/UEbuPOt5cfmddYVvW\n" +
"gtDM3rX+ePBhg7d/mVkiDT2KM0Cr7GSrdZ8q83fF4sat3Xp8OHMq61GWriNK4gwb\n" +
"ViT/vmEsY0n55TVgN5VagMZJJlEThkqWE2wpEjq4HM8pNxR4PvP5inqP5oipn8Os\n" +
"Nq2a5f77BS7GB9NQ+hnaLSBsywJkZVKqygN81MeHBnNrVXFQ/Hqg24BLwk49bOuH\n" +
"jTUzorudCnjj/p8/WiyR+PuESkQDLKpo1z9KGEE/rKjTqAwH/fBKJ73XpmAWwd4U\n" +
"1hv/EdF+XDZv7XwxeVjSIK2inAjdNz+way1WNY3qouo7G+gjKf1gbL14q2HVPnd/\n" +
"7mfiW3uWCQ7GhXlHtd1E0WeK2296TdBuSGAvLlVxI4xepsiuVw+zC4BxvAkTXB/a\n" +
"HyJZhKPksHMRCt8ZxitMfhdvMNGjc4MDhv1MLqKijpagKhgrmDTiJRMb3ng6QLvJ\n" +
"L2GICyuS4aIsz6XiCQvZG8ebfb0J4uogbb5HxS2fGFT+Y/VgpCnwhwj6LnLBy1Q3\n" +
"=D/1d\n" +
"-----END PGP MESSAGE-----\n";
System.out.println(decrypt(IOUtils.toInputStream(testEncryptedMsg, StandardCharsets.UTF_8), privateKey, passphrase));
}
我认为问题出在加密方面,但我无法弄清楚到底是什么问题。有人有什么想法吗?
public static byte[] encrypt(byte[] bytes, String publicKey) throws IOException {
ByteArrayOutputStream encOut = new ByteArrayOutputStream();
try {
PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(
new JcePGPDataEncryptorBuilder(PGPEncryptedData.AES_128)
.setSecureRandom(new SecureRandom())
.setWithIntegrityPacket(true)
.setProvider("BC"));
PGPPublicKey encryptionKey = readPublicKey(publicKey);
encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(encryptionKey).setProvider("BC"));
ArmoredOutputStream aOut = new ArmoredOutputStream(encOut);
OutputStream cOut = encGen.open(aOut, bytes.length);
// write out the literal data
PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
OutputStream pOut = lData.open(aOut, PGPLiteralData.UTF8, PGPLiteralData.CONSOLE, bytes.length, new Date());
pOut.write(bytes);
pOut.close();
// finish the encryption
cOut.close();
aOut.close();
} catch (PGPException e) {
log.error("Exception encountered while encoding data.", e);
}
return encOut.toByteArray();
}
public static String decrypt(InputStream in, String secretKey, String passphrase) throws IOException {
String content = null;
try {
in = PGPUtil.getDecoderStream(in);
JcaPGPObjectFactory 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<PGPEncryptedData> it = enc.getEncryptedDataObjects();
PGPPrivateKey sKey = null;
PGPPublicKeyEncryptedData pbe = null;
PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(new ByteArrayInputStream(secretKey.getBytes())), new JcaKeyFingerprintCalculator());
while (sKey == null && it.hasNext()) {
pbe = (PGPPublicKeyEncryptedData) it.next();
sKey = findSecretKey(pgpSec, pbe.getKeyID(), passphrase.toCharArray());
}
if (sKey == null) {
log.error("Secret key for message not found.");
throw new IllegalArgumentException("secret key for message not found.");
}
InputStream clear = pbe.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider("BC").build(sKey));
JcaPGPObjectFactory plainFact = new JcaPGPObjectFactory(clear);
Object message = plainFact.nextObject();
if (message instanceof PGPCompressedData) {
PGPCompressedData cData = (PGPCompressedData) message;
JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(cData.getDataStream());
message = pgpFact.nextObject();
}
if (message instanceof PGPLiteralData) {
PGPLiteralData ld = (PGPLiteralData) message;
content = new String(ld.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
} 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()) {
log.error("message failed integrity check");
} else {
log.info("message integrity check passed");
}
} else {
log.warn("no message integrity check");
}
} catch (PGPException e) {
log.error("Exception encountered while decrypting file.", e);
}
return content;
}
private static PGPPrivateKey findSecretKey(PGPSecretKeyRingCollection pgpSec, long keyID, char[] pass) throws PGPException {
PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID);
if (pgpSecKey == null) {
return null;
}
return pgpSecKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(pass));
}
private static PGPPublicKey readPublicKey(String publicKey) throws IOException, PGPException {
InputStream input = IOUtils.toInputStream(publicKey, StandardCharsets.UTF_8);
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(input), new JcaKeyFingerprintCalculator());
Iterator<PGPPublicKeyRing> keyRingIter = pgpPub.getKeyRings();
while (keyRingIter.hasNext()) {
PGPPublicKeyRing keyRing = keyRingIter.next();
Iterator<PGPPublicKey> keyIter = keyRing.getPublicKeys();
while (keyIter.hasNext()) {
PGPPublicKey key = keyIter.next();
if (key.isEncryptionKey()) {
return key;
}
}
}
throw new IllegalArgumentException("Can't find encryption key in key ring.");
}
您的 encrypt() 方法代码中存在一些错误:
首先,在这一行中,您将直接在装甲输出流上打开文字数据流,而您应该在加密流上打开它:
OutputStream pOut = lData.open(aOut, PGPLiteralData.UTF8, PGPLiteralData.CONSOLE, bytes.length, new Date());
// replace with
OutputStream pOut = lData.open(cOut, PGPLiteralData.UTF8, PGPLiteralData.CONSOLE, bytes.length, new Date());
此外,您应该更换行
OutputStream cOut = encGen.open(aOut, bytes.length);
// with
OutputStream cOut = encGen.open(aOut, new byte[1<<9]);
否则您将运行陷入编码问题。
这些更改应该使您的代码 运行.
不过,我建议使用 PGPainless(免责声明:此处为作者)之类的库,它会为您完成所有低级流包装和检查。最重要的是,它进行了适当的签名验证,这是大多数其他基于 Bouncycastle 的库所缺乏的。
这是最终对我有用的加密方法。
它与 BCFipsIn100 书中提供的示例一起帮助调试。
public byte[] encrypt(byte[] bytes, String publicKey) throws IOException {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
byte[] compressedData = compress(bytes);
PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(
new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5)
.setWithIntegrityPacket(true)
.setSecureRandom(new SecureRandom())
.setProvider(PROVIDER));
PGPPublicKey encryptionKey = readPublicKey(publicKey);
encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(encryptionKey).setProvider(PROVIDER));
ArmoredOutputStream aOut = new ArmoredOutputStream(byteArrayOutputStream);
OutputStream cOut = encGen.open(aOut, compressedData.length);
cOut.write(compressedData);
cOut.close();
aOut.close();
return byteArrayOutputStream.toByteArray();
} catch (PGPException e) {
log.error("Exception encountered while encoding data.", e);
}
return new byte[0];
}
private static byte[] compress(byte[] clearData) throws IOException {
try (ByteArrayOutputStream bOut = new ByteArrayOutputStream()) {
PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP);
OutputStream cos = comData.open(bOut);
PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
OutputStream pOut = lData.open(cos,
PGPLiteralData.BINARY,
PGPLiteralData.CONSOLE,
clearData.length,
new Date());
pOut.write(clearData);
pOut.close();
comData.close();
return bOut.toByteArray();
}
}
我正在尝试使用 Bouncy Castle 在 Java 中实现 PGP 加密,同时使用他们提供的一些示例。
但是,如果我尝试解密我刚刚加密的消息,那是行不通的。密钥和解密方法似乎都可以,因为我可以使用外部工具加密(link)然后在这里成功解密。
public static void main(String[] args) throws PGPException, IOException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
Security.addProvider(new BouncyCastleProvider());
byte[] msg = ("Test blabla BLA").getBytes();
byte[] encData = encrypt(msg, publicKey);
System.out.println(new String(encData));
System.err.println("DECRYPTED");
System.out.println(decrypt(IOUtils.toInputStream(new String(encData), StandardCharsets.UTF_8), privateKey, passphrase));
System.err.println("DECRYPTED external");
String testEncryptedMsg = "-----BEGIN PGP MESSAGE-----\n" +
"Version: BCPG C# v1.6.1.0\n" +
"\n" +
"hQIMA4p9BgtBOg+BARAAiLdmGZBupsSkrji1qdC/y6KACuaWytcjVg3LWudWRlH8\n" +
"Ye1U0bz+HYc6mLt21RivAZ3p+lPXPzh/h0GVLW/aI2ngJracL+iEA7eam5/H3NQV\n" +
"2o0CR1mjw4xOf0XwDxaLrnivdeuWniXiZyFN7Ud7lmNG+EmQ0d5ZfR7KEnTJNjSe\n" +
"FpzwRffBG5lKB4CxF49jBp+Iupdv4A5JzHuDVBkypPMNhS/UEbuPOt5cfmddYVvW\n" +
"gtDM3rX+ePBhg7d/mVkiDT2KM0Cr7GSrdZ8q83fF4sat3Xp8OHMq61GWriNK4gwb\n" +
"ViT/vmEsY0n55TVgN5VagMZJJlEThkqWE2wpEjq4HM8pNxR4PvP5inqP5oipn8Os\n" +
"Nq2a5f77BS7GB9NQ+hnaLSBsywJkZVKqygN81MeHBnNrVXFQ/Hqg24BLwk49bOuH\n" +
"jTUzorudCnjj/p8/WiyR+PuESkQDLKpo1z9KGEE/rKjTqAwH/fBKJ73XpmAWwd4U\n" +
"1hv/EdF+XDZv7XwxeVjSIK2inAjdNz+way1WNY3qouo7G+gjKf1gbL14q2HVPnd/\n" +
"7mfiW3uWCQ7GhXlHtd1E0WeK2296TdBuSGAvLlVxI4xepsiuVw+zC4BxvAkTXB/a\n" +
"HyJZhKPksHMRCt8ZxitMfhdvMNGjc4MDhv1MLqKijpagKhgrmDTiJRMb3ng6QLvJ\n" +
"L2GICyuS4aIsz6XiCQvZG8ebfb0J4uogbb5HxS2fGFT+Y/VgpCnwhwj6LnLBy1Q3\n" +
"=D/1d\n" +
"-----END PGP MESSAGE-----\n";
System.out.println(decrypt(IOUtils.toInputStream(testEncryptedMsg, StandardCharsets.UTF_8), privateKey, passphrase));
}
我认为问题出在加密方面,但我无法弄清楚到底是什么问题。有人有什么想法吗?
public static byte[] encrypt(byte[] bytes, String publicKey) throws IOException {
ByteArrayOutputStream encOut = new ByteArrayOutputStream();
try {
PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(
new JcePGPDataEncryptorBuilder(PGPEncryptedData.AES_128)
.setSecureRandom(new SecureRandom())
.setWithIntegrityPacket(true)
.setProvider("BC"));
PGPPublicKey encryptionKey = readPublicKey(publicKey);
encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(encryptionKey).setProvider("BC"));
ArmoredOutputStream aOut = new ArmoredOutputStream(encOut);
OutputStream cOut = encGen.open(aOut, bytes.length);
// write out the literal data
PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
OutputStream pOut = lData.open(aOut, PGPLiteralData.UTF8, PGPLiteralData.CONSOLE, bytes.length, new Date());
pOut.write(bytes);
pOut.close();
// finish the encryption
cOut.close();
aOut.close();
} catch (PGPException e) {
log.error("Exception encountered while encoding data.", e);
}
return encOut.toByteArray();
}
public static String decrypt(InputStream in, String secretKey, String passphrase) throws IOException {
String content = null;
try {
in = PGPUtil.getDecoderStream(in);
JcaPGPObjectFactory 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<PGPEncryptedData> it = enc.getEncryptedDataObjects();
PGPPrivateKey sKey = null;
PGPPublicKeyEncryptedData pbe = null;
PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(new ByteArrayInputStream(secretKey.getBytes())), new JcaKeyFingerprintCalculator());
while (sKey == null && it.hasNext()) {
pbe = (PGPPublicKeyEncryptedData) it.next();
sKey = findSecretKey(pgpSec, pbe.getKeyID(), passphrase.toCharArray());
}
if (sKey == null) {
log.error("Secret key for message not found.");
throw new IllegalArgumentException("secret key for message not found.");
}
InputStream clear = pbe.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider("BC").build(sKey));
JcaPGPObjectFactory plainFact = new JcaPGPObjectFactory(clear);
Object message = plainFact.nextObject();
if (message instanceof PGPCompressedData) {
PGPCompressedData cData = (PGPCompressedData) message;
JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(cData.getDataStream());
message = pgpFact.nextObject();
}
if (message instanceof PGPLiteralData) {
PGPLiteralData ld = (PGPLiteralData) message;
content = new String(ld.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
} 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()) {
log.error("message failed integrity check");
} else {
log.info("message integrity check passed");
}
} else {
log.warn("no message integrity check");
}
} catch (PGPException e) {
log.error("Exception encountered while decrypting file.", e);
}
return content;
}
private static PGPPrivateKey findSecretKey(PGPSecretKeyRingCollection pgpSec, long keyID, char[] pass) throws PGPException {
PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID);
if (pgpSecKey == null) {
return null;
}
return pgpSecKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(pass));
}
private static PGPPublicKey readPublicKey(String publicKey) throws IOException, PGPException {
InputStream input = IOUtils.toInputStream(publicKey, StandardCharsets.UTF_8);
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(input), new JcaKeyFingerprintCalculator());
Iterator<PGPPublicKeyRing> keyRingIter = pgpPub.getKeyRings();
while (keyRingIter.hasNext()) {
PGPPublicKeyRing keyRing = keyRingIter.next();
Iterator<PGPPublicKey> keyIter = keyRing.getPublicKeys();
while (keyIter.hasNext()) {
PGPPublicKey key = keyIter.next();
if (key.isEncryptionKey()) {
return key;
}
}
}
throw new IllegalArgumentException("Can't find encryption key in key ring.");
}
您的 encrypt() 方法代码中存在一些错误:
首先,在这一行中,您将直接在装甲输出流上打开文字数据流,而您应该在加密流上打开它:
OutputStream pOut = lData.open(aOut, PGPLiteralData.UTF8, PGPLiteralData.CONSOLE, bytes.length, new Date());
// replace with
OutputStream pOut = lData.open(cOut, PGPLiteralData.UTF8, PGPLiteralData.CONSOLE, bytes.length, new Date());
此外,您应该更换行
OutputStream cOut = encGen.open(aOut, bytes.length);
// with
OutputStream cOut = encGen.open(aOut, new byte[1<<9]);
否则您将运行陷入编码问题。 这些更改应该使您的代码 运行.
不过,我建议使用 PGPainless(免责声明:此处为作者)之类的库,它会为您完成所有低级流包装和检查。最重要的是,它进行了适当的签名验证,这是大多数其他基于 Bouncycastle 的库所缺乏的。
这是最终对我有用的加密方法。 它与 BCFipsIn100 书中提供的示例一起帮助调试。
public byte[] encrypt(byte[] bytes, String publicKey) throws IOException {
try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
byte[] compressedData = compress(bytes);
PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(
new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5)
.setWithIntegrityPacket(true)
.setSecureRandom(new SecureRandom())
.setProvider(PROVIDER));
PGPPublicKey encryptionKey = readPublicKey(publicKey);
encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(encryptionKey).setProvider(PROVIDER));
ArmoredOutputStream aOut = new ArmoredOutputStream(byteArrayOutputStream);
OutputStream cOut = encGen.open(aOut, compressedData.length);
cOut.write(compressedData);
cOut.close();
aOut.close();
return byteArrayOutputStream.toByteArray();
} catch (PGPException e) {
log.error("Exception encountered while encoding data.", e);
}
return new byte[0];
}
private static byte[] compress(byte[] clearData) throws IOException {
try (ByteArrayOutputStream bOut = new ByteArrayOutputStream()) {
PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP);
OutputStream cos = comData.open(bOut);
PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
OutputStream pOut = lData.open(cos,
PGPLiteralData.BINARY,
PGPLiteralData.CONSOLE,
clearData.length,
new Date());
pOut.write(clearData);
pOut.close();
comData.close();
return bOut.toByteArray();
}
}