无法 gpg 解密 BouncyCastlePGP 加密的消息



当我尝试使用BouncyCastle加密的GnuPG解密消息时,我收到两条gpg: [don't know]: invalid packet (ctb=xx)消息,解密失败。

我正在使用BouncyCastle 1.54gpg (GnuPG) 2.0.30 on OSX

1(PGP 密钥是使用 gpg 生成的,如下所示:

$ gpg --gen-key
gpg (GnuPG) 2.0.30; Copyright (C) 2015 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Please select what kind of key you want:
(1) RSA and RSA (default)
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)
Your selection? 1
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048)
Requested keysize is 2048 bits
Please specify how long the key should be valid.
0 = key does not expire
<n>  = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Key is valid for? (0) 0
Key does not expire at all
Is this correct? (y/N) y
GnuPG needs to construct a user ID to identify your key.
Real name: Foo Bar
Email address: foo@bar.com
Comment: Test Key
You selected this USER-ID:
"Foo Bar (Test Key) <foo@bar.com>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
You need a Passphrase to protect your secret key.
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
public and secret key created and signed.
pub   2048R/79CC322A 2017-08-17
Key fingerprint = 93B9 0D06 08D2 EB84 9F83  4CD3 A470 748E 79CC 322A
uid       [ unknown] Foo Bar (Test Key) <foo@bar.com>
sub   2048R/21B41E21 2017-08-17
$ 

