feat: v0.6.0, add age support

This commit is contained in:
2023-03-11 16:40:31 +08:00
parent 6424d47878
commit d224bf6789
10 changed files with 145 additions and 18 deletions

View File

@@ -2,6 +2,21 @@
Tiny encrypt implemented by Java 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: Debug logging:
```shell ```shell

View File

@@ -12,22 +12,24 @@ File format:
Meta format: Meta format:
| Field | Type | Comment | | Field | Type | Comment |
|---------|-----------|----------------------------------------------| |------------------|-----------|----------------------------------------------|
| version | String | Constant value: `1.1` | | version | String | Constant value: `1.1` |
| created | Long | Created time, Unix Epoch | | created | Long | Created time, Unix Epoch |
| userAgent | String | User Agent, e.g. `TinyEncrypt v0.5.1@MacOS` | | userAgent | String | User Agent, e.g. `TinyEncrypt v0.5.1@MacOS` |
| comment | String | `optional` Plain text comment | | comment | String | `optional` Plain text comment |
| encryptedComment | String | `optional` Encrypted comment | | encryptedComment | String | `optional` Encrypted comment |
| encryptedMeta | String | `optional` Encrypted Meta Data | | encryptedMeta | String | `optional` Encrypted Meta Data |
| pgpEnvelop | String | `deprecated` PGP Publickey Encrypted DataKey | | pgpEnvelop | String | `deprecated` PGP Publickey Encrypted DataKey |
| pgpFingerprint | String | `deprecated` Hex(Sha256(PGP Publickey)) | | pgpFingerprint | String | `deprecated` Hex(Sha256(PGP Publickey)) |
| envelop | String | `deprecated` KMS Encrypted DataKey | | ageEnvelop | String | `deprecated` PGP Publickey Encrypted DataKey |
| envelops | Envelop[] | Envelop Array | | ageRecipient | String | `deprecated` Hex(Sha256(PGP Publickey)) |
| nonce | String | `base64` GCM Nonce | | envelop | String | `deprecated` KMS Encrypted DataKey |
| fileLength | Long | File Length | | envelops | Envelop[] | Envelop Array |
| nonce | String | `base64` GCM Nonce |
| fileLength | Long | File Length |
| fileLastModified | Long | File Last Modified, Unix Epoch | | fileLastModified | Long | File Last Modified, Unix Epoch |
| compress | Boolean | Compressed or Not | | compress | Boolean | Compressed or Not |
Envelop format: Envelop format:

View File

