feat: v0.6.1, supports age decrypt
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
package me.hatter.tools.tinyencrypt;
|
package me.hatter.tools.tinyencrypt;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
import me.hatter.tools.commons.bytes.Bytes;
|
import me.hatter.tools.commons.bytes.Bytes;
|
||||||
import me.hatter.tools.commons.exception.JumpOutException;
|
import me.hatter.tools.commons.exception.JumpOutException;
|
||||||
import me.hatter.tools.commons.io.RFile;
|
import me.hatter.tools.commons.io.RFile;
|
||||||
@@ -89,10 +90,10 @@ public class TinyEncryptMain {
|
|||||||
tinyEncryptArgs.comment, tinyEncryptArgs.encryptedComment);
|
tinyEncryptArgs.comment, tinyEncryptArgs.encryptedComment);
|
||||||
} else {
|
} else {
|
||||||
if (tinyEncryptArgs.showInWindow || tinyEncryptArgs.editInWindow) {
|
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
|
encryptOrDecryptSuccess = false; // do not delete file
|
||||||
} else if (tinyEncryptArgs.digest) {
|
} 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) {
|
if (sha256 != null) {
|
||||||
log.info(sha256.asHex() + " - " + f);
|
log.info(sha256.asHex() + " - " + f);
|
||||||
final File clearTextFile = EncryptedFileUtil.getDecryptFile(f);
|
final File clearTextFile = EncryptedFileUtil.getDecryptFile(f);
|
||||||
@@ -108,7 +109,7 @@ public class TinyEncryptMain {
|
|||||||
}
|
}
|
||||||
encryptOrDecryptSuccess = false; // do not delete file
|
encryptOrDecryptSuccess = false; // do not delete file
|
||||||
} else {
|
} else {
|
||||||
encryptOrDecryptSuccess = EncryptedFileUtil.decryptFile(config, f, tinyEncryptArgs.pgp);
|
encryptOrDecryptSuccess = EncryptedFileUtil.decryptFile(config, f, tinyEncryptArgs.pgp, tinyEncryptArgs.age);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (encryptOrDecryptSuccess && tinyEncryptArgs.removeFile) {
|
if (encryptOrDecryptSuccess && tinyEncryptArgs.removeFile) {
|
||||||
|
|||||||
@@ -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.6.0";
|
public static final String VERSION = "0.6.1";
|
||||||
|
|
||||||
public static final String ENC_FILE_EXT = ".tinyenc";
|
public static final String ENC_FILE_EXT = ".tinyenc";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import me.hatter.tools.commons.tlv.Tlv;
|
|||||||
import me.hatter.tools.commons.tlv.TlvUtil;
|
import me.hatter.tools.commons.tlv.TlvUtil;
|
||||||
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 me.hatter.tools.tinyencrypt.util.NilOutputStream;
|
import me.hatter.tools.tinyencrypt.util.NilOutputStream;
|
||||||
import me.hatter.tools.tinyencrypt.util.SwingWindow;
|
import me.hatter.tools.tinyencrypt.util.SwingWindow;
|
||||||
@@ -32,7 +33,10 @@ import java.util.zip.GZIPOutputStream;
|
|||||||
public class EncryptedFileUtil {
|
public class EncryptedFileUtil {
|
||||||
private static final LogTool log = LogTools.getLogTool(EncryptedFileUtil.class);
|
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) {
|
if (getDecryptFile(file) == null) {
|
||||||
log.warn("File is not tinyenc file, skip: " + file);
|
log.warn("File is not tinyenc file, skip: " + file);
|
||||||
return false;
|
return false;
|
||||||
@@ -70,6 +74,17 @@ public class EncryptedFileUtil {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
dataKey = dataKeyOpt.get();
|
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 {
|
} else {
|
||||||
dataKey = TinyEncryptMetaUtil.decryptDataKey(config, meta);
|
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());
|
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 null;
|
||||||
}
|
}
|
||||||
return outputStream.digest();
|
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 ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
final AtomicReference<TinyEncryptMeta> metaRef = new AtomicReference<>();
|
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) {
|
if (!decryptSuccess) {
|
||||||
return;
|
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);
|
final File decFile = getDecryptFile(file);
|
||||||
if (decFile == null) {
|
if (decFile == null) {
|
||||||
log.warn("File is not tinyenc file, skip: " + decFile);
|
log.warn("File is not tinyenc file, skip: " + decFile);
|
||||||
@@ -160,7 +175,7 @@ public class EncryptedFileUtil {
|
|||||||
final AtomicReference<TinyEncryptMeta> meta = new AtomicReference<>();
|
final AtomicReference<TinyEncryptMeta> meta = new AtomicReference<>();
|
||||||
final boolean decryptResult;
|
final boolean decryptResult;
|
||||||
try (FileOutputStream fos = new FileOutputStream(decFile)) {
|
try (FileOutputStream fos = new FileOutputStream(decFile)) {
|
||||||
decryptResult = decryptToOutputStream(config, file, fos, pgp, meta);
|
decryptResult = decryptToOutputStream(config, file, fos, pgp, age, meta);
|
||||||
}
|
}
|
||||||
if (!decryptResult) {
|
if (!decryptResult) {
|
||||||
if (decFile.length() == 0) {
|
if (decFile.length() == 0) {
|
||||||
|
|||||||
@@ -1,34 +1,67 @@
|
|||||||
package me.hatter.tools.tinyencrypt.util;
|
package me.hatter.tools.tinyencrypt.util;
|
||||||
|
|
||||||
import me.hatter.tools.commons.assertion.AssertUtil;
|
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 me.hatter.tools.commons.misc.Base64s;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class AgeCliUtil {
|
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(
|
System.out.println(
|
||||||
encryptBytes(
|
decryptBytes("age", "age1yubikey1qtwna67eqmyu7q9s3mpf7lkkrqzdrnqazdfdjftmv2qercy0cdchc7jcpu5", "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHBpdi1wMjU2IHFNWDVVUSBBNGMwMTl3\neXhQSXQ1dnhxSzFUNk4rNlJ6QkJCcnJqazZzTVAzcS9sSktyRQpBOXNQSVFudDBQ\nd2kyMFpySGxIUS8yemVwdkJxdEpoZHl3NTJuUzFaTjJJCi0tLSBwbW45LzUyem9j\nZWpROUFRT2huVDArS2hjaWFBeVd4S0xaTzh5eFo5QlhRCo2zDaVK/7YuOShVT0iw\n43LFOEP3T9v53YKhuUqjmm+6af93U2H/ppZVZluXrYPpR6+WHL4vdxflExP4yH1e\n8C+hFKSSdvxQe7cE8lHG\n-----END AGE ENCRYPTED FILE-----")
|
||||||
"age",
|
|
||||||
"age1yubikey1qtwna67eqmyu7q9s3mpf7lkkrqzdrnqazdfdjftmv2qercy0cdchc7jcpu5",
|
|
||||||
"hello world".getBytes(StandardCharsets.UTF_8)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
public static String encryptBytes(String ageCli, String ageRecipient, byte[] bytes) {
|
||||||
AssertUtil.notEmpty(ageCli, "Age-cli cannot be empty");
|
AssertUtil.notEmpty(ageCli, "Age-cli cannot be empty");
|
||||||
AssertUtil.notEmpty(ageRecipient, "Age-recipient 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(
|
final ProcessBuilder pb = new ProcessBuilder(
|
||||||
"sh",
|
"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);
|
final Optional<String> outputsOpt = CardCliUtil.runProcess(pb);
|
||||||
if (!outputsOpt.isPresent()) {
|
if (!outputsOpt.isPresent()) {
|
||||||
throw new RuntimeException("Encrypt use age failed!");
|
throw new RuntimeException("Encrypt use age failed!");
|
||||||
}
|
}
|
||||||
return outputsOpt.get();
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user