diff --git a/README.md b/README.md index af1443c..cd2a4e1 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,21 @@ Tiny encrypt implemented by Java +Config `~/.tinyencrypt_config.json`: + +```json +{ + "ageRecipient": "age1***", + "cardCli": "/Users/hatterjiang/.cargo/bin/card-cli", + "pgpEncryptPublicKeyPem": "-----BEGIN PUBLIC KEY-----\n***\n-----END PUBLIC KEY-----", + "turnOffEnvelop": false, + "defaultKeyName": "prod_ec_key", + "localPrivateKeyPemChallenge": "***", + "localPrivateKeyPemEncrypted": "***", + "localPublicKeyPem": "-----BEGIN PUBLIC KEY-----\n***\n-----END PUBLIC KEY-----" +} +``` + Debug logging: ```shell diff --git a/TinyEncryptSpecV1.1.md b/TinyEncryptSpecV1.1.md index d02414f..2696c1a 100644 --- a/TinyEncryptSpecV1.1.md +++ b/TinyEncryptSpecV1.1.md @@ -12,22 +12,24 @@ File format: Meta format: -| Field | Type | Comment | -|---------|-----------|----------------------------------------------| -| version | String | Constant value: `1.1` | -| created | Long | Created time, Unix Epoch | -| userAgent | String | User Agent, e.g. `TinyEncrypt v0.5.1@MacOS` | -| comment | String | `optional` Plain text comment | +| Field | Type | Comment | +|------------------|-----------|----------------------------------------------| +| version | String | Constant value: `1.1` | +| created | Long | Created time, Unix Epoch | +| userAgent | String | User Agent, e.g. `TinyEncrypt v0.5.1@MacOS` | +| comment | String | `optional` Plain text comment | | encryptedComment | String | `optional` Encrypted comment | -| encryptedMeta | String | `optional` Encrypted Meta Data | -| pgpEnvelop | String | `deprecated` PGP Publickey Encrypted DataKey | -| pgpFingerprint | String | `deprecated` Hex(Sha256(PGP Publickey)) | -| envelop | String | `deprecated` KMS Encrypted DataKey | -| envelops | Envelop[] | Envelop Array | -| nonce | String | `base64` GCM Nonce | -| fileLength | Long | File Length | +| encryptedMeta | String | `optional` Encrypted Meta Data | +| pgpEnvelop | String | `deprecated` PGP Publickey Encrypted DataKey | +| pgpFingerprint | String | `deprecated` Hex(Sha256(PGP Publickey)) | +| ageEnvelop | String | `deprecated` PGP Publickey Encrypted DataKey | +| ageRecipient | String | `deprecated` Hex(Sha256(PGP Publickey)) | +| envelop | String | `deprecated` KMS Encrypted DataKey | +| envelops | Envelop[] | Envelop Array | +| nonce | String | `base64` GCM Nonce | +| fileLength | Long | File Length | | fileLastModified | Long | File Last Modified, Unix Epoch | -| compress | Boolean | Compressed or Not | +| compress | Boolean | Compressed or Not | Envelop format: diff --git a/src/main/java/me/hatter/tools/tinyencrypt/TinyEncryptArgs.java b/src/main/java/me/hatter/tools/tinyencrypt/TinyEncryptArgs.java index 3679d6e..35290a9 100644 --- a/src/main/java/me/hatter/tools/tinyencrypt/TinyEncryptArgs.java +++ b/src/main/java/me/hatter/tools/tinyencrypt/TinyEncryptArgs.java @@ -5,7 +5,16 @@ import picocli.CommandLine; import java.io.File; -@CommandLine.Command(name = "tiny-encrypt", version = "tiny-encrypt v" + TinyEncryptConstant.VERSION) +@CommandLine.Command(name = "tiny-encrypt", version = "tiny-encrypt v" + TinyEncryptConstant.VERSION, description = "\n" + + "Samples:\n" + + "Encrypt file: tiny-encrypt -e test.txt\n" + + "Decrypt file: tiny-encrypt -d test.txt.tinyenc [--pgp|--age]\n" + + "Decrypt and show file: tiny-encrypt -d --show test.txt.tinyenc [--pgp|--age]\n" + + "Decrypt and edit file: tiny-encrypt -d --edit test.txt.tinyenc [--pgp|--age]\n" + + "Show *.tinyenc file info: tiny-encrypt -I test.txt.tinyenc\n" + + "Create encrypted file: tiny-encrypt --create test.txt\n" + + "\n" + + "Argument details:") public class TinyEncryptArgs { @CommandLine.Option(names = {"-e", "--encrypt"}, description = "Encrypt file") boolean encrypt = false; @@ -40,6 +49,9 @@ public class TinyEncryptArgs { @CommandLine.Option(names = {"--skip-envelop"}, description = "Skip envelop data key") boolean skipEnvelop = false; + @CommandLine.Option(names = {"--turn-on-envelop"}, description = "Turn on envelop data key") + boolean turnOnEnvelop = false; + @CommandLine.Option(names = {"--require-sign"}, description = "Require signature when create data key") boolean requireSign = false; @@ -55,6 +67,9 @@ public class TinyEncryptArgs { @CommandLine.Option(names = {"-P", "--pgp"}, description = "Decrypt use PGP") boolean pgp = false; + @CommandLine.Option(names = {"-A", "--age"}, description = "Decrypt use Age") + boolean age = false; + @CommandLine.Option(names = {"--use-jce"}, description = "Use JCE") boolean useJce = false; diff --git a/src/main/java/me/hatter/tools/tinyencrypt/TinyEncryptMain.java b/src/main/java/me/hatter/tools/tinyencrypt/TinyEncryptMain.java index c86c12b..eebe557 100644 --- a/src/main/java/me/hatter/tools/tinyencrypt/TinyEncryptMain.java +++ b/src/main/java/me/hatter/tools/tinyencrypt/TinyEncryptMain.java @@ -82,8 +82,10 @@ public class TinyEncryptMain { } final boolean encryptOrDecryptSuccess; if (tinyEncryptArgs.encrypt) { + final boolean turnOffEnvelop = config.getTurnOffEnvelop() != null && config.getTurnOffEnvelop(); + final boolean useEnvelop = tinyEncryptArgs.turnOnEnvelop || (!turnOffEnvelop && !tinyEncryptArgs.skipEnvelop); encryptOrDecryptSuccess = EncryptedFileUtil.encryptFile(config, tinyEncryptArgs.key, f, - tinyEncryptArgs.compress, !tinyEncryptArgs.skipEnvelop, tinyEncryptArgs.requireSign, + tinyEncryptArgs.compress, useEnvelop, tinyEncryptArgs.requireSign, tinyEncryptArgs.comment, tinyEncryptArgs.encryptedComment); } else { if (tinyEncryptArgs.showInWindow || tinyEncryptArgs.editInWindow) { diff --git a/src/main/java/me/hatter/tools/tinyencrypt/TinyEncryptMainUtil.java b/src/main/java/me/hatter/tools/tinyencrypt/TinyEncryptMainUtil.java index f6b006e..d97f155 100644 --- a/src/main/java/me/hatter/tools/tinyencrypt/TinyEncryptMainUtil.java +++ b/src/main/java/me/hatter/tools/tinyencrypt/TinyEncryptMainUtil.java @@ -138,10 +138,12 @@ public class TinyEncryptMainUtil { .editable(true) .show().getResult(); + final boolean turnOffEnvelop = config.getTurnOffEnvelop() != null && config.getTurnOffEnvelop(); + final boolean useEnvelop = tinyEncryptArgs.turnOnEnvelop || (!turnOffEnvelop && !tinyEncryptArgs.skipEnvelop); final byte[] bytes = editResult.getBytes(StandardCharsets.UTF_8); final TinyEncryptMeta meta = TinyEncryptMetaUtil.create(config, tinyEncryptArgs.key, tinyEncryptArgs.comment, tinyEncryptArgs.encryptedComment, - !tinyEncryptArgs.skipEnvelop, tinyEncryptArgs.requireSign); + useEnvelop, tinyEncryptArgs.requireSign); meta.setFileLength((long) bytes.length); meta.setCreated(System.currentTimeMillis()); meta.setFileLastModified(System.currentTimeMillis()); @@ -183,10 +185,14 @@ public class TinyEncryptMainUtil { sb.append(header("Enc file created")).append(new Date(meta.getCreated())).append("\n"); sb.append(header("Envelops")).append("KMS: ").append(toYesOrNo(meta.getEnvelop())) .append(", PGP: ").append(toYesOrNo(meta.getPgpEnvelop())) + .append(", Age: ").append(toYesOrNo(meta.getAgeEnvelop())) .append("\n"); if (StringUtil.isNotBlank(meta.getPgpFingerprint())) { sb.append(header("PGP fingerprint")).append(meta.getPgpFingerprint()).append("\n"); } + if (StringUtil.isNotBlank(meta.getAgeRecipient())) { + sb.append(header("Age recipient")).append(meta.getAgeRecipient()).append("\n"); + } if (StringUtil.isNotBlank(meta.getComment())) { sb.append(header("Comment")).append(meta.getComment()).append("\n"); } 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 4131de2..01173ec 100644 --- a/src/main/java/me/hatter/tools/tinyencrypt/config/TinyEncryptConfig.java +++ b/src/main/java/me/hatter/tools/tinyencrypt/config/TinyEncryptConfig.java @@ -8,10 +8,14 @@ public class TinyEncryptConfig { private String localPrivateKeyPem; private String localPrivateKeyPemEncrypted; private String localPrivateKeyPemChallenge; + private Boolean turnOffEnvelop; private String pgpEncryptPublicKeyPem; @Deprecated private String pgpDecryptCmd; private String cardCli; + private String ageRecipient; + // Optional, default "age" when `ageRecipient` presents + private String ageCli; public String getDefaultKeyName() { return defaultKeyName; @@ -53,6 +57,14 @@ public class TinyEncryptConfig { this.localPrivateKeyPemChallenge = localPrivateKeyPemChallenge; } + public Boolean getTurnOffEnvelop() { + return turnOffEnvelop; + } + + public void setTurnOffEnvelop(Boolean turnOffEnvelop) { + this.turnOffEnvelop = turnOffEnvelop; + } + public String getPgpEncryptPublicKeyPem() { return pgpEncryptPublicKeyPem; } @@ -76,4 +88,20 @@ public class TinyEncryptConfig { public void setCardCli(String cardCli) { this.cardCli = cardCli; } + + public String getAgeRecipient() { + return ageRecipient; + } + + public void setAgeRecipient(String ageRecipient) { + this.ageRecipient = ageRecipient; + } + + public String getAgeCli() { + return ageCli; + } + + public void setAgeCli(String ageCli) { + this.ageCli = ageCli; + } } 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 919c35c..d6a5ff4 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.5.3"; + public static final String VERSION = "0.6.0"; public static final String ENC_FILE_EXT = ".tinyenc"; } 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 6d6f958..612c87f 100644 --- a/src/main/java/me/hatter/tools/tinyencrypt/encrypt/TinyEncryptMeta.java +++ b/src/main/java/me/hatter/tools/tinyencrypt/encrypt/TinyEncryptMeta.java @@ -10,6 +10,8 @@ public class TinyEncryptMeta { private String encryptedComment; private String pgpEnvelop; private String pgpFingerprint; + private String ageEnvelop; + private String ageRecipient; private String envelop; @JSONField(serialize = false) private byte[] dataKey; @@ -74,6 +76,22 @@ public class TinyEncryptMeta { this.pgpFingerprint = pgpFingerprint; } + public String getAgeEnvelop() { + return ageEnvelop; + } + + public void setAgeEnvelop(String ageEnvelop) { + this.ageEnvelop = ageEnvelop; + } + + public String getAgeRecipient() { + return ageRecipient; + } + + public void setAgeRecipient(String ageRecipient) { + this.ageRecipient = ageRecipient; + } + 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 d58982d..495125e 100644 --- a/src/main/java/me/hatter/tools/tinyencrypt/encrypt/TinyEncryptMetaUtil.java +++ b/src/main/java/me/hatter/tools/tinyencrypt/encrypt/TinyEncryptMetaUtil.java @@ -17,6 +17,7 @@ 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 me.hatter.tools.tinyencrypt.util.AgeCliUtil; import me.hatter.tools.tinyencrypt.util.CardCliUtil; import java.security.PrivateKey; @@ -131,6 +132,12 @@ public class TinyEncryptMetaUtil { log.warn("PGP encrypt public key is not RSAPublicKey: " + pgpEncryptPublicKey.getClass()); } } + if (StringUtil.isNotBlank(config.getAgeRecipient())) { + final String ageCli = StringUtil.def(config.getAgeCli(), "age"); + final String ageEnvelop = AgeCliUtil.encryptBytes(ageCli, config.getAgeRecipient(), dataKey); + tinyEncryptMeta.setAgeEnvelop(ageEnvelop.trim()); + tinyEncryptMeta.setAgeRecipient(config.getAgeRecipient()); + } tinyEncryptMeta.setNonce(RandomTool.secureRandom().nextbytes(12)); tinyEncryptMeta.setUserAgent("TinyEncrypt v" + TinyEncryptConstant.VERSION + "@" + OSUtil.getCurrentOS().name()); tinyEncryptMeta.setComment(comment); diff --git a/src/main/java/me/hatter/tools/tinyencrypt/util/AgeCliUtil.java b/src/main/java/me/hatter/tools/tinyencrypt/util/AgeCliUtil.java new file mode 100644 index 0000000..2fe8434 --- /dev/null +++ b/src/main/java/me/hatter/tools/tinyencrypt/util/AgeCliUtil.java @@ -0,0 +1,34 @@ +package me.hatter.tools.tinyencrypt.util; + +import me.hatter.tools.commons.assertion.AssertUtil; +import me.hatter.tools.commons.misc.Base64s; + +import java.nio.charset.StandardCharsets; +import java.util.Optional; + +public class AgeCliUtil { + + public static void main(String[] args) { + System.out.println( + encryptBytes( + "age", + "age1yubikey1qtwna67eqmyu7q9s3mpf7lkkrqzdrnqazdfdjftmv2qercy0cdchc7jcpu5", + "hello world".getBytes(StandardCharsets.UTF_8) + ) + ); + } + + public static String encryptBytes(String ageCli, String ageRecipient, byte[] bytes) { + AssertUtil.notEmpty(ageCli, "Age-cli cannot be empty"); + AssertUtil.notEmpty(ageRecipient, "Age-recipient cannot be empty"); + AssertUtil.notNull(bytes, "Bytes cannot null"); + final ProcessBuilder pb = new ProcessBuilder( + "sh", + "-c", "echo " + Base64s.normal().encode(bytes) + " | " + ageCli + " -e -r " + ageRecipient + " -a"); + final Optional outputsOpt = CardCliUtil.runProcess(pb); + if (!outputsOpt.isPresent()) { + throw new RuntimeException("Encrypt use age failed!"); + } + return outputsOpt.get(); + } +}