feat: v0.7.0, add ecdh envelop
This commit is contained in:
@@ -70,6 +70,9 @@ public class TinyEncryptArgs {
|
||||
@CommandLine.Option(names = {"-A", "--age"}, description = "Decrypt use Age")
|
||||
boolean age = false;
|
||||
|
||||
@CommandLine.Option(names = {"--ecdh"}, description = "Decrypt use ECDH")
|
||||
boolean ecdh = false;
|
||||
|
||||
@CommandLine.Option(names = {"--use-jce"}, description = "Use JCE")
|
||||
boolean useJce = false;
|
||||
|
||||
|
||||
@@ -90,10 +90,10 @@ public class TinyEncryptMain {
|
||||
tinyEncryptArgs.comment, tinyEncryptArgs.encryptedComment);
|
||||
} else {
|
||||
if (tinyEncryptArgs.showInWindow || tinyEncryptArgs.editInWindow) {
|
||||
EncryptedFileUtil.decryptInWindow(config, f, tinyEncryptArgs.pgp, tinyEncryptArgs.age, tinyEncryptArgs.editInWindow);
|
||||
EncryptedFileUtil.decryptInWindow(config, f, tinyEncryptArgs.pgp, tinyEncryptArgs.age, tinyEncryptArgs.ecdh, tinyEncryptArgs.editInWindow);
|
||||
encryptOrDecryptSuccess = false; // do not delete file
|
||||
} else if (tinyEncryptArgs.digest) {
|
||||
final Bytes sha256 = EncryptedFileUtil.decryptAndDigest(config, f, tinyEncryptArgs.pgp, tinyEncryptArgs.age);
|
||||
final Bytes sha256 = EncryptedFileUtil.decryptAndDigest(config, f, tinyEncryptArgs.pgp, tinyEncryptArgs.age, tinyEncryptArgs.ecdh);
|
||||
if (sha256 != null) {
|
||||
log.info(sha256.asHex() + " - " + f);
|
||||
final File clearTextFile = EncryptedFileUtil.getDecryptFile(f);
|
||||
@@ -109,7 +109,7 @@ public class TinyEncryptMain {
|
||||
}
|
||||
encryptOrDecryptSuccess = false; // do not delete file
|
||||
} else {
|
||||
encryptOrDecryptSuccess = EncryptedFileUtil.decryptFile(config, f, tinyEncryptArgs.pgp, tinyEncryptArgs.age);
|
||||
encryptOrDecryptSuccess = EncryptedFileUtil.decryptFile(config, f, tinyEncryptArgs.pgp, tinyEncryptArgs.age, tinyEncryptArgs.ecdh);
|
||||
}
|
||||
}
|
||||
if (encryptOrDecryptSuccess && tinyEncryptArgs.removeFile) {
|
||||
|
||||
@@ -186,6 +186,7 @@ public class TinyEncryptMainUtil {
|
||||
sb.append(header("Envelops")).append("KMS: ").append(toYesOrNo(meta.getEnvelop()))
|
||||
.append(", PGP: ").append(toYesOrNo(meta.getPgpEnvelop()))
|
||||
.append(", Age: ").append(toYesOrNo(meta.getAgeEnvelop()))
|
||||
.append(", ECDH: ").append(toYesOrNo(meta.getEcdhEnvelop()))
|
||||
.append("\n");
|
||||
if (StringUtil.isNotBlank(meta.getPgpFingerprint())) {
|
||||
sb.append(header("PGP fingerprint")).append(meta.getPgpFingerprint()).append("\n");
|
||||
@@ -193,6 +194,9 @@ public class TinyEncryptMainUtil {
|
||||
if (StringUtil.isNotBlank(meta.getAgeRecipient())) {
|
||||
sb.append(header("Age recipient")).append(meta.getAgeRecipient()).append("\n");
|
||||
}
|
||||
if (StringUtil.isNotBlank(meta.getEcdhPoint())) {
|
||||
sb.append(header("ECDH point")).append(meta.getEcdhPoint()).append("\n");
|
||||
}
|
||||
if (StringUtil.isNotBlank(meta.getComment())) {
|
||||
sb.append(header("Comment")).append(meta.getComment()).append("\n");
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ public class TinyEncryptConfig {
|
||||
private String localPrivateKeyPemChallenge;
|
||||
private Boolean turnOffEnvelop;
|
||||
private String pgpEncryptPublicKeyPem;
|
||||
private String ecdhPublicKeyPoint;
|
||||
@Deprecated
|
||||
private String pgpDecryptCmd;
|
||||
private String cardCli;
|
||||
@@ -73,6 +74,14 @@ public class TinyEncryptConfig {
|
||||
this.pgpEncryptPublicKeyPem = pgpEncryptPublicKeyPem;
|
||||
}
|
||||
|
||||
public String getEcdhPublicKeyPoint() {
|
||||
return ecdhPublicKeyPoint;
|
||||
}
|
||||
|
||||
public void setEcdhPublicKeyPoint(String ecdhPublicKeyPoint) {
|
||||
this.ecdhPublicKeyPoint = ecdhPublicKeyPoint;
|
||||
}
|
||||
|
||||
public String getPgpDecryptCmd() {
|
||||
return pgpDecryptCmd;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package me.hatter.tools.tinyencrypt.config;
|
||||
|
||||
public class TinyEncryptConstant {
|
||||
public static final String VERSION = "0.6.4";
|
||||
public static final String VERSION = "0.7.0";
|
||||
|
||||
public static final String ENC_FILE_EXT = ".tinyenc";
|
||||
}
|
||||
|
||||
@@ -8,10 +8,14 @@ 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.misc.Base64s;
|
||||
import me.hatter.tools.commons.security.crypt.AESCryptTool;
|
||||
import me.hatter.tools.commons.security.crypt.CryptInputStream;
|
||||
import me.hatter.tools.commons.security.crypt.CryptOutputStream;
|
||||
import me.hatter.tools.commons.security.crypt.WrapKeyUtil;
|
||||
import me.hatter.tools.commons.security.digest.Digests;
|
||||
import me.hatter.tools.commons.security.key.KdfUtil;
|
||||
import me.hatter.tools.commons.security.key.KeyUtil;
|
||||
import me.hatter.tools.commons.string.StringUtil;
|
||||
import me.hatter.tools.commons.tlv.Tlv;
|
||||
import me.hatter.tools.commons.tlv.TlvUtil;
|
||||
@@ -24,6 +28,9 @@ import me.hatter.tools.tinyencrypt.util.SwingWindow;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.spec.ECPoint;
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@@ -36,6 +43,7 @@ public class EncryptedFileUtil {
|
||||
public static boolean decryptToOutputStream(TinyEncryptConfig config, File file, OutputStream os,
|
||||
boolean pgp,
|
||||
boolean age,
|
||||
boolean ecdh,
|
||||
AtomicReference<TinyEncryptMeta> metaRef) {
|
||||
if (getDecryptFile(file) == null) {
|
||||
log.warn("File is not tinyenc file, skip: " + file);
|
||||
@@ -86,6 +94,27 @@ public class EncryptedFileUtil {
|
||||
return false;
|
||||
}
|
||||
dataKey = dataKeyOpt.get();
|
||||
} else if (ecdh) {
|
||||
if (StringUtil.isBlank(meta.getEcdhEnvelop())) {
|
||||
log.error("File is not encrypted with ECDH envelop");
|
||||
return false;
|
||||
}
|
||||
final Optional<String> pinOpt = CardCliUtil.readUserPin();
|
||||
final String pin = pinOpt.orElse(null);
|
||||
final WrapKeyUtil.WrapKey wrapKey = WrapKeyUtil.WrapKey.parse(meta.getEcdhEnvelop());
|
||||
final PublicKey ecPublicKey = KeyUtil.parsePublicKeyBytes(Base64s.uriCompatible().decode(wrapKey.getHeader().getePubKey()));
|
||||
final ECPoint ecPoint = ((ECPublicKey) ecPublicKey).getW();
|
||||
final byte[] ecPointXBytes = ecPoint.getAffineX().toByteArray();
|
||||
final byte[] ecPointYBytes = ecPoint.getAffineY().toByteArray();
|
||||
final String ePublicKeyHex = "04"
|
||||
+ Bytes.from(ecPointXBytes).subBytes((ecPointXBytes.length == 33 && ecPointXBytes[0] == 0x00) ? 1 : 0).asHex()
|
||||
+ Bytes.from(ecPointYBytes).subBytes((ecPointYBytes.length == 33 && ecPointYBytes[0] == 0x00) ? 1 : 0).asHex();
|
||||
final Optional<byte[]> sharedSecretBytesOpt = CardCliUtil.ecdhSharedSecret(config.getCardCli(), pin, ePublicKeyHex);
|
||||
if (!sharedSecretBytesOpt.isPresent()) {
|
||||
return false;
|
||||
}
|
||||
final byte[] sharedSecretBytes = sharedSecretBytesOpt.get();
|
||||
dataKey = WrapKeyUtil.decryptEcdhP256((header) -> sharedSecretBytes, wrapKey);
|
||||
} else {
|
||||
dataKey = TinyEncryptMetaUtil.decryptDataKey(config, meta);
|
||||
}
|
||||
@@ -118,18 +147,18 @@ public class EncryptedFileUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static Bytes decryptAndDigest(TinyEncryptConfig config, File file, boolean pgp, boolean age) {
|
||||
public static Bytes decryptAndDigest(TinyEncryptConfig config, File file, boolean pgp, boolean age, boolean ecdh) {
|
||||
final DigestOutputStream outputStream = new DigestOutputStream(new NilOutputStream(), Digests.sha256());
|
||||
if (!decryptToOutputStream(config, file, outputStream, pgp, age, null)) {
|
||||
if (!decryptToOutputStream(config, file, outputStream, pgp, age, ecdh, null)) {
|
||||
return null;
|
||||
}
|
||||
return outputStream.digest();
|
||||
}
|
||||
|
||||
public static void decryptInWindow(TinyEncryptConfig config, File file, boolean pgp, boolean age, boolean editable) {
|
||||
public static void decryptInWindow(TinyEncryptConfig config, File file, boolean pgp, boolean age, boolean ecdh, boolean editable) {
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
final AtomicReference<TinyEncryptMeta> metaRef = new AtomicReference<>();
|
||||
final boolean decryptSuccess = decryptToOutputStream(config, file, baos, pgp, age, metaRef);
|
||||
final boolean decryptSuccess = decryptToOutputStream(config, file, baos, pgp, age, ecdh, metaRef);
|
||||
if (!decryptSuccess) {
|
||||
return;
|
||||
}
|
||||
@@ -162,7 +191,7 @@ public class EncryptedFileUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean decryptFile(TinyEncryptConfig config, File file, boolean pgp, boolean age) {
|
||||
public static boolean decryptFile(TinyEncryptConfig config, File file, boolean pgp, boolean age, boolean ecdh) {
|
||||
final File decFile = getDecryptFile(file);
|
||||
if (decFile == null) {
|
||||
log.warn("File is not tinyenc file, skip: " + decFile);
|
||||
@@ -176,7 +205,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, age, meta);
|
||||
decryptResult = decryptToOutputStream(config, file, fos, pgp, age, ecdh, meta);
|
||||
}
|
||||
if (!decryptResult) {
|
||||
if (decFile.length() == 0) {
|
||||
|
||||
@@ -10,6 +10,8 @@ public class TinyEncryptMeta {
|
||||
private String encryptedComment;
|
||||
private String pgpEnvelop;
|
||||
private String pgpFingerprint;
|
||||
private String ecdhEnvelop;
|
||||
private String ecdhPoint;
|
||||
private String ageEnvelop;
|
||||
private String ageRecipient;
|
||||
private String envelop;
|
||||
@@ -76,6 +78,22 @@ public class TinyEncryptMeta {
|
||||
this.pgpFingerprint = pgpFingerprint;
|
||||
}
|
||||
|
||||
public String getEcdhEnvelop() {
|
||||
return ecdhEnvelop;
|
||||
}
|
||||
|
||||
public void setEcdhEnvelop(String ecdhEnvelop) {
|
||||
this.ecdhEnvelop = ecdhEnvelop;
|
||||
}
|
||||
|
||||
public String getEcdhPoint() {
|
||||
return ecdhPoint;
|
||||
}
|
||||
|
||||
public void setEcdhPoint(String ecdhPoint) {
|
||||
this.ecdhPoint = ecdhPoint;
|
||||
}
|
||||
|
||||
public String getAgeEnvelop() {
|
||||
return ageEnvelop;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@ import me.hatter.tools.commons.log.LogTools;
|
||||
import me.hatter.tools.commons.network.HttpRequest;
|
||||
import me.hatter.tools.commons.os.OSUtil;
|
||||
import me.hatter.tools.commons.security.crypt.AESCryptTool;
|
||||
import me.hatter.tools.commons.security.crypt.WrapKeyUtil;
|
||||
import me.hatter.tools.commons.security.digest.Digests;
|
||||
import me.hatter.tools.commons.security.ec.ECUtil;
|
||||
import me.hatter.tools.commons.security.key.KeyUtil;
|
||||
import me.hatter.tools.commons.security.random.RandomTool;
|
||||
import me.hatter.tools.commons.security.rsa.RSAUtil;
|
||||
@@ -22,6 +24,7 @@ import me.hatter.tools.tinyencrypt.util.CardCliUtil;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.interfaces.ECPublicKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
@@ -138,6 +141,15 @@ public class TinyEncryptMetaUtil {
|
||||
tinyEncryptMeta.setAgeEnvelop(ageEnvelop.trim());
|
||||
tinyEncryptMeta.setAgeRecipient(config.getAgeRecipient());
|
||||
}
|
||||
if (StringUtil.isNotBlank(config.getEcdhPublicKeyPoint())) {
|
||||
final PublicKey ecPubliecKey = ECUtil.getEcPublicKey(ECUtil.CURVE_SECP256R1, Bytes.fromHex(config.getEcdhPublicKeyPoint()).bytes());
|
||||
final byte[] ecPointXBytes = ((ECPublicKey) ecPubliecKey).getW().getAffineX().toByteArray();
|
||||
final boolean startsWith00AndLen33 = (ecPointXBytes.length == 33 && ecPointXBytes[0] == 0x00);
|
||||
final String ecdhPoint = "02" + Bytes.from(ecPointXBytes).subBytes(startsWith00AndLen33 ? 1 : 0).asHex();
|
||||
final String ecdhEnvelop = WrapKeyUtil.encryptEcdhP256(null, ecPubliecKey, dataKey).toString();
|
||||
tinyEncryptMeta.setEcdhEnvelop(ecdhEnvelop);
|
||||
tinyEncryptMeta.setEcdhPoint(ecdhPoint);
|
||||
}
|
||||
tinyEncryptMeta.setNonce(RandomTool.secureRandom().nextbytes(12));
|
||||
tinyEncryptMeta.setUserAgent("TinyEncrypt v" + TinyEncryptConstant.VERSION + "@" + OSUtil.getCurrentOS().name());
|
||||
tinyEncryptMeta.setComment(comment);
|
||||
|
||||
@@ -10,16 +10,17 @@ import me.hatter.tools.commons.log.LogTools;
|
||||
import me.hatter.tools.commons.string.StringUtil;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
public class CardCliUtil {
|
||||
private static final LogTool log = LogTools.getLogTool(CardCliUtil.class);
|
||||
|
||||
public static Optional<String> readUserPin() {
|
||||
System.out.print("Input PGP user PIN: ");
|
||||
System.out.print("Please input user PIN: ");
|
||||
final char[] pin = System.console().readPassword();
|
||||
if (pin.length < 6) {
|
||||
log.error("User PIN must have 6 letters");
|
||||
log.warn("Input user PIN has " + pin.length + " char(s)");
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(new String(pin));
|
||||
@@ -80,6 +81,26 @@ public class CardCliUtil {
|
||||
return Optional.of(Bytes.fromHex(jo.getString("text_hex")).bytes());
|
||||
}
|
||||
|
||||
public static Optional<byte[]> ecdhSharedSecret(String cardCli, String pin, String ePublicKeyPointHex) {
|
||||
final ProcessBuilder pb = new ProcessBuilder(
|
||||
cardCli,
|
||||
"piv-ecdh",
|
||||
"--private",
|
||||
"--slot", "82",
|
||||
"--epk", ePublicKeyPointHex,
|
||||
"--json");
|
||||
if (StringUtil.isNotBlank(pin)) {
|
||||
pb.command().addAll(Arrays.asList("--pin", pin));
|
||||
}
|
||||
log.info("Start: " + cardCli);
|
||||
final Optional<String> outputsOpt = runProcess(pb);
|
||||
if (!outputsOpt.isPresent()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
final JSONObject jo = JSON.parseObject(outputsOpt.get());
|
||||
return Optional.of(Bytes.fromHex(jo.getString("shared_secret_hex")).bytes());
|
||||
}
|
||||
|
||||
public static Optional<String> runProcess(ProcessBuilder pb) {
|
||||
Process p = null;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user