2(以 asc 形式导出的 PGP 公钥如下所示:

-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG v2
mQENBFmWE88BCADB+esy+D2Zobru86ztUTp7hWyDy5w9B2iHlyORLI6mQ0JH+ya6
cbaO9nWT7WC68l1ocWaeak0t/9hx0CDWv6zdAXfEUPu8qB53M4m1NACDJp3UDeQS
UytmpB/kutViUO+yiRhtezYsFP70PHOO7o9Tgze5H/qF/hgAVmk1/eN4oSTH4hqo
VIvlsvTNZYLt+a2wYN0RPPryfXnvrKOSXdE5roQd/TMaMwwd2Mbhhh31IB1ROw2/
I+tvJfrM4zLseuo0ndYmWL7ZfjPslsFUezUQf5BDmFdIi9M3/UWHtHmSR6GGG1ko
uo0XjABiIjwfj1mQSA3txI1yO+RLHYrI5UL1ABEBAAG0IEZvbyBCYXIgKFRlc3Qg
S2V5KSA8Zm9vQGJhci5jb20+iQE5BBMBCAAjBQJZlhPPAhsDBwsJCAcDAgEGFQgC
CQoLBBYCAwECHgECF4AACgkQpHB0jnnMMipk+Qf/cKLSdv+aYHKteS/rtAXQbRWG
5nEGKk8vU5HseESr9tDbzkMpruYXtGG99GjPjZO2NqoYF+3NzD6suiQzP95dHyH0
g3i2AHLoyd7W9VvZEieH7vGRrUjYYP2N/qifUepdiu3gKQLHDBE14tXRHEfTN5WV
BxClZ4MwmMvHzsg6NB2RXJb7t5e9apgQ/+0O9l5LwGsSSkCQr53OmoMSUnQP0lrz
gqMxzbC6sI/FYaVaoMGnAUMFQ+8l7nI/Tv6R/sCN5d4egcIC1AzVcDE+zGkwRrmX
4lcKen0XvolJfpUNx9D2CIAQttz0nvygxjBAByXT4oFcbPw9GZCNmxKo/eUp4LkB
DQRZlhPPAQgAz4tebylTFXZCj4xwzy7wyrw4J+vUkMrOh5tOVgISPMaEiDBzFyhf
oqs0uAamImUyF2HGVGXEGappZncjrympdYFpDXG6jb0oYap03hheli1R6h+56PNq
zNyVzlfq85BqgVa5Qed1VnDbEz29JMjLwSvCEY3V0SIOgVZxE5GfMjvAkUhPoE+o
T5uVuRUlnfdKXMYgNRh5gbNEsvx+PMGihC3pSrWWYbBtU6otNXCVCYQryORaHWAf
pvB99P6YzP0nc6Dbu2ZSiGsDAQwa3ZCow+E997upf22WMfowwkNFtarnrr1fVLkl
exR28nJdeAbh0R3WpgQJqeCGmH9fYQEBjwARAQABiQEfBBgBCAAJBQJZlhPPAhsM
AAoJEKRwdI55zDIqgAAH/39teAauUrB+xEVr/Q+McXa0PQSrErB2P4jaVIBuZH2/
6EeRycp6bIwc5R/gpkIVcPg8DmDYtobRbj2YDc5o+4tPVgSKJUgOB2l9CryP+aCm
lx8R0nCN8q53vqtmm7LYc+W1K6tXQJZi8VbCC9sLGUxH0HuDP2ldcBnrialV2cLZ
zsxRgoFF3f8u/We97z2qwzRQXshvw4GVL3wSs511mTcOZW8LnO1YMt3m5ABIsZN0
P/avr7zEqsvr0iJOQ7WbJLbgHkxKu03SfcN0XfVDX4VzqykLvn9THHVtofU6h4LE
LoIRKGg57DXFrsc93a1GWzN9z764sXQ7JgWqnW6a72g=
=n8OZ
-----END PGP PUBLIC KEY BLOCK-----

3(PGP公钥以其"公钥块"形式传入,并按如下方式解析: (pgpKeyBytes 是密钥的字符串表示形式的 UTF-8 字节编码(

private PGPPublicKey resolvePgpPublicKey(byte[] pgpKeyBytes) throws IOException, PGPException {
PGPPublicKeyRingCollection keyRingCollection;
try (InputStream in = PGPUtil.getDecoderStream(new ByteArrayInputStream(pgpKeyBytes))) {
keyRingCollection = new PGPPublicKeyRingCollection(
PGPUtil.getDecoderStream(in), new JcaKeyFingerprintCalculator());
}
Iterator<?> keyRingIterator = keyRingCollection.getKeyRings();
while (keyRingIterator.hasNext()) {
PGPPublicKeyRing keyRing = (PGPPublicKeyRing) keyRingIterator.next();
Iterator<?> keyIterator = keyRing.getPublicKeys();
while (keyIterator.hasNext()) {
PGPPublicKey key = (PGPPublicKey) keyIterator.next();
if (key.isMasterKey()) {
continue;
}
if (key.isEncryptionKey()) {
return key;
}
}
}
throw new ServiceRequestException("Cannot resolve PGPPublicKey");
}

4(PGP加密的是16字节数组的十六进制字符串表示形式(传入为 keyBytes(,如下所示:

private byte[] encryptKeyBytes(byte[] keyBytes, byte[] pgpKey) throws GeneralSecurityException {
ByteArrayOutputStream encKeyBytes = new ByteArrayOutputStream(keyBytes.length);
try (Handle<SecureRandom> randomHandle = RngSupport.getRandom()) {
JcePGPDataEncryptorBuilder encryptorBuilder =
new JcePGPDataEncryptorBuilder(PGPEncryptedDataGenerator.AES_256);
encryptorBuilder.setWithIntegrityPacket(true);
encryptorBuilder.setSecureRandom(randomHandle.getObject());
encryptorBuilder.setProvider("BC");
PGPEncryptedDataGenerator encryptor = new PGPEncryptedDataGenerator(encryptorBuilder);
try {
JcePublicKeyKeyEncryptionMethodGenerator keyEncryptionMethodGenerator =
new JcePublicKeyKeyEncryptionMethodGenerator(resolvePgpPublicKey(pgpKey));
keyEncryptionMethodGenerator.setProvider("BC");
encryptor.addMethod(keyEncryptionMethodGenerator);
try (
OutputStream ao = new ArmoredOutputStream(encKeyBytes);
OutputStream eo = encryptor.open(ao, keyBytes.length)) {
eo.write(BytesSupport.encodeHex(keyBytes).getBytes(StandardCharsets.UTF_8));
}
} catch (ServiceRequestException e) {
throw e;
} catch (Exception e) {
throw new GeneralSecurityException("Cannot perform PGP encryption", e);
}
}
return encKeyBytes.toByteArray();
}

5(此加密的示例结果如下所示:

-----BEGIN PGP MESSAGE-----
Version: BCPG v@RELEASE_NAME@
hQEMA0a1HkkhtB4hAQf+MfDa3ILJJivDYO+V9GwLDXMq1Oi8YFe/oNfScT2KT6aG
rKBIaCQvwTQPD95QS3lo9sRZYvD64C7+Y+PA2e4nSJYNiLmyEczqFVzSgoI8ibhD
LDG+trkAgEd3UiSltju8oF/d5SUPaubVrfH413xZ2xg5lbx7z78U4KtAZ1IMk/XN
DN2nCaOVIw/EbqzVt8YCdDpQRrnfh1ZB5lDmLYoRuJykQ08UCrxv9dyQN3wpOX/G
K7Nq3w6Q6+vT8LiP9iA7NEEu3BObNHAQ371VQ4uJQaZOysxPAH/RFHDHRtDa6R/J
F6ca8z28mliQdZxFpyrqKgPwjrsthZ4BApUnHZ6dm9I5ATnr4m4jPQefMeSgGLYD
I3Mk82COdct8ZNk108bOQjSAl+CPlARb53pItOZm21PL1lVwzcq1IXvUjJYjEOSA
6SccakqFhF8cgQ==
=A56Z
-----END PGP MESSAGE-----

6(当我尝试解密消息时,我收到两个"gpg:[不知道]:无效数据包"消息和 解密失败:

$ gpg -vv --decrypt /tmp/ct.asc
gpg: armor: BEGIN PGP MESSAGE
Version: BCPG v@RELEASE_NAME@
:pubkey enc packet: version 3, algo 1, keyid 46B51E4921B41E21
data: [2046 bits]
gpg: armor header:
gpg: public key is 21B41E21
gpg: using subkey 21B41E21 instead of primary key 79CC322A
You need a passphrase to unlock the secret key for
user: "Foo Bar (Test Key) <foo@bar.com>"
gpg: using subkey 21B41E21 instead of primary key 79CC322A
2048-bit RSA key, ID 21B41E21, created 2017-08-17 (main key ID 79CC322A)
gpg: no running gpg-agent - starting one
gpg: public key encrypted data: good DEK
:encrypted data packet:
length: 57
mdc_method: 2
gpg: encrypted with 2048-bit RSA key, ID 21B41E21, created 2017-08-17
"Foo Bar (Test Key) <foo@bar.com>"
gpg: AES256 encrypted data
gpg: [don't know]: invalid packet (ctb=39)
gpg: decryption okay
gpg: [don't know]: invalid packet (ctb=44)
$

encryptKeyBytes实现(即构建加密 PGP 消息的地方(的问题在于它按原样写入纯文本消息八位字节,而不是文字数据,因此在解密尝试期间会出现协议错误。

正确的实现如下所示:

private byte[] encryptKeyBytes(String keyName, byte[] keyBytes, byte[] pgpKey)
throws GeneralSecurityException {
ByteArrayOutputStream encKeyBytes = new ByteArrayOutputStream(keyBytes.length);
try (Handle<SecureRandom> randomHandle = RngSupport.getRandom()) {
JcePGPDataEncryptorBuilder encryptorBuilder =
new JcePGPDataEncryptorBuilder(PGPEncryptedDataGenerator.AES_256);
encryptorBuilder.setWithIntegrityPacket(true);
encryptorBuilder.setSecureRandom(randomHandle.getObject());
encryptorBuilder.setProvider("BC");
PGPEncryptedDataGenerator encryptor = new PGPEncryptedDataGenerator(encryptorBuilder);
try {
JcePublicKeyKeyEncryptionMethodGenerator keyEncryptionMethodGenerator =
new JcePublicKeyKeyEncryptionMethodGenerator(resolvePgpPublicKey(pgpKey));
keyEncryptionMethodGenerator.setProvider("BC");
encryptor.addMethod(keyEncryptionMethodGenerator);
PGPLiteralDataGenerator dataGenerator = new PGPLiteralDataGenerator();
byte[] data = BytesSupport.encodeHex(keyBytes).getBytes(StandardCharsets.UTF_8);
try (
OutputStream ao = new ArmoredOutputStream(encKeyBytes);
OutputStream eo = encryptor.open(ao, keyBytes.length);
OutputStream go = dataGenerator.open(
eo, PGPLiteralData.UTF8, keyName, data.length, new Date())) {
go.write(data);
}
} catch (ServiceRequestException e) {
throw e;
} catch (Exception e) {
throw new GeneralSecurityException("Cannot perform PGP encryption on the content key", e);
}
}
return encKeyBytes.toByteArray();
}

请注意PGPLiteralDataGenerator的用法,它是提供消息字节写入的输出流的抽象。

最新更新