226 lines
10 KiB
Java
226 lines
10 KiB
Java
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;
|
|
import me.hatter.tools.tinyencrypt.config.TinyEncryptConstant;
|
|
import me.hatter.tools.tinyencrypt.util.NilOutputStream;
|
|
import me.hatter.tools.tinyencrypt.util.SwingWindow;
|
|
|
|
import java.io.*;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.zip.GZIPInputStream;
|
|
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) {
|
|
if (getDecryptFile(file) == null) {
|
|
log.warn("File is not tinyenc file, skip: " + file);
|
|
return false;
|
|
}
|
|
try {
|
|
try (FileInputStream fis = new FileInputStream(file)) {
|
|
Tlv tlv = TlvUtil.readTlv(fis);
|
|
TinyEncryptMeta meta = tlv.getValueAsBytes().asJSONObject(TinyEncryptMeta.class);
|
|
|
|
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();
|
|
log.info("Finished command");
|
|
try {
|
|
byte[] jsonBytes = IOUtil.readToBytes(p.getInputStream());
|
|
String jsonStr = new String(jsonBytes, StandardCharsets.UTF_8);
|
|
if (log.isDebugEnable()) {
|
|
log.debug("Read cmd JSON: " + jsonStr);
|
|
}
|
|
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();
|
|
if (isCompressed) {
|
|
GZIPInputStream gzIs = new GZIPInputStream(newIs);
|
|
IOUtil.copy(gzIs, os, new DefaultRollCounter().prefix("Decrypting, "));
|
|
} else {
|
|
IOUtil.copy(newIs, os, new DefaultRollCounter().prefix("Decrypting, "));
|
|
}
|
|
os.flush();
|
|
}
|
|
}
|
|
log.info("Decrypt file success: " + file);
|
|
return true;
|
|
} catch (Exception e) {
|
|
log.error("Decrypt file filed: " + file + ", reason: " + e.getMessage());
|
|
log.debug("Decrypt file filed: " + file + ", reason: " + e.getMessage(), e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static Bytes decryptAndDigest(TinyEncryptConfig config, File file, boolean pgp) {
|
|
DigestOutputStream outputStream = new DigestOutputStream(new NilOutputStream(), Digests.sha256());
|
|
if (!decryptToOutputStream(config, file, outputStream, pgp)) {
|
|
return null;
|
|
}
|
|
return outputStream.digest();
|
|
}
|
|
|
|
public static void decryptInWindow(TinyEncryptConfig config, File file, boolean pgp) {
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
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, boolean pgp) {
|
|
File decFile = getDecryptFile(file);
|
|
if (decFile == null) {
|
|
log.warn("File is not tinyenc file, skip: " + decFile);
|
|
return false;
|
|
}
|
|
if (decFile.exists()) {
|
|
log.warn("File exists, skip: " + decFile);
|
|
return false;
|
|
}
|
|
try {
|
|
boolean decryptResult;
|
|
try (FileOutputStream fos = new FileOutputStream(decFile)) {
|
|
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;
|
|
}
|
|
}
|
|
|
|
public static boolean encryptFile(TinyEncryptConfig config, String keyName, File file, boolean compress, String comment) {
|
|
File encFile = getEncryptFile(file);
|
|
if (encFile == null) {
|
|
log.warn("Cannot encrypt .tinyenc file: " + file);
|
|
return false;
|
|
}
|
|
if (encFile.exists()) {
|
|
log.warn("File exists, skip: " + encFile);
|
|
return false;
|
|
}
|
|
try {
|
|
TinyEncryptMeta meta = TinyEncryptMetaUtil.create(config, comment);
|
|
meta.setFileLength(file.length());
|
|
meta.setFileLastModified(file.lastModified());
|
|
meta.setCompress(compress);
|
|
Tlv tlv = TlvUtil.create(1, TinyEncryptMetaUtil.toString(meta));
|
|
|
|
try (FileInputStream fis = new FileInputStream(file)) {
|
|
try (FileOutputStream fos = new FileOutputStream(encFile)) {
|
|
TlvUtil.writeTlv(fos, tlv);
|
|
fos.flush();
|
|
try (OutputStream newOs = getEncryptOutputStream(fos, meta)) {
|
|
if (compress) {
|
|
GZIPOutputStream gzOs = new GZIPOutputStream(newOs);
|
|
IOUtil.copy(fis, gzOs, new DefaultRollCounter().prefix("Encrypting, "));
|
|
gzOs.finish();
|
|
} else {
|
|
IOUtil.copy(fis, newOs, new DefaultRollCounter().prefix("Encrypting, "));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
log.info("Encrypt file success: " + file);
|
|
return true;
|
|
} catch (Exception e) {
|
|
log.error("Encrypt file filed: " + file + ", reason: " + e.getMessage());
|
|
log.debug("Encrypt file filed: " + file + ", reason: " + e.getMessage(), e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static File getEncryptFile(File file) {
|
|
File absFile = file.getAbsoluteFile();
|
|
if (absFile.getName().endsWith(TinyEncryptConstant.ENC_FILE_EXT)) {
|
|
return null;
|
|
}
|
|
return new File(absFile.getParent(), absFile.getName() + TinyEncryptConstant.ENC_FILE_EXT);
|
|
}
|
|
|
|
public static File getDecryptFile(File file) {
|
|
File absFile = file.getAbsoluteFile();
|
|
String fn = absFile.getName();
|
|
if (!fn.endsWith(TinyEncryptConstant.ENC_FILE_EXT)) {
|
|
return null;
|
|
}
|
|
return new File(absFile.getParent(), fn.substring(0, fn.length() - TinyEncryptConstant.ENC_FILE_EXT.length()));
|
|
}
|
|
|
|
private static OutputStream getEncryptOutputStream(OutputStream out, TinyEncryptMeta tinyEncryptMeta) {
|
|
return CryptOutputStream.gcmEncrypt(out, tinyEncryptMeta.getDataKey(), tinyEncryptMeta.getNonce());
|
|
}
|
|
|
|
private static InputStream getDecryptInputStream(InputStream is, TinyEncryptMeta tinyEncryptMeta) {
|
|
AssertUtil.notNull(tinyEncryptMeta.getDataKey(), "Data key cannot be null");
|
|
AssertUtil.notNull(tinyEncryptMeta.getNonce(), "Nonce cannot be null");
|
|
return CryptInputStream.gcmDecrypt(is, tinyEncryptMeta.getDataKey(), tinyEncryptMeta.getNonce());
|
|
}
|
|
}
|