Skip to content

Instantly share code, notes, and snippets.

@irof
Created March 22, 2023 16:17
Show Gist options
  • Save irof/47f11078a491e3e020076fcef09bd096 to your computer and use it in GitHub Desktop.
Save irof/47f11078a491e3e020076fcef09bd096 to your computer and use it in GitHub Desktop.
package bc;
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
import org.bouncycastle.openpgp.*;
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory;
import org.bouncycastle.openpgp.bc.BcPGPPublicKeyRing;
import org.bouncycastle.openpgp.bc.BcPGPSecretKeyRing;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.*;
import org.bouncycastle.util.io.Streams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Instant;
import java.util.Date;
import java.util.function.Predicate;
import java.util.stream.StreamSupport;
/**
* BouncyCastle OpenPGPの暗号化/復号実装
*/
public class BCPGPEncryptDecryptSample {
private static final Logger logger = LoggerFactory.getLogger(BCPGPEncryptDecryptSample.class);
private final Path publicKeyPath;
private final Path privateKeyPath;
private final char[] passPhrase;
public BCPGPEncryptDecryptSample(Path publicKeyPath, Path privateKeyPath, String passPhrase) {
this.publicKeyPath = publicKeyPath;
this.privateKeyPath = privateKeyPath;
this.passPhrase = passPhrase.toCharArray();
}
/**
* 復号
*
* @param inputStream 復号対象の入力ストリーム
* @param outputStream 出力先のストリーム
*/
private void decrypt(InputStream inputStream, OutputStream outputStream) {
try {
try (InputStream decreptedInputStream = readPGPObject(PGPUtil.getDecoderStream(inputStream))) {
Streams.pipeAll(decreptedInputStream, outputStream);
}
} catch (IOException | PGPException e) {
throw new RuntimeException(e);
}
}
/**
* PGPデータの読み込み
*
* @param inputStream 読み込み対象
* @return 入力ストリーム
*/
private InputStream readPGPObject(InputStream inputStream) throws IOException, PGPException {
PGPObjectFactory factory = new BcPGPObjectFactory(inputStream);
Object pgpObject = factory.nextObject();
if (pgpObject instanceof PGPEncryptedDataList encryptedDataList) {
PGPEncryptedData encryptedData = encryptedDataList.get(0);
if (encryptedData instanceof PGPPublicKeyEncryptedData publicKeyEncryptedData) {
PGPPrivateKey privateKey = privateKey(publicKeyEncryptedData.getKeyID());
BcPublicKeyDataDecryptorFactory decryptorFactory = new BcPublicKeyDataDecryptorFactory(privateKey);
InputStream dataStream = publicKeyEncryptedData.getDataStream(decryptorFactory);
return readPGPObject(dataStream);
}
} else if (pgpObject instanceof PGPCompressedData compressedData) {
return readPGPObject(compressedData.getDataStream());
} else if (pgpObject instanceof PGPLiteralData literalData) {
return literalData.getInputStream();
}
throw new UnsupportedOperationException();
}
/**
* 暗号化
*
* @param inputStream 暗号化対象の入力ストリーム
* @param outputStream 出力先のストリーム(FileOutputStream(BufferedOutputStream)やByteArrayOutputStream)
*/
public void encrypt(InputStream inputStream, OutputStream outputStream) {
int encryptionAlgorithm = PGPEncryptedData.AES_256;
int compressionAlgorithm = CompressionAlgorithmTags.ZIP;
encrypt(inputStream, outputStream, encryptionAlgorithm, compressionAlgorithm);
}
/**
* 暗号化
*
* @param inputStream 暗号化対象の入力
* @param outputStream 出力先のストリーム(FileOutputStream(BufferedOutputStream)やByteArrayOutputStream)
* @param encryptionAlgorithm 暗号化方式(PGPEncryptedData定数)
* @param compressionAlgorithm 圧縮方式(CompressionAlgorithmTags定数)
*/
private void encrypt(InputStream inputStream, OutputStream outputStream, int encryptionAlgorithm, int compressionAlgorithm) {
// 暗号化
PGPDataEncryptorBuilder encryptorBuilder = new BcPGPDataEncryptorBuilder(encryptionAlgorithm);
encryptorBuilder.setWithIntegrityPacket(true); // デフォルトfalse
PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator(encryptorBuilder);
encryptedDataGenerator.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(encryptionPublicKey()));
// 圧縮
PGPCompressedDataGenerator compressedDataGenerator = new PGPCompressedDataGenerator(compressionAlgorithm);
// リテラル(データの書き込み先)
PGPLiteralDataGenerator literalDataGenerator = new PGPLiteralDataGenerator();
try (
Closeable ignore1 = encryptedDataGenerator::close;
Closeable ignore2 = compressedDataGenerator::close;
Closeable ignore3 = literalDataGenerator::close;
OutputStream encryptedOutputStream = encryptedDataGenerator.open(outputStream, new byte[4096]);
OutputStream compressedOutputStream = compressedDataGenerator.open(encryptedOutputStream);
OutputStream literalOutputStream = literalDataGenerator.open(compressedOutputStream, PGPLiteralDataGenerator.BINARY, "", new Date(), new byte[1 << 16])
) {
Streams.pipeAll(inputStream, literalOutputStream);
} catch (IOException | PGPException e) {
throw new RuntimeException(e);
}
}
/**
* 暗号化に使用する公開鍵を取得する
*/
private PGPPublicKey encryptionPublicKey() {
try (InputStream keyInputStream = Files.newInputStream(publicKeyPath);
InputStream decoderStream = PGPUtil.getDecoderStream(keyInputStream)) {
PGPPublicKeyRing keyRing = new BcPGPPublicKeyRing(decoderStream);
return StreamSupport.stream(keyRing.spliterator(), false)
// 暗号化キー(フィルタリング)
.filter(PGPPublicKey::isEncryptionKey)
// 失効署名されていない(サブキー)
.filter(Predicate.not(PGPPublicKey::hasRevocation))
// 有効期限切れでない
.filter(key -> {
long validSeconds = key.getValidSeconds();
// 0は無期限
if (validSeconds == 0) return true;
Instant expireTime = key.getCreationTime().toInstant().plusSeconds(validSeconds);
boolean expired = expireTime.isBefore(Instant.now());
if (expired) {
logger.warn("有効期限切れ {} expired at {}", Long.toHexString(key.getKeyID()), expireTime);
return false;
}
return true;
})
.findFirst()
.orElseThrow();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 復号に使用する秘密鍵を取得する
*/
private PGPPrivateKey privateKey(long keyID) {
try (InputStream keyInputStream = Files.newInputStream(privateKeyPath);
InputStream decoderStream = PGPUtil.getDecoderStream(keyInputStream)) {
PGPSecretKeyRing keyRing = new BcPGPSecretKeyRing(decoderStream);
PBESecretKeyDecryptor decryptor = new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(passPhrase);
return keyRing.getSecretKey(keyID).extractPrivateKey(decryptor);
} catch (IOException | PGPException e) {
throw new RuntimeException(e);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment