Created
March 22, 2023 16:17
-
-
Save irof/47f11078a491e3e020076fcef09bd096 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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