Files
tiny-encrypt-java/src/main/java/me/hatter/tools/tinyencrypt/encrypt/EncryptedFileUtil.java
2021-07-21 00:04:49 +08:00

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());
}
}