@@ -5,7 +5,16 @@ import picocli.CommandLine;
import java.io.File; 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 { public class TinyEncryptArgs {
@CommandLine.Option(names = {"-e", "--encrypt"}, description = "Encrypt file") @CommandLine.Option(names = {"-e", "--encrypt"}, description = "Encrypt file")
boolean encrypt = false; boolean encrypt = false;
@@ -40,6 +49,9 @@ public class TinyEncryptArgs {
@CommandLine.Option(names = {"--skip-envelop"}, description = "Skip envelop data key") @CommandLine.Option(names = {"--skip-envelop"}, description = "Skip envelop data key")
boolean skipEnvelop = false; 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") @CommandLine.Option(names = {"--require-sign"}, description = "Require signature when create data key")
boolean requireSign = false; boolean requireSign = false;
@@ -55,6 +67,9 @@ public class TinyEncryptArgs {
@CommandLine.Option(names = {"-P", "--pgp"}, description = "Decrypt use PGP") @CommandLine.Option(names = {"-P", "--pgp"}, description = "Decrypt use PGP")
boolean pgp = false; boolean pgp = false;
@CommandLine.Option(names = {"-A", "--age"}, description = "Decrypt use Age")
boolean age = false;
@CommandLine.Option(names = {"--use-jce"}, description = "Use JCE") @CommandLine.Option(names = {"--use-jce"}, description = "Use JCE")
boolean useJce = false; boolean useJce = false;

View File

@@ -82,8 +82,10 @@ public class TinyEncryptMain {
} }
final boolean encryptOrDecryptSuccess; final boolean encryptOrDecryptSuccess;
if (tinyEncryptArgs.encrypt) { 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, encryptOrDecryptSuccess = EncryptedFileUtil.encryptFile(config, tinyEncryptArgs.key, f,
tinyEncryptArgs.compress, !tinyEncryptArgs.skipEnvelop, tinyEncryptArgs.requireSign, tinyEncryptArgs.compress, useEnvelop, tinyEncryptArgs.requireSign,
tinyEncryptArgs.comment, tinyEncryptArgs.encryptedComment); tinyEncryptArgs.comment, tinyEncryptArgs.encryptedComment);
} else { } else {
if (tinyEncryptArgs.showInWindow || tinyEncryptArgs.editInWindow) { if (tinyEncryptArgs.showInWindow || tinyEncryptArgs.editInWindow) {

View File

@@ -138,10 +138,12 @@ public class TinyEncryptMainUtil {
.editable(true) .editable(true)
.show().getResult(); .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 byte[] bytes = editResult.getBytes(StandardCharsets.UTF_8);
final TinyEncryptMeta meta = TinyEncryptMetaUtil.create(config, tinyEncryptArgs.key, final TinyEncryptMeta meta = TinyEncryptMetaUtil.create(config, tinyEncryptArgs.key,
tinyEncryptArgs.comment, tinyEncryptArgs.encryptedComment, tinyEncryptArgs.comment, tinyEncryptArgs.encryptedComment,
!tinyEncryptArgs.skipEnvelop, tinyEncryptArgs.requireSign); useEnvelop, tinyEncryptArgs.requireSign);
meta.setFileLength((long) bytes.length); meta.setFileLength((long) bytes.length);
meta.setCreated(System.currentTimeMillis()); meta.setCreated(System.currentTimeMillis());
meta.setFileLastModified(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("Enc file created")).append(new Date(meta.getCreated())).append("\n");
sb.append(header("Envelops")).append("KMS: ").append(toYesOrNo(meta.getEnvelop())) sb.append(header("Envelops")).append("KMS: ").append(toYesOrNo(meta.getEnvelop()))
.append(", PGP: ").append(toYesOrNo(meta.getPgpEnvelop())) .append(", PGP: ").append(toYesOrNo(meta.getPgpEnvelop()))
.append(", Age: ").append(toYesOrNo(meta.getAgeEnvelop()))
.append("\n"); .append("\n");
if (StringUtil.isNotBlank(meta.getPgpFingerprint())) { if (StringUtil.isNotBlank(meta.getPgpFingerprint())) {
sb.append(header("PGP fingerprint")).append(meta.getPgpFingerprint()).append("\n"); 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())) { if (StringUtil.isNotBlank(meta.getComment())) {
sb.append(header("Comment")).append(meta.getComment()).append("\n"); sb.append(header("Comment")).append(meta.getComment()).append("\n");
} }

View File

@@ -8,10 +8,14 @@ public class TinyEncryptConfig {
private String localPrivateKeyPem; private String localPrivateKeyPem;
private String localPrivateKeyPemEncrypted; private String localPrivateKeyPemEncrypted;
private String localPrivateKeyPemChallenge; private String localPrivateKeyPemChallenge;
private Boolean turnOffEnvelop;
private String pgpEncryptPublicKeyPem; private String pgpEncryptPublicKeyPem;
@Deprecated @Deprecated
private String pgpDecryptCmd; private String pgpDecryptCmd;
private String cardCli; private String cardCli;
private String ageRecipient;
// Optional, default "age" when `ageRecipient` presents
private String ageCli;
public String getDefaultKeyName() { public String getDefaultKeyName() {
return defaultKeyName; return defaultKeyName;
@@ -53,6 +57,14 @@ public class TinyEncryptConfig {
this.localPrivateKeyPemChallenge = localPrivateKeyPemChallenge; this.localPrivateKeyPemChallenge = localPrivateKeyPemChallenge;
} }
public Boolean getTurnOffEnvelop() {
return turnOffEnvelop;
}
public void setTurnOffEnvelop(Boolean turnOffEnvelop) {
this.turnOffEnvelop = turnOffEnvelop;
}
public String getPgpEncryptPublicKeyPem() { public String getPgpEncryptPublicKeyPem() {
return pgpEncryptPublicKeyPem; return pgpEncryptPublicKeyPem;
} }
@@ -76,4 +88,20 @@ public class TinyEncryptConfig {
public void setCardCli(String cardCli) { public void setCardCli(String cardCli) {
this.cardCli = 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;
}
} }

View File

@@ -1,7 +1,7 @@
package me.hatter.tools.tinyencrypt.config; package me.hatter.tools.tinyencrypt.config;
public class TinyEncryptConstant { 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"; public static final String ENC_FILE_EXT = ".tinyenc";
} }

View File

@@ -10,6 +10,8 @@ public class TinyEncryptMeta {
private String encryptedComment; private String encryptedComment;
private String pgpEnvelop; private String pgpEnvelop;
private String pgpFingerprint; private String pgpFingerprint;
private String ageEnvelop;
private String ageRecipient;
private String envelop; private String envelop;
@JSONField(serialize = false) @JSONField(serialize = false)
private byte[] dataKey; private byte[] dataKey;
@@ -74,6 +76,22 @@ public class TinyEncryptMeta {
this.pgpFingerprint = pgpFingerprint; 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() { public String getEnvelop() {
return envelop; return envelop;
} }

View File

@@ -17,6 +17,7 @@ import me.hatter.tools.commons.security.sign.Signatures;
import me.hatter.tools.commons.string.StringUtil; import me.hatter.tools.commons.string.StringUtil;
import me.hatter.tools.tinyencrypt.config.TinyEncryptConfig; import me.hatter.tools.tinyencrypt.config.TinyEncryptConfig;
import me.hatter.tools.tinyencrypt.config.TinyEncryptConstant; import me.hatter.tools.tinyencrypt.config.TinyEncryptConstant;
import me.hatter.tools.tinyencrypt.util.AgeCliUtil;
import me.hatter.tools.tinyencrypt.util.CardCliUtil; import me.hatter.tools.tinyencrypt.util.CardCliUtil;
import java.security.PrivateKey; import java.security.PrivateKey;
@@ -131,6 +132,12 @@ public class TinyEncryptMetaUtil {
log.warn("PGP encrypt public key is not RSAPublicKey: " + pgpEncryptPublicKey.getClass()); 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.setNonce(RandomTool.secureRandom().nextbytes(12));
tinyEncryptMeta.setUserAgent("TinyEncrypt v" + TinyEncryptConstant.VERSION + "@" + OSUtil.getCurrentOS().name()); tinyEncryptMeta.setUserAgent("TinyEncrypt v" + TinyEncryptConstant.VERSION + "@" + OSUtil.getCurrentOS().name());
tinyEncryptMeta.setComment(comment); tinyEncryptMeta.setComment(comment);

View File

@@ -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<String> outputsOpt = CardCliUtil.runProcess(pb);
if (!outputsOpt.isPresent()) {
throw new RuntimeException("Encrypt use age failed!");
}
return outputsOpt.get();
}
}