feat: v0.6.1, supports age decrypt

This commit is contained in:
2023-03-11 20:32:15 +08:00
parent 61fa9216ef
commit 9fc6814366
4 changed files with 69 additions and 20 deletions

View File

@@ -1,5 +1,6 @@
package me.hatter.tools.tinyencrypt;
import com.alibaba.fastjson.JSON;
import me.hatter.tools.commons.bytes.Bytes;
import me.hatter.tools.commons.exception.JumpOutException;
import me.hatter.tools.commons.io.RFile;
@@ -89,10 +90,10 @@ public class TinyEncryptMain {
tinyEncryptArgs.comment, tinyEncryptArgs.encryptedComment);
} else {
if (tinyEncryptArgs.showInWindow || tinyEncryptArgs.editInWindow) {
EncryptedFileUtil.decryptInWindow(config, f, tinyEncryptArgs.pgp, tinyEncryptArgs.editInWindow);
EncryptedFileUtil.decryptInWindow(config, f, tinyEncryptArgs.pgp, tinyEncryptArgs.age, tinyEncryptArgs.editInWindow);
encryptOrDecryptSuccess = false; // do not delete file
} else if (tinyEncryptArgs.digest) {
final Bytes sha256 = EncryptedFileUtil.decryptAndDigest(config, f, tinyEncryptArgs.pgp);
final Bytes sha256 = EncryptedFileUtil.decryptAndDigest(config, f, tinyEncryptArgs.pgp, tinyEncryptArgs.age);
if (sha256 != null) {
log.info(sha256.asHex() + " - " + f);
final File clearTextFile = EncryptedFileUtil.getDecryptFile(f);
@@ -108,7 +109,7 @@ public class TinyEncryptMain {
}
encryptOrDecryptSuccess = false; // do not delete file
} else {
encryptOrDecryptSuccess = EncryptedFileUtil.decryptFile(config, f, tinyEncryptArgs.pgp);
encryptOrDecryptSuccess = EncryptedFileUtil.decryptFile(config, f, tinyEncryptArgs.pgp, tinyEncryptArgs.age);
}
}
if (encryptOrDecryptSuccess && tinyEncryptArgs.removeFile) {

View File

@@ -1,7 +1,7 @@
package me.hatter.tools.tinyencrypt.config;
public class TinyEncryptConstant {
public static final String VERSION = "0.6.0";
public static final String VERSION = "0.6.1";
public static final String ENC_FILE_EXT = ".tinyenc";
}

View File

@@ -17,6 +17,7 @@ import me.hatter.tools.commons.tlv.Tlv;
import me.hatter.tools.commons.tlv.TlvUtil;
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 me.hatter.tools.tinyencrypt.util.NilOutputStream;
import me.hatter.tools.tinyencrypt.util.SwingWindow;
@@ -32,7 +33,10 @@ 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, boolean pgp, AtomicReference<TinyEncryptMeta> metaRef) {
public static boolean decryptToOutputStream(TinyEncryptConfig config, File file, OutputStream os,
boolean pgp,
boolean age,
AtomicReference<TinyEncryptMeta> metaRef) {
if (getDecryptFile(file) == null) {
log.warn("File is not tinyenc file, skip: " + file);
return false;
@@ -70,6 +74,17 @@ public class EncryptedFileUtil {
return false;
}
dataKey = dataKeyOpt.get();
} else if (age) {
if (StringUtil.isBlank(meta.getAgeEnvelop())) {
log.error("File is not encrypted with Age envelop");
return false;
}
final String ageCli = StringUtil.def(config.getAgeCli(), "age");
final Optional<byte[]> dataKeyOpt = AgeCliUtil.decryptBytes(ageCli, meta.getAgeRecipient(), meta.getAgeEnvelop());
if (!dataKeyOpt.isPresent()) {
return false;
}
dataKey = dataKeyOpt.get();
} else {
dataKey = TinyEncryptMetaUtil.decryptDataKey(config, meta);
}
@@ -102,18 +117,18 @@ public class EncryptedFileUtil {
}
}
public static Bytes decryptAndDigest(TinyEncryptConfig config, File file, boolean pgp) {
public static Bytes decryptAndDigest(TinyEncryptConfig config, File file, boolean pgp, boolean age) {
final DigestOutputStream outputStream = new DigestOutputStream(new NilOutputStream(), Digests.sha256());
if (!decryptToOutputStream(config, file, outputStream, pgp, null)) {
if (!decryptToOutputStream(config, file, outputStream, pgp, age, null)) {
return null;
}
return outputStream.digest();
}
public static void decryptInWindow(TinyEncryptConfig config, File file, boolean pgp, boolean editable) {
public static void decryptInWindow(TinyEncryptConfig config, File file, boolean pgp, boolean age, boolean editable) {
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final AtomicReference<TinyEncryptMeta> metaRef = new AtomicReference<>();
final boolean decryptSuccess = decryptToOutputStream(config, file, baos, pgp, metaRef);
final boolean decryptSuccess = decryptToOutputStream(config, file, baos, pgp, age, metaRef);
if (!decryptSuccess) {
return;
}
@@ -146,7 +161,7 @@ public class EncryptedFileUtil {
}
}
public static boolean decryptFile(TinyEncryptConfig config, File file, boolean pgp) {
public static boolean decryptFile(TinyEncryptConfig config, File file, boolean pgp, boolean age) {
final File decFile = getDecryptFile(file);
if (decFile == null) {
log.warn("File is not tinyenc file, skip: " + decFile);
@@ -160,7 +175,7 @@ public class EncryptedFileUtil {
final AtomicReference<TinyEncryptMeta> meta = new AtomicReference<>();
final boolean decryptResult;
try (FileOutputStream fos = new FileOutputStream(decFile)) {
decryptResult = decryptToOutputStream(config, file, fos, pgp, meta);
decryptResult = decryptToOutputStream(config, file, fos, pgp, age, meta);
}
if (!decryptResult) {
if (decFile.length() == 0) {

View File

@@ -1,34 +1,67 @@
package me.hatter.tools.tinyencrypt.util;
import me.hatter.tools.commons.assertion.AssertUtil;
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.misc.Base64s;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
public class AgeCliUtil {
private static final LogTool log = LogTools.getLogTool(AgeCliUtil.class);
public static void main(String[] args) {
public static void main(String[] args) throws Exception {
System.out.println(
encryptBytes(
"age",
"age1yubikey1qtwna67eqmyu7q9s3mpf7lkkrqzdrnqazdfdjftmv2qercy0cdchc7jcpu5",
"hello world".getBytes(StandardCharsets.UTF_8)
)
decryptBytes("age", "age1yubikey1qtwna67eqmyu7q9s3mpf7lkkrqzdrnqazdfdjftmv2qercy0cdchc7jcpu5", "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHBpdi1wMjU2IHFNWDVVUSBBNGMwMTl3\neXhQSXQ1dnhxSzFUNk4rNlJ6QkJCcnJqazZzTVAzcS9sSktyRQpBOXNQSVFudDBQ\nd2kyMFpySGxIUS8yemVwdkJxdEpoZHl3NTJuUzFaTjJJCi0tLSBwbW45LzUyem9j\nZWpROUFRT2huVDArS2hjaWFBeVd4S0xaTzh5eFo5QlhRCo2zDaVK/7YuOShVT0iw\n43LFOEP3T9v53YKhuUqjmm+6af93U2H/ppZVZluXrYPpR6+WHL4vdxflExP4yH1e\n8C+hFKSSdvxQe7cE8lHG\n-----END AGE ENCRYPTED FILE-----")
);
}
public static Optional<byte[]> decryptBytes(String ageCli, String ageRecipient, String ageEnvelop) {
AssertUtil.notEmpty(ageCli, "Age-cli cannot be empty");
AssertUtil.notEmpty(ageRecipient, "Age-recipient cannot be empty");
AssertUtil.isTrue(ageRecipient.matches("^[a-zA-Z0-9]+$"), "Age-recipient illegal");
AssertUtil.notEmpty(ageEnvelop, "Age-envelop cannot be empty");
AssertUtil.isFalse(ageEnvelop.contains("'"), "Age-envelop cannot contains `'`");
AssertUtil.isTrue(ageEnvelop.matches("^[\\-/+=\\n\\r\\sa-zA-Z0-9]+$"), "Age-envelop illegal");
final RFile recipientAgeFile = RFile.fromUserHome(".tinyencrypt/" + ageRecipient + ".age");
AssertUtil.isTrue(recipientAgeFile.isFile(), "Age key file required");
final ProcessBuilder pb = new ProcessBuilder(
"sh",
"-c",
"echo '" + ageEnvelop + "' | " + ageCli + " -d -i " + recipientAgeFile.file().getAbsolutePath());
pb.redirectInput(ProcessBuilder.Redirect.PIPE);
pb.redirectError(ProcessBuilder.Redirect.PIPE);
return CardCliUtil.runProcess(pb).map(b -> Base64s.normal().decode(b.trim()));
}
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");
AssertUtil.notNull(bytes, "Bytes cannot be null");
final ProcessBuilder pb = new ProcessBuilder(
"sh",
"-c", "echo " + Base64s.normal().encode(bytes) + " | " + ageCli + " -e -r " + ageRecipient + " -a");
"-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();
}
public static Optional<String> readUserPin() {
System.out.print("Input PIV user PIN: ");
final char[] pin = System.console().readPassword();
if (pin.length < 6) {
log.error("User PIN must have 6 letters");
return Optional.empty();
}
return Optional.of(new String(pin));
}
}