From ed11f541ba9144795e0225049c80b782ccb39ddd Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Sun, 18 Jul 2021 15:20:37 +0800 Subject: [PATCH] feat: add pgp encrypt support --- .../tools/tinyencrypt/TinyEncryptArgs.java | 3 + .../tools/tinyencrypt/TinyEncryptMain.java | 11 ++- .../tinyencrypt/config/TinyEncryptConfig.java | 18 +++++ .../config/TinyEncryptConstant.java | 2 +- .../encrypt/EncryptedFileUtil.java | 73 +++++++++++++++++-- .../tinyencrypt/encrypt/TinyEncryptMeta.java | 9 +++ .../encrypt/TinyEncryptMetaUtil.java | 15 ++++ 7 files changed, 119 insertions(+), 12 deletions(-) diff --git a/src/main/java/me/hatter/tools/tinyencrypt/TinyEncryptArgs.java b/src/main/java/me/hatter/tools/tinyencrypt/TinyEncryptArgs.java index fb83a16..29298bb 100644 --- a/src/main/java/me/hatter/tools/tinyencrypt/TinyEncryptArgs.java +++ b/src/main/java/me/hatter/tools/tinyencrypt/TinyEncryptArgs.java @@ -37,6 +37,9 @@ public class TinyEncryptArgs { @CommandLine.Option(names = {"-I", "--info"}, description = "Encrypt file info") boolean fileInfo = false; + @CommandLine.Option(names = {"-P", "--pgp"}, description = "Decrypt use PGP") + boolean pgp = false; + @CommandLine.Parameters(paramLabel = "FILE", description = "Encrypt or Decrypt files") File[] files; diff --git a/src/main/java/me/hatter/tools/tinyencrypt/TinyEncryptMain.java b/src/main/java/me/hatter/tools/tinyencrypt/TinyEncryptMain.java index 42865f0..59a7ad4 100644 --- a/src/main/java/me/hatter/tools/tinyencrypt/TinyEncryptMain.java +++ b/src/main/java/me/hatter/tools/tinyencrypt/TinyEncryptMain.java @@ -105,6 +105,11 @@ public class TinyEncryptMain { sb.append("Enc file created: ") .append(new Date(meta.getCreated())) .append("\n"); + if (StringUtil.isNotBlank(meta.getPgpEnvelop())) { + sb.append("PGP envelop: YES\n"); + } else { + sb.append("PGP envelop: NO\n"); + } sb.append("Agent: ").append(meta.getUserAgent()); if (StringUtil.isNotBlank(meta.getComment())) { sb.append("Comment: ").append(meta.getComment()).append("\n"); @@ -189,10 +194,10 @@ public class TinyEncryptMain { result = EncryptedFileUtil.encryptFile(config, tinyEncryptArgs.key, f, tinyEncryptArgs.compress, tinyEncryptArgs.comment); } else { if (tinyEncryptArgs.showInWindow) { - EncryptedFileUtil.decryptInWindow(config, f); + EncryptedFileUtil.decryptInWindow(config, f, tinyEncryptArgs.pgp); result = false; // do not delete file } else if (tinyEncryptArgs.digest) { - Bytes sha256 = EncryptedFileUtil.decryptAndDigest(config, f); + Bytes sha256 = EncryptedFileUtil.decryptAndDigest(config, f, tinyEncryptArgs.pgp); if (sha256 != null) { log.info(sha256.asHex() + " - " + f); File clearTextFile = EncryptedFileUtil.getDecryptFile(f); @@ -208,7 +213,7 @@ public class TinyEncryptMain { } result = false; // do not delete file } else { - result = EncryptedFileUtil.decryptFile(config, f); + result = EncryptedFileUtil.decryptFile(config, f, tinyEncryptArgs.pgp); } } if (result && tinyEncryptArgs.removeFile) { diff --git a/src/main/java/me/hatter/tools/tinyencrypt/config/TinyEncryptConfig.java b/src/main/java/me/hatter/tools/tinyencrypt/config/TinyEncryptConfig.java index a71bd75..3c9bee9 100644 --- a/src/main/java/me/hatter/tools/tinyencrypt/config/TinyEncryptConfig.java +++ b/src/main/java/me/hatter/tools/tinyencrypt/config/TinyEncryptConfig.java @@ -4,6 +4,8 @@ public class TinyEncryptConfig { private String defaultKeyName; private String localPublicKeyPem; private String localPrivateKeyPem; + private String pgpEncryptPublicKeyPem; + private String pgpDecryptCmd; public String getDefaultKeyName() { return defaultKeyName; @@ -28,4 +30,20 @@ public class TinyEncryptConfig { public void setLocalPrivateKeyPem(String localPrivateKeyPem) { this.localPrivateKeyPem = localPrivateKeyPem; } + + public String getPgpEncryptPublicKeyPem() { + return pgpEncryptPublicKeyPem; + } + + public void setPgpEncryptPublicKeyPem(String pgpEncryptPublicKeyPem) { + this.pgpEncryptPublicKeyPem = pgpEncryptPublicKeyPem; + } + + public String getPgpDecryptCmd() { + return pgpDecryptCmd; + } + + public void setPgpDecryptCmd(String pgpDecryptCmd) { + this.pgpDecryptCmd = pgpDecryptCmd; + } } diff --git a/src/main/java/me/hatter/tools/tinyencrypt/config/TinyEncryptConstant.java b/src/main/java/me/hatter/tools/tinyencrypt/config/TinyEncryptConstant.java index b749fae..15f4428 100644 --- a/src/main/java/me/hatter/tools/tinyencrypt/config/TinyEncryptConstant.java +++ b/src/main/java/me/hatter/tools/tinyencrypt/config/TinyEncryptConstant.java @@ -1,7 +1,7 @@ package me.hatter.tools.tinyencrypt.config; public class TinyEncryptConstant { - public static final String VERSION = "0.3.6"; + public static final String VERSION = "0.3.7"; public static final String ENC_FILE_EXT = ".tinyenc"; } diff --git a/src/main/java/me/hatter/tools/tinyencrypt/encrypt/EncryptedFileUtil.java b/src/main/java/me/hatter/tools/tinyencrypt/encrypt/EncryptedFileUtil.java index a58fe13..8571256 100644 --- a/src/main/java/me/hatter/tools/tinyencrypt/encrypt/EncryptedFileUtil.java +++ b/src/main/java/me/hatter/tools/tinyencrypt/encrypt/EncryptedFileUtil.java @@ -1,15 +1,19 @@ package me.hatter.tools.tinyencrypt.encrypt; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; import me.hatter.tools.commons.assertion.AssertUtil; import me.hatter.tools.commons.bytes.Bytes; import me.hatter.tools.commons.io.DefaultRollCounter; import me.hatter.tools.commons.io.DigestOutputStream; import me.hatter.tools.commons.io.IOUtil; +import me.hatter.tools.commons.io.RFile; import me.hatter.tools.commons.log.LogTool; import me.hatter.tools.commons.log.LogTools; import me.hatter.tools.commons.security.crypt.CryptInputStream; import me.hatter.tools.commons.security.crypt.CryptOutputStream; import me.hatter.tools.commons.security.digest.Digests; +import me.hatter.tools.commons.string.StringUtil; import me.hatter.tools.commons.tlv.Tlv; import me.hatter.tools.commons.tlv.TlvUtil; import me.hatter.tools.tinyencrypt.config.TinyEncryptConfig; @@ -25,7 +29,7 @@ import java.util.zip.GZIPOutputStream; public class EncryptedFileUtil { private static final LogTool log = LogTools.getLogTool(EncryptedFileUtil.class); - public static boolean decryptToOutputStream(TinyEncryptConfig config, File file, OutputStream os) { + public static boolean decryptToOutputStream(TinyEncryptConfig config, File file, OutputStream os, boolean pgp) { if (getDecryptFile(file) == null) { log.warn("File is not tinyenc file, skip: " + file); return false; @@ -34,7 +38,50 @@ public class EncryptedFileUtil { try (FileInputStream fis = new FileInputStream(file)) { Tlv tlv = TlvUtil.readTlv(fis); TinyEncryptMeta meta = tlv.getValueAsBytes().asJSONObject(TinyEncryptMeta.class); - byte[] dataKey = TinyEncryptMetaUtil.decryptDataKey(config, meta); + + byte[] dataKey; + if (pgp) { + if (StringUtil.isBlank(meta.getPgpEnvelop())) { + log.error("File is not encrypted with PGP envelop"); + return false; + } + if (StringUtil.isBlank(config.getPgpDecryptCmd())) { + log.error("PGP decrypt cmd is not configed"); + return false; + } + if (RFile.from(config.getPgpDecryptCmd()).isNotFile()) { + log.error("PGP decrypt cmd is miss configed"); + return false; + } + System.out.print("Input PGP PIN: "); + char[] pin = System.console().readPassword(); + if (pin.length < 6) { + log.error("PIN must have 6 letters"); + return false; + } + ProcessBuilder pb = new ProcessBuilder( + config.getPgpDecryptCmd(), + "pgp-card-decrypt", + "--cipher-base64", meta.getPgpEnvelop(), + "--pass", new String(pin), + "--json"); + log.info("Start: " + config.getPgpDecryptCmd()); + log.debug("Start process: " + pb.command()); + Process p = pb.start(); + p.waitFor(); + try { + byte[] jsonBytes = IOUtil.readToBytes(p.getInputStream()); + String jsonStr = new String(jsonBytes, StandardCharsets.UTF_8); + JSONObject jo = JSON.parseObject(jsonStr); + dataKey = Bytes.fromHex(jo.getString("text_hex")).bytes(); + } catch (Exception e) { + log.error("Error in parse pgp-card-decrypt: " + e, e); + log.error("err out: " + Bytes.from(IOUtil.readToBytes(p.getErrorStream()))); + return false; + } + } else { + dataKey = TinyEncryptMetaUtil.decryptDataKey(config, meta); + } meta.setDataKey(dataKey); try (InputStream newIs = getDecryptInputStream(fis, meta)) { boolean isCompressed = (meta.getCompress() != null) && meta.getCompress(); @@ -56,24 +103,24 @@ public class EncryptedFileUtil { } } - public static Bytes decryptAndDigest(TinyEncryptConfig config, File file) { + public static Bytes decryptAndDigest(TinyEncryptConfig config, File file, boolean pgp) { DigestOutputStream outputStream = new DigestOutputStream(new NilOutputStream(), Digests.sha256()); - if (!decryptToOutputStream(config, file, outputStream)) { + if (!decryptToOutputStream(config, file, outputStream, pgp)) { return null; } return outputStream.digest(); } - public static void decryptInWindow(TinyEncryptConfig config, File file) { + public static void decryptInWindow(TinyEncryptConfig config, File file, boolean pgp) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); - decryptToOutputStream(config, file, baos); + decryptToOutputStream(config, file, baos, pgp); SwingWindow.create("Decrypted file: " + file.getName()) .message("File: " + file) .text(new String(baos.toByteArray(), StandardCharsets.UTF_8)) .show().getResult(); } - public static boolean decryptFile(TinyEncryptConfig config, File file) { + public static boolean decryptFile(TinyEncryptConfig config, File file, boolean pgp) { File decFile = getDecryptFile(file); if (decFile == null) { log.warn("File is not tinyenc file, skip: " + decFile); @@ -84,10 +131,20 @@ public class EncryptedFileUtil { return false; } try { + boolean decryptResult; try (FileOutputStream fos = new FileOutputStream(decFile)) { - return decryptToOutputStream(config, file, fos); + decryptResult = decryptToOutputStream(config, file, fos, pgp); } + if (!decryptResult) { + if (decFile.length() == 0) { + decFile.delete(); + } + } + return decryptResult; } catch (Exception e) { + if (decFile.length() == 0) { + decFile.delete(); + } log.error("Decrypt file filed: " + file + ", reason: " + e.getMessage()); log.debug("Decrypt file filed: " + file + ", reason: " + e.getMessage(), e); return false; diff --git a/src/main/java/me/hatter/tools/tinyencrypt/encrypt/TinyEncryptMeta.java b/src/main/java/me/hatter/tools/tinyencrypt/encrypt/TinyEncryptMeta.java index 4c75423..9c36022 100644 --- a/src/main/java/me/hatter/tools/tinyencrypt/encrypt/TinyEncryptMeta.java +++ b/src/main/java/me/hatter/tools/tinyencrypt/encrypt/TinyEncryptMeta.java @@ -7,6 +7,7 @@ public class TinyEncryptMeta { private long created; private String userAgent; private String comment; + private String pgpEnvelop; private String envelop; @JSONField(serialize = false) private byte[] dataKey; @@ -47,6 +48,14 @@ public class TinyEncryptMeta { this.comment = comment; } + public String getPgpEnvelop() { + return pgpEnvelop; + } + + public void setPgpEnvelop(String pgpEnvelop) { + this.pgpEnvelop = pgpEnvelop; + } + public String getEnvelop() { return envelop; } diff --git a/src/main/java/me/hatter/tools/tinyencrypt/encrypt/TinyEncryptMetaUtil.java b/src/main/java/me/hatter/tools/tinyencrypt/encrypt/TinyEncryptMetaUtil.java index f1fca8d..8599857 100644 --- a/src/main/java/me/hatter/tools/tinyencrypt/encrypt/TinyEncryptMetaUtil.java +++ b/src/main/java/me/hatter/tools/tinyencrypt/encrypt/TinyEncryptMetaUtil.java @@ -10,12 +10,15 @@ import me.hatter.tools.commons.network.HttpRequest; import me.hatter.tools.commons.os.OSUtil; import me.hatter.tools.commons.security.key.KeyUtil; import me.hatter.tools.commons.security.random.RandomTool; +import me.hatter.tools.commons.security.rsa.RSAUtil; import me.hatter.tools.commons.security.sign.Signatures; +import me.hatter.tools.commons.string.StringUtil; import me.hatter.tools.tinyencrypt.config.TinyEncryptConfig; import me.hatter.tools.tinyencrypt.config.TinyEncryptConstant; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.interfaces.RSAPublicKey; import java.util.ArrayList; import java.util.Base64; import java.util.List; @@ -61,6 +64,10 @@ public class TinyEncryptMetaUtil { public static TinyEncryptMeta create(TinyEncryptConfig config, String comment) { PublicKey publicKey = KeyUtil.parsePublicKeyPEM(config.getLocalPublicKeyPem()); PrivateKey privateKey = KeyUtil.parsePrivateKeyPEM(config.getLocalPrivateKeyPem()); + PublicKey pgpEncryptPublicKey = null; + if (StringUtil.isNotBlank(config.getPgpEncryptPublicKeyPem())) { + pgpEncryptPublicKey = KeyUtil.parsePublicKeyPEM(config.getPgpEncryptPublicKeyPem()); + } String name = config.getDefaultKeyName(); String timestamp = String.valueOf(System.currentTimeMillis()); @@ -90,6 +97,14 @@ public class TinyEncryptMetaUtil { tinyEncryptMeta.setCreated(System.currentTimeMillis()); tinyEncryptMeta.setDataKey(dataKey); tinyEncryptMeta.setEnvelop(envelop); + if (pgpEncryptPublicKey != null) { + if (pgpEncryptPublicKey instanceof RSAPublicKey) { + byte[] pgpEnvelop = RSAUtil.encrypt((RSAPublicKey) pgpEncryptPublicKey, dataKey); + tinyEncryptMeta.setPgpEnvelop(Base64.getEncoder().encodeToString(pgpEnvelop)); + } else { + log.warn("PGP encrypt public key is not RSAPublicKey: " + pgpEncryptPublicKey.getClass()); + } + } tinyEncryptMeta.setNonce(RandomTool.secureRandom().nextbytes(12)); tinyEncryptMeta.setUserAgent("TinyEncrypt v" + TinyEncryptConstant.VERSION + "@" + OSUtil.getCurrentOS().name()); tinyEncryptMeta.setComment(comment);