Compare commits
49 Commits
324aee0ff6
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
8ad52e04b4
|
|||
|
90e5f06ce7
|
|||
|
edeff5c265
|
|||
|
abb2480b0d
|
|||
|
da45deccb9
|
|||
|
430bcbc9f2
|
|||
|
108ad3008b
|
|||
|
8334e835fc
|
|||
|
e3eb182873
|
|||
|
3f58992680
|
|||
|
9a6e5ff067
|
|||
|
cf32c86c0c
|
|||
|
88af928b20
|
|||
|
2009984976
|
|||
|
7e90e281b0
|
|||
|
ec26fcce2b
|
|||
|
0b90e4e8a4
|
|||
|
9fc6814366
|
|||
|
61fa9216ef
|
|||
|
ad85033444
|
|||
|
6f973cbd05
|
|||
|
72307b1dfd
|
|||
|
d224bf6789
|
|||
|
6424d47878
|
|||
|
2e63433a23
|
|||
|
84e6928701
|
|||
|
1aec5a61f3
|
|||
|
196a99825d
|
|||
|
a83e4674ad
|
|||
|
84748768e1
|
|||
|
5f2cd622d6
|
|||
|
a61618a46e
|
|||
|
bcc1379f7c
|
|||
|
2bca147e3d
|
|||
|
33aea5ae59
|
|||
|
e9d97a09b9
|
|||
|
f0e521142f
|
|||
|
7cebbfddaa
|
|||
|
f8cfb926f0
|
|||
|
7794adadb1
|
|||
|
284d2874d6
|
|||
|
7e74afe98c
|
|||
|
f9277a60a9
|
|||
|
2ca2c5241d
|
|||
|
948024bfd8
|
|||
| 9880b0c439 | |||
|
0a12ac717c
|
|||
|
1fa032ca06
|
|||
|
f1ed2bae7e
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
|
__tinyencrypt_config.json
|
||||||
|
*.tinyenc
|
||||||
out/
|
out/
|
||||||
build
|
build
|
||||||
classes
|
classes
|
||||||
|
|||||||
135
README.md
135
README.md
@@ -1,3 +1,138 @@
|
|||||||
# tiny-encrypt-java
|
# tiny-encrypt-java
|
||||||
|
|
||||||
Tiny encrypt implemented by Java
|
Tiny encrypt implemented by Java
|
||||||
|
|
||||||
|
Config `~/.tinyencrypt_config.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ageRecipient": "age1***",
|
||||||
|
"cardCli": "/Users/hatterjiang/.cargo/bin/card-cli",
|
||||||
|
"pgpEncryptPublicKeyPem": "-----BEGIN PUBLIC KEY-----\n***\n-----END PUBLIC KEY-----",
|
||||||
|
"turnOffEnvelop": false,
|
||||||
|
"defaultKeyName": "prod_ec_key",
|
||||||
|
"localPrivateKeyPemChallenge": "***",
|
||||||
|
"localPrivateKeyPemEncrypted": "***",
|
||||||
|
"localPublicKeyPem": "-----BEGIN PUBLIC KEY-----\n***\n-----END PUBLIC KEY-----"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Debug logging:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ java -jar -Dcommons.log.debug=true tiny-encrypt.jar ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Encrypt file:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ tiny-encrypt -e test.txt
|
||||||
|
[INFO] Start processing file: test.txt, 1 of 1
|
||||||
|
[INFO] Get data key from kms, key name: prod_ec_key, with sign: false ...
|
||||||
|
Encrypting, Processed: 12, Speed: 2.93KB/s
|
||||||
|
[INFO] Encrypt file success: test.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
> Skip envelop data key: `--skip-envelop`
|
||||||
|
|
||||||
|
Show encrypted file info:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ tiny-encrypt -I test.txt.tinyenc
|
||||||
|
[INFO] Tiny Encrypt File Info
|
||||||
|
File..............: test.txt.tinyenc
|
||||||
|
File size.........: 12 byte(s)
|
||||||
|
Enc file summary..: Version: 1.0, Agent: TinyEncrypt v0.5.1@MacOS
|
||||||
|
Last modified.....: Fri Feb 10 00:50:07 CST 2023
|
||||||
|
Enc file created..: Fri Feb 10 01:16:12 CST 2023
|
||||||
|
Envelops..........: KMS: YES, PGP: YES
|
||||||
|
PGP fingerprint...: de5a99c239a82adf039982cb6319abcb95f44cfc76a5027ae6f7819cfc5fde7c
|
||||||
|
Encrypted comment.: NO
|
||||||
|
```
|
||||||
|
|
||||||
|
Decrypt file:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ tiny-encrypt -d test.txt.tinyenc
|
||||||
|
[INFO] Start processing file: test.txt.tinyenc, 1 of 1
|
||||||
|
[INFO] Start: /Users/hatterjiang/.cargo/bin/card-cli
|
||||||
|
[INFO] Finished command
|
||||||
|
[INFO] Decrypt local private key success!
|
||||||
|
[INFO] Decrypt data key ...
|
||||||
|
Decrypting, Processed: 12, Speed: 3.91KB/s
|
||||||
|
[INFO] Decrypt file success: test.txt.tinyenc
|
||||||
|
```
|
||||||
|
|
||||||
|
Decrypt file with OpenPGP card:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ tiny-encrypt -d --pgp test.txt.tinyenc
|
||||||
|
[INFO] Start processing file: test.txt.tinyenc, 1 of 1
|
||||||
|
Input PGP user PIN:
|
||||||
|
[INFO] Start: /Users/hatterjiang/.cargo/bin/card-cli
|
||||||
|
[INFO] Finished command
|
||||||
|
Decrypting, Processed: 12, Speed: 2.93KB/s
|
||||||
|
[INFO] Decrypt file success: test.txt.tinyenc
|
||||||
|
```
|
||||||
|
|
||||||
|
Create file from text window:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ tiny-encrypt --create FILE
|
||||||
|
```
|
||||||
|
|
||||||
|
Show file in text window:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ tiny-encrypt -d --show FILE.tinyenc
|
||||||
|
```
|
||||||
|
|
||||||
|
Edit file in text window:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ tiny-encrypt -d --edit FILE.tinyenc
|
||||||
|
```
|
||||||
|
|
||||||
|
Use alias:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
alias tiny-encrypt='java -jar ~/bin/tiny-encrypt.jar'
|
||||||
|
alias tiny-encrypt-debug='java -jar -Dcommons.log.debug=true ~/bin/tiny-encrypt.jar'
|
||||||
|
```
|
||||||
|
|
||||||
|
File ext: `*.tinyenc`
|
||||||
|
|
||||||
|
File format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[TAG; 2 bytes; short; BE; Always 1]
|
||||||
|
[LENGTH; 4 bytes; int; BE]
|
||||||
|
[META; LENGTH bytes]
|
||||||
|
[ENCRYPTED_DATA; n bytes; AES/GCM]
|
||||||
|
```
|
||||||
|
|
||||||
|
Meta format:
|
||||||
|
|
||||||
|
| Field | Type | Comment |
|
||||||
|
|---------|---------|---------------------------------------------|
|
||||||
|
| version | String | Constant value: `1.0` |
|
||||||
|
| created | Long | Created time, Unix Epoch, in millis |
|
||||||
|
| userAgent | String | User Agent, e.g. `TinyEncrypt v0.6.0@MacOS` |
|
||||||
|
| comment | String | Plain text comment |
|
||||||
|
| encryptedComment | String | Encrypted comment |
|
||||||
|
| envelop | String | KMS encrypted data key |
|
||||||
|
| pgpEnvelop | String | PGP Publickey encrypted data key |
|
||||||
|
| pgpFingerprint | String | Hex(Sha256(PGP Publickey)) |
|
||||||
|
| ageEnvelop | String | Age encrypted data key |
|
||||||
|
| ageRecipient | String | Age recipient |
|
||||||
|
| ecdhEnvelop | String | ECDH encrypted data key |
|
||||||
|
| ecdhPoint | String | ECDH ephemeral public key |
|
||||||
|
| nonce | byte[] | GCM nonce |
|
||||||
|
| fileLength | Long | File length |
|
||||||
|
| fileLastModified | Long | File last modified, Unix Epoch, in millis |
|
||||||
|
| compress | Boolean | Compressed or not |
|
||||||
|
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
Tiny encrypt specification v1.1: [TinyEncryptSpecv1.1.md](https://github.com/OpenWebStandard/tiny-encrypt-format-spec/blob/main/TinyEncryptSpecv1.1.md)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
},
|
},
|
||||||
"repo": {
|
"repo": {
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
"me.hatter:commons:3.36",
|
"me.hatter:commons:3.56",
|
||||||
"info.picocli:picocli:4.6.1",
|
"info.picocli:picocli:4.6.1",
|
||||||
"org.bouncycastle:bcprov-jdk15on:1.69"
|
"org.bouncycastle:bcprov-jdk15on:1.69"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -5,11 +5,23 @@ import picocli.CommandLine;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
@CommandLine.Command(name = "tiny-encrypt", version = "tiny-encrypt v" + TinyEncryptConstant.VERSION)
|
@CommandLine.Command(name = "tiny-encrypt", version = "tiny-encrypt v" + TinyEncryptConstant.VERSION, description = "\n" +
|
||||||
|
"Samples:\n" +
|
||||||
|
"Encrypt file: tiny-encrypt -e test.txt\n" +
|
||||||
|
"Decrypt file: tiny-encrypt -d test.txt.tinyenc [--pgp|--age]\n" +
|
||||||
|
"Decrypt and show file: tiny-encrypt -d --show test.txt.tinyenc [--pgp|--age]\n" +
|
||||||
|
"Decrypt and edit file: tiny-encrypt -d --edit test.txt.tinyenc [--pgp|--age]\n" +
|
||||||
|
"Show *.tinyenc file info: tiny-encrypt -I test.txt.tinyenc\n" +
|
||||||
|
"Create encrypted file: tiny-encrypt --create test.txt\n" +
|
||||||
|
"\n" +
|
||||||
|
"Argument details:")
|
||||||
public class TinyEncryptArgs {
|
public class TinyEncryptArgs {
|
||||||
@CommandLine.Option(names = {"-e", "--encrypt"}, description = "Encrypt file")
|
@CommandLine.Option(names = {"-e", "--encrypt"}, description = "Encrypt file")
|
||||||
boolean encrypt = false;
|
boolean encrypt = false;
|
||||||
|
|
||||||
|
@CommandLine.Option(names = {"--create"}, description = "Create file")
|
||||||
|
boolean create = false;
|
||||||
|
|
||||||
@CommandLine.Option(names = {"-d", "--decrypt"}, description = "Decrypt file")
|
@CommandLine.Option(names = {"-d", "--decrypt"}, description = "Decrypt file")
|
||||||
boolean decrypt = false;
|
boolean decrypt = false;
|
||||||
|
|
||||||
@@ -19,15 +31,30 @@ public class TinyEncryptArgs {
|
|||||||
@CommandLine.Option(names = {"-s", "--show"}, description = "Show decrypted text in window")
|
@CommandLine.Option(names = {"-s", "--show"}, description = "Show decrypted text in window")
|
||||||
boolean showInWindow = false;
|
boolean showInWindow = false;
|
||||||
|
|
||||||
@CommandLine.Option(names = {"-k", "--key"}, description = "Encrypt key")
|
@CommandLine.Option(names = {"-t", "--edit"}, description = "Edit decrypted text in window")
|
||||||
|
boolean editInWindow = false;
|
||||||
|
|
||||||
|
@CommandLine.Option(names = {"-k", "--key"}, description = "Encrypt KMS key")
|
||||||
String key;
|
String key;
|
||||||
|
|
||||||
@CommandLine.Option(names = {"-c", "--comment"}, description = "Encrypt comment")
|
@CommandLine.Option(names = {"-c", "--comment"}, description = "Encrypt comment")
|
||||||
String comment;
|
String comment;
|
||||||
|
|
||||||
|
@CommandLine.Option(names = {"-E", "--encrypted-comment"}, description = "Encrypt comment")
|
||||||
|
String encryptedComment;
|
||||||
|
|
||||||
@CommandLine.Option(names = {"--compress"}, description = "Encrypt compress")
|
@CommandLine.Option(names = {"--compress"}, description = "Encrypt compress")
|
||||||
boolean compress = false;
|
boolean compress = false;
|
||||||
|
|
||||||
|
@CommandLine.Option(names = {"--skip-envelop"}, description = "Skip envelop data key")
|
||||||
|
boolean skipEnvelop = false;
|
||||||
|
|
||||||
|
@CommandLine.Option(names = {"--turn-on-envelop"}, description = "Turn on envelop data key")
|
||||||
|
boolean turnOnEnvelop = false;
|
||||||
|
|
||||||
|
@CommandLine.Option(names = {"--require-sign"}, description = "Require signature when create data key")
|
||||||
|
boolean requireSign = false;
|
||||||
|
|
||||||
@CommandLine.Option(names = {"-C", "--config"}, description = "Encrypt config")
|
@CommandLine.Option(names = {"-C", "--config"}, description = "Encrypt config")
|
||||||
File config;
|
File config;
|
||||||
|
|
||||||
@@ -37,9 +64,18 @@ public class TinyEncryptArgs {
|
|||||||
@CommandLine.Option(names = {"-I", "--info"}, description = "Encrypt file info")
|
@CommandLine.Option(names = {"-I", "--info"}, description = "Encrypt file info")
|
||||||
boolean fileInfo = false;
|
boolean fileInfo = false;
|
||||||
|
|
||||||
|
@CommandLine.Option(names = {"--info-json"}, description = "Encrypt file info JSON")
|
||||||
|
boolean fileInfoJson = false;
|
||||||
|
|
||||||
@CommandLine.Option(names = {"-P", "--pgp"}, description = "Decrypt use PGP")
|
@CommandLine.Option(names = {"-P", "--pgp"}, description = "Decrypt use PGP")
|
||||||
boolean pgp = false;
|
boolean pgp = false;
|
||||||
|
|
||||||
|
@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")
|
@CommandLine.Option(names = {"--use-jce"}, description = "Use JCE")
|
||||||
boolean useJce = false;
|
boolean useJce = false;
|
||||||
|
|
||||||
@@ -49,6 +85,9 @@ public class TinyEncryptArgs {
|
|||||||
@CommandLine.Option(names = {"--init-config"}, description = "Init encrypt config")
|
@CommandLine.Option(names = {"--init-config"}, description = "Init encrypt config")
|
||||||
boolean doInitConfig = false;
|
boolean doInitConfig = false;
|
||||||
|
|
||||||
|
@CommandLine.Option(names = {"--encrypt-config-local-private-key"}, description = "Do encrypt config local private key")
|
||||||
|
boolean doEncryptConfigLocalPrivateKey = false;
|
||||||
|
|
||||||
@CommandLine.Option(names = {"-h", "--help"}, usageHelp = true, description = "Display a help message")
|
@CommandLine.Option(names = {"-h", "--help"}, usageHelp = true, description = "Display a help message")
|
||||||
boolean helpRequested = false;
|
boolean helpRequested = false;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package me.hatter.tools.tinyencrypt;
|
||||||
|
|
||||||
|
import me.hatter.tools.commons.io.RFile;
|
||||||
|
import me.hatter.tools.commons.log.LogTool;
|
||||||
|
import me.hatter.tools.commons.log.LogTools;
|
||||||
|
import picocli.CommandLine;
|
||||||
|
|
||||||
|
public class TinyEncryptArgsUtil {
|
||||||
|
private static final LogTool log = LogTools.getLogTool(TinyEncryptArgsUtil.class);
|
||||||
|
private static final String DEFAULT_TINY_ENCRYPT_CONFIG = "~/.tinyencrypt_config.json";
|
||||||
|
|
||||||
|
public static TinyEncryptArgs parseTinyEncryptArgs(String[] args) {
|
||||||
|
try {
|
||||||
|
return innerParseTinyEncryptArgs(args);
|
||||||
|
} catch (CommandLine.UnmatchedArgumentException unmatchedArgumentException) {
|
||||||
|
log.error("Parse args failed: " + unmatchedArgumentException.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TinyEncryptArgs innerParseTinyEncryptArgs(String[] args) {
|
||||||
|
final TinyEncryptArgs tinyEncryptArgs = new TinyEncryptArgs();
|
||||||
|
final CommandLine cmd = new CommandLine(tinyEncryptArgs);
|
||||||
|
cmd.parseArgs(args);
|
||||||
|
|
||||||
|
if (cmd.isUsageHelpRequested()) {
|
||||||
|
cmd.usage(cmd.getOut());
|
||||||
|
return null;
|
||||||
|
} else if (cmd.isVersionHelpRequested()) {
|
||||||
|
cmd.printVersionHelp(cmd.getOut());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return tinyEncryptArgs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RFile getTinyEncryptConfigRFile(TinyEncryptArgs tinyEncryptArgs) {
|
||||||
|
if (tinyEncryptArgs.config != null) {
|
||||||
|
return RFile.from(tinyEncryptArgs.config);
|
||||||
|
} else {
|
||||||
|
return RFile.from(DEFAULT_TINY_ENCRYPT_CONFIG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package me.hatter.tools.tinyencrypt;
|
package me.hatter.tools.tinyencrypt;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import me.hatter.tools.commons.bytes.ByteUtil;
|
|
||||||
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;
|
||||||
@@ -10,25 +9,14 @@ import me.hatter.tools.commons.log.LogTool;
|
|||||||
import me.hatter.tools.commons.log.LogTools;
|
import me.hatter.tools.commons.log.LogTools;
|
||||||
import me.hatter.tools.commons.security.bc.BCUtil;
|
import me.hatter.tools.commons.security.bc.BCUtil;
|
||||||
import me.hatter.tools.commons.security.digest.Digests;
|
import me.hatter.tools.commons.security.digest.Digests;
|
||||||
import me.hatter.tools.commons.security.key.KeyPairTool;
|
|
||||||
import me.hatter.tools.commons.security.key.KeyUtil;
|
|
||||||
import me.hatter.tools.commons.security.key.PKType;
|
|
||||||
import me.hatter.tools.commons.string.StringUtil;
|
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.TinyEncryptConfig;
|
||||||
import me.hatter.tools.tinyencrypt.config.TinyEncryptConstant;
|
import me.hatter.tools.tinyencrypt.config.TinyEncryptConstant;
|
||||||
import me.hatter.tools.tinyencrypt.encrypt.EncryptedFileUtil;
|
import me.hatter.tools.tinyencrypt.encrypt.EncryptedFileUtil;
|
||||||
import me.hatter.tools.tinyencrypt.encrypt.TinyEncryptMeta;
|
|
||||||
import picocli.CommandLine;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
public class TinyEncryptMain {
|
public class TinyEncryptMain {
|
||||||
private static final String DEFAULT_TINY_ENCRYPT_CONFIG = "~/.tinyencrypt_config.json";
|
|
||||||
private static final LogTool log;
|
private static final LogTool log;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@@ -36,208 +24,106 @@ public class TinyEncryptMain {
|
|||||||
log = LogTools.getLogTool(TinyEncryptMain.class);
|
log = LogTools.getLogTool(TinyEncryptMain.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static TinyEncryptArgs parseTinyEncryptArgs(String[] args) {
|
|
||||||
TinyEncryptArgs tinyEncryptArgs = new TinyEncryptArgs();
|
|
||||||
CommandLine cmd = new CommandLine(tinyEncryptArgs);
|
|
||||||
cmd.parseArgs(args);
|
|
||||||
|
|
||||||
if (cmd.isUsageHelpRequested()) {
|
|
||||||
cmd.usage(cmd.getOut());
|
|
||||||
return null;
|
|
||||||
} else if (cmd.isVersionHelpRequested()) {
|
|
||||||
cmd.printVersionHelp(cmd.getOut());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return tinyEncryptArgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void doInitConfig(TinyEncryptArgs tinyEncryptArgs) {
|
|
||||||
if (StringUtil.isEmpty(tinyEncryptArgs.key)) {
|
|
||||||
log.error("Default key is not assigned");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
RFile writeTinyEncryptConfigRFile;
|
|
||||||
if (tinyEncryptArgs.config != null) {
|
|
||||||
writeTinyEncryptConfigRFile = RFile.from(tinyEncryptArgs.config);
|
|
||||||
} else {
|
|
||||||
writeTinyEncryptConfigRFile = RFile.from(DEFAULT_TINY_ENCRYPT_CONFIG);
|
|
||||||
}
|
|
||||||
if (writeTinyEncryptConfigRFile.exists()) {
|
|
||||||
log.error("File exists: " + tinyEncryptArgs.config);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
KeyPair keyPair = KeyPairTool.ins(PKType.secp256r1).generate().getKeyPair();
|
|
||||||
TinyEncryptConfig writeTinyEncryptConfig = new TinyEncryptConfig();
|
|
||||||
writeTinyEncryptConfig.setDefaultKeyName(tinyEncryptArgs.key);
|
|
||||||
writeTinyEncryptConfig.setLocalPublicKeyPem(KeyUtil.serializePublicKeyToPEM(keyPair.getPublic()));
|
|
||||||
writeTinyEncryptConfig.setLocalPrivateKeyPem(KeyUtil.serializePrivateKeyToPEM(keyPair.getPrivate()));
|
|
||||||
writeTinyEncryptConfigRFile.write(JSON.toJSONString(writeTinyEncryptConfig, true));
|
|
||||||
log.info("Write file success: " + writeTinyEncryptConfigRFile.file());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void fileInfo(TinyEncryptArgs tinyEncryptArgs) {
|
|
||||||
if ((tinyEncryptArgs.files == null) || (tinyEncryptArgs.files.length == 0)) {
|
|
||||||
log.error("No file assigned");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (File f : tinyEncryptArgs.files) {
|
|
||||||
boolean isTinyEncFile = f.getName().endsWith(TinyEncryptConstant.ENC_FILE_EXT);
|
|
||||||
if (!isTinyEncFile) {
|
|
||||||
log.warn("File is not tiny enc file: " + f);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
try (FileInputStream fis = new FileInputStream(f)) {
|
|
||||||
Tlv tlv = TlvUtil.readTlv(fis);
|
|
||||||
TinyEncryptMeta meta = tlv.getValueAsBytes().asJSONObject(TinyEncryptMeta.class);
|
|
||||||
StringBuilder sb = new StringBuilder(256);
|
|
||||||
sb.append("File: ").append(f);
|
|
||||||
if ((meta.getCompress() != null) && meta.getCompress()) {
|
|
||||||
sb.append(" [compressed]");
|
|
||||||
}
|
|
||||||
sb.append("\n");
|
|
||||||
sb.append("File version: ").append(meta.getVersion()).append("\n");
|
|
||||||
if (meta.getFileLength() != null) {
|
|
||||||
sb.append("File size: ").append(meta.getFileLength())
|
|
||||||
.append(" (")
|
|
||||||
.append(ByteUtil.formatBytes(meta.getFileLength()))
|
|
||||||
.append(")\n");
|
|
||||||
}
|
|
||||||
if (meta.getFileLastModified() != null) {
|
|
||||||
sb.append("Last modified: ")
|
|
||||||
.append(new Date(meta.getFileLastModified()))
|
|
||||||
.append("\n");
|
|
||||||
}
|
|
||||||
sb.append("Enc file created: ")
|
|
||||||
.append(new Date(meta.getCreated()))
|
|
||||||
.append("\n");
|
|
||||||
if (StringUtil.isNotBlank(meta.getPgpEnvelop())) {
|
|
||||||
sb.append("PGP envelop: YES\n");
|
|
||||||
} else {
|
|
||||||
sb.append("PGP envelop: NO\n");
|
|
||||||
}
|
|
||||||
if (StringUtil.isNotBlank(meta.getPgpFingerprint())) {
|
|
||||||
sb.append("PGP fingerprint: ")
|
|
||||||
.append(meta.getPgpFingerprint())
|
|
||||||
.append("\n");
|
|
||||||
}
|
|
||||||
sb.append("Agent: ").append(meta.getUserAgent());
|
|
||||||
if (StringUtil.isNotBlank(meta.getComment())) {
|
|
||||||
sb.append("Comment: ").append(meta.getComment()).append("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info(sb.toString());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("Read tiny encrypt file failed: " + e.getMessage() + ", file: " + f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static TinyEncryptConfig loadTinyEncryptConfig(TinyEncryptArgs tinyEncryptArgs) {
|
|
||||||
TinyEncryptConfig config;
|
|
||||||
if (tinyEncryptArgs.config != null) {
|
|
||||||
config = RFile.from(tinyEncryptArgs.config).parseJSONObject(TinyEncryptConfig.class);
|
|
||||||
} else {
|
|
||||||
RFile defaultTinyEncryptConfigFile = RFile.from(DEFAULT_TINY_ENCRYPT_CONFIG);
|
|
||||||
if (defaultTinyEncryptConfigFile.notExists()) {
|
|
||||||
log.error("Config file not assigned, and no default config file: " + DEFAULT_TINY_ENCRYPT_CONFIG);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
config = defaultTinyEncryptConfigFile.parseJSONObject(TinyEncryptConfig.class);
|
|
||||||
}
|
|
||||||
if (StringUtil.isNotBlank(tinyEncryptArgs.key)) {
|
|
||||||
log.info("Using key from args: " + tinyEncryptArgs.key);
|
|
||||||
config.setDefaultKeyName(tinyEncryptArgs.key);
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
TinyEncryptArgs tinyEncryptArgs = parseTinyEncryptArgs(args);
|
final TinyEncryptArgs tinyEncryptArgs = TinyEncryptArgsUtil.parseTinyEncryptArgs(args);
|
||||||
if (tinyEncryptArgs == null) {
|
if (tinyEncryptArgs == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
BCUtil.useBc(!tinyEncryptArgs.useJce);
|
BCUtil.useBc(!tinyEncryptArgs.useJce);
|
||||||
// ====================================================================================
|
// ====================================================================================
|
||||||
if (tinyEncryptArgs.doInitConfig) { // --init-config
|
if (tinyEncryptArgs.doInitConfig) { // --init-config
|
||||||
doInitConfig(tinyEncryptArgs);
|
TinyEncryptMainUtil.initConfig(tinyEncryptArgs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (tinyEncryptArgs.doEncryptConfigLocalPrivateKey) { // --encrypt-config-local-private-key
|
||||||
|
TinyEncryptMainUtil.encryptConfigLocalPrivateKey(tinyEncryptArgs);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (tinyEncryptArgs.fileInfo) { // --info
|
if (tinyEncryptArgs.fileInfo) { // --info
|
||||||
fileInfo(tinyEncryptArgs);
|
TinyEncryptMainUtil.fileInfo(tinyEncryptArgs);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// ====================================================================================
|
// ====================================================================================
|
||||||
|
|
||||||
boolean isEncrypt = tinyEncryptArgs.encrypt;
|
final boolean isCreate = tinyEncryptArgs.create;
|
||||||
boolean isDecrypt = tinyEncryptArgs.decrypt;
|
final boolean isEncrypt = tinyEncryptArgs.encrypt;
|
||||||
if (isEncrypt && isDecrypt) {
|
final boolean isDecrypt = tinyEncryptArgs.decrypt;
|
||||||
log.error("Encrypt and decrypt flag cannot both assigned.");
|
if ((isEncrypt && isDecrypt) || (isCreate && isEncrypt) || (isCreate && isDecrypt)) {
|
||||||
|
log.error("Encrypt create or decrypt flag cannot multiple assigned.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ((!isDecrypt) && (!isEncrypt)) {
|
if ((!isDecrypt) && (!isEncrypt) && (!isCreate)) {
|
||||||
log.error("Encrypt and decrypt flag must assign one.");
|
log.error("Encrypt, create and decrypt flag must assign one.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TinyEncryptConfig config = loadTinyEncryptConfig(tinyEncryptArgs);
|
final TinyEncryptConfig config = TinyEncryptMainUtil.loadTinyEncryptConfig(tinyEncryptArgs);
|
||||||
if (config == null) {
|
if (config == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((tinyEncryptArgs.files == null) || (tinyEncryptArgs.files.length == 0)) {
|
if ((tinyEncryptArgs.files == null) || (tinyEncryptArgs.files.length == 0)) {
|
||||||
log.error("FILE is not assigned.");
|
log.error("FILE is not assigned.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int total = tinyEncryptArgs.files.length;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (isCreate) {
|
||||||
|
TinyEncryptMainUtil.createFileFromWindow(tinyEncryptArgs, config);
|
||||||
|
throw new JumpOutException();
|
||||||
|
}
|
||||||
|
|
||||||
int index = 1;
|
int index = 1;
|
||||||
|
final int total = tinyEncryptArgs.files.length;
|
||||||
for (File f : tinyEncryptArgs.files) {
|
for (File f : tinyEncryptArgs.files) {
|
||||||
log.info("Start processing file: " + f + ", " + index + " of " + total);
|
log.info("Start processing file: " + f + ", " + index + " of " + total);
|
||||||
|
index++;
|
||||||
if (!f.isFile()) {
|
if (!f.isFile()) {
|
||||||
log.info("Skip not a file: " + f);
|
log.info("Skip not a file: " + f);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
boolean result;
|
final boolean encryptOrDecryptSuccess;
|
||||||
if (tinyEncryptArgs.encrypt) {
|
if (tinyEncryptArgs.encrypt) {
|
||||||
result = EncryptedFileUtil.encryptFile(config, tinyEncryptArgs.key, f, tinyEncryptArgs.compress, tinyEncryptArgs.comment);
|
final boolean turnOffEnvelop = config.getTurnOffEnvelop() != null && config.getTurnOffEnvelop();
|
||||||
|
final boolean useEnvelop = tinyEncryptArgs.turnOnEnvelop || (!turnOffEnvelop && !tinyEncryptArgs.skipEnvelop);
|
||||||
|
encryptOrDecryptSuccess = EncryptedFileUtil.encryptFile(config, tinyEncryptArgs.key, f,
|
||||||
|
tinyEncryptArgs.compress, useEnvelop, tinyEncryptArgs.requireSign,
|
||||||
|
tinyEncryptArgs.comment, tinyEncryptArgs.encryptedComment);
|
||||||
} else {
|
} else {
|
||||||
if (tinyEncryptArgs.showInWindow) {
|
if (tinyEncryptArgs.showInWindow || tinyEncryptArgs.editInWindow) {
|
||||||
EncryptedFileUtil.decryptInWindow(config, f, tinyEncryptArgs.pgp);
|
EncryptedFileUtil.decryptInWindow(config, f, tinyEncryptArgs.pgp, tinyEncryptArgs.age, tinyEncryptArgs.ecdh, tinyEncryptArgs.editInWindow);
|
||||||
result = false; // do not delete file
|
encryptOrDecryptSuccess = false; // do not delete file
|
||||||
} else if (tinyEncryptArgs.digest) {
|
} else if (tinyEncryptArgs.digest) {
|
||||||
Bytes sha256 = EncryptedFileUtil.decryptAndDigest(config, f, tinyEncryptArgs.pgp);
|
final Bytes sha256 = EncryptedFileUtil.decryptAndDigest(config, f, tinyEncryptArgs.pgp, tinyEncryptArgs.age, tinyEncryptArgs.ecdh);
|
||||||
if (sha256 != null) {
|
if (sha256 != null) {
|
||||||
log.info(sha256.asHex() + " - " + f);
|
log.info(sha256.asHex() + " - " + f);
|
||||||
File clearTextFile = EncryptedFileUtil.getDecryptFile(f);
|
final File clearTextFile = EncryptedFileUtil.getDecryptFile(f);
|
||||||
if ((clearTextFile != null) && clearTextFile.exists()) {
|
if ((clearTextFile != null) && clearTextFile.exists()) {
|
||||||
Bytes clearTextSha256 = RFile.from(clearTextFile).digest(Digests.sha256());
|
final Bytes clearTextSha256 = RFile.from(clearTextFile).digest(Digests.sha256());
|
||||||
if (clearTextSha256.equals(sha256)) {
|
if (clearTextSha256.equals(sha256)) {
|
||||||
log.info("Clear text file exists, and MATCHES.");
|
log.info("Clear text file exists, and MATCHES.");
|
||||||
} else {
|
} else {
|
||||||
String nfn = f.toString();
|
final String nfn = f.toString();
|
||||||
log.warn(clearTextSha256.asHex() + " - " + nfn.substring(0, nfn.length() - TinyEncryptConstant.ENC_FILE_EXT.length()));
|
log.warn(clearTextSha256.asHex() + " - " + nfn.substring(0, nfn.length() - TinyEncryptConstant.ENC_FILE_EXT.length()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = false; // do not delete file
|
encryptOrDecryptSuccess = false; // do not delete file
|
||||||
} else {
|
} else {
|
||||||
result = EncryptedFileUtil.decryptFile(config, f, tinyEncryptArgs.pgp);
|
encryptOrDecryptSuccess = EncryptedFileUtil.decryptFile(config, f, tinyEncryptArgs.pgp, tinyEncryptArgs.age, tinyEncryptArgs.ecdh);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (result && tinyEncryptArgs.removeFile) {
|
if (encryptOrDecryptSuccess && tinyEncryptArgs.removeFile) {
|
||||||
log.info("Remove file: " + f);
|
log.info("Remove file: " + f);
|
||||||
if (!f.delete()) {
|
if (!f.delete()) {
|
||||||
log.warn("Remove file: " + f + " failed.");
|
log.warn("Remove file: " + f + " failed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
index++;
|
|
||||||
}
|
}
|
||||||
} catch (JumpOutException joe) {
|
} catch (JumpOutException joe) {
|
||||||
log.error(joe.getMessage());
|
if (StringUtil.isNotEmpty(joe.getMessage())) {
|
||||||
log.debug(joe.getMessage(), joe);
|
log.error(joe.getMessage());
|
||||||
|
log.debug(joe.getMessage(), joe);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,223 @@
|
|||||||
|
package me.hatter.tools.tinyencrypt;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import me.hatter.tools.commons.assertion.AssertUtil;
|
||||||
|
import me.hatter.tools.commons.bytes.ByteUtil;
|
||||||
|
import me.hatter.tools.commons.bytes.Bytes;
|
||||||
|
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.AESCryptTool;
|
||||||
|
import me.hatter.tools.commons.security.key.KeyPairTool;
|
||||||
|
import me.hatter.tools.commons.security.key.KeyUtil;
|
||||||
|
import me.hatter.tools.commons.security.key.PKType;
|
||||||
|
import me.hatter.tools.commons.security.random.RandomTool;
|
||||||
|
import me.hatter.tools.commons.string.JSONUtil;
|
||||||
|
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.encrypt.EncryptedFileUtil;
|
||||||
|
import me.hatter.tools.tinyencrypt.encrypt.TinyEncryptMeta;
|
||||||
|
import me.hatter.tools.tinyencrypt.encrypt.TinyEncryptMetaUtil;
|
||||||
|
import me.hatter.tools.tinyencrypt.util.CardCliUtil;
|
||||||
|
import me.hatter.tools.tinyencrypt.util.SwingWindow;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class TinyEncryptMainUtil {
|
||||||
|
private static final LogTool log = LogTools.getLogTool(TinyEncryptMainUtil.class);
|
||||||
|
|
||||||
|
public static void encryptConfigLocalPrivateKey(TinyEncryptArgs tinyEncryptArgs) {
|
||||||
|
final TinyEncryptConfig config = loadTinyEncryptConfig(tinyEncryptArgs);
|
||||||
|
if (config == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (StringUtil.isEmpty(config.getLocalPrivateKeyPem())) {
|
||||||
|
log.error("Local private key pem is empty!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (StringUtil.isNotEmpty(config.getLocalPrivateKeyPemEncrypted())
|
||||||
|
|| StringUtil.isNotEmpty(config.getLocalPrivateKeyPemChallenge())) {
|
||||||
|
log.error("Local private key is already encrypted!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String challenge = RandomTool.secureRandom().nextBytes(16).asHex();
|
||||||
|
final Optional<byte[]> keyOpt = CardCliUtil.getChall(config.getCardCli(), challenge);
|
||||||
|
if (!keyOpt.isPresent()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final byte[] key = keyOpt.get();
|
||||||
|
final String localPrivateKeyPemEncrypted = AESCryptTool.gcmEncrypt(key).from(Bytes.from(config.getLocalPrivateKeyPem())).toBytes().asBase64();
|
||||||
|
|
||||||
|
final RFile tinyEncryptConfigRFile = TinyEncryptArgsUtil.getTinyEncryptConfigRFile(tinyEncryptArgs);
|
||||||
|
config.setLocalPrivateKeyPem(null);
|
||||||
|
config.setLocalPrivateKeyPemChallenge(challenge);
|
||||||
|
config.setLocalPrivateKeyPemEncrypted(localPrivateKeyPemEncrypted);
|
||||||
|
tinyEncryptConfigRFile.write(JSONUtil.pretty(config));
|
||||||
|
log.info("Write file success: " + tinyEncryptConfigRFile.file());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void initConfig(TinyEncryptArgs tinyEncryptArgs) {
|
||||||
|
if (StringUtil.isEmpty(tinyEncryptArgs.key)) {
|
||||||
|
log.error("Default key is not assigned");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final RFile writeTinyEncryptConfigRFile = TinyEncryptArgsUtil.getTinyEncryptConfigRFile(tinyEncryptArgs);
|
||||||
|
if (writeTinyEncryptConfigRFile.exists()) {
|
||||||
|
log.error("File exists: " + tinyEncryptArgs.config);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final KeyPair keyPair = KeyPairTool.ins(PKType.secp256r1).generate().getKeyPair();
|
||||||
|
final TinyEncryptConfig writeTinyEncryptConfig = new TinyEncryptConfig();
|
||||||
|
writeTinyEncryptConfig.setDefaultKeyName(tinyEncryptArgs.key);
|
||||||
|
writeTinyEncryptConfig.setLocalPublicKeyPem(KeyUtil.serializePublicKeyToPEM(keyPair.getPublic()));
|
||||||
|
writeTinyEncryptConfig.setLocalPrivateKeyPem(KeyUtil.serializePrivateKeyToPEM(keyPair.getPrivate()));
|
||||||
|
writeTinyEncryptConfigRFile.write(JSON.toJSONString(writeTinyEncryptConfig, true));
|
||||||
|
log.info("Write file success: " + writeTinyEncryptConfigRFile.file());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void fileInfo(TinyEncryptArgs tinyEncryptArgs) {
|
||||||
|
if ((tinyEncryptArgs.files == null) || (tinyEncryptArgs.files.length == 0)) {
|
||||||
|
log.error("No file assigned");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (File f : tinyEncryptArgs.files) {
|
||||||
|
final boolean isTinyEncFile = f.getName().endsWith(TinyEncryptConstant.ENC_FILE_EXT);
|
||||||
|
if (!isTinyEncFile) {
|
||||||
|
log.warn("File is not tiny enc file: " + f);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
try (FileInputStream fis = new FileInputStream(f)) {
|
||||||
|
printOneFileInfo(f, fis, tinyEncryptArgs.fileInfoJson);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Read tiny encrypt file failed: " + e.getMessage() + ", file: " + f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TinyEncryptConfig loadTinyEncryptConfig(TinyEncryptArgs tinyEncryptArgs) {
|
||||||
|
final RFile tinyEncryptConfigRFile = TinyEncryptArgsUtil.getTinyEncryptConfigRFile(tinyEncryptArgs);
|
||||||
|
if (tinyEncryptConfigRFile.notExists()) {
|
||||||
|
log.error("Config file not found: " + tinyEncryptConfigRFile.file());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final TinyEncryptConfig config = tinyEncryptConfigRFile.parseJSONObject(TinyEncryptConfig.class);
|
||||||
|
if (StringUtil.isNotBlank(tinyEncryptArgs.key)) {
|
||||||
|
log.info("Using key from args: " + tinyEncryptArgs.key);
|
||||||
|
config.setDefaultKeyName(tinyEncryptArgs.key);
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void createFileFromWindow(TinyEncryptArgs tinyEncryptArgs, TinyEncryptConfig config) {
|
||||||
|
if (tinyEncryptArgs.files.length != 1) {
|
||||||
|
log.error("Create only can assign one file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
File file = tinyEncryptArgs.files[0];
|
||||||
|
if (!file.getName().endsWith(TinyEncryptConstant.ENC_FILE_EXT)) {
|
||||||
|
file = EncryptedFileUtil.getEncryptFile(file);
|
||||||
|
AssertUtil.notNull(file, "File cannot be null");
|
||||||
|
}
|
||||||
|
if (file.exists()) {
|
||||||
|
log.error("File: " + file + " exists, cannot create");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String editResult = SwingWindow.create("Create file: " + file.getName())
|
||||||
|
.message("File: " + file)
|
||||||
|
.text("")
|
||||||
|
.editable(true)
|
||||||
|
.show().getResult();
|
||||||
|
|
||||||
|
final boolean turnOffEnvelop = config.getTurnOffEnvelop() != null && config.getTurnOffEnvelop();
|
||||||
|
final boolean useEnvelop = tinyEncryptArgs.turnOnEnvelop || (!turnOffEnvelop && !tinyEncryptArgs.skipEnvelop);
|
||||||
|
final byte[] bytes = editResult.getBytes(StandardCharsets.UTF_8);
|
||||||
|
final TinyEncryptMeta meta = TinyEncryptMetaUtil.create(config, tinyEncryptArgs.key,
|
||||||
|
tinyEncryptArgs.comment, tinyEncryptArgs.encryptedComment,
|
||||||
|
useEnvelop, tinyEncryptArgs.requireSign);
|
||||||
|
meta.setFileLength((long) bytes.length);
|
||||||
|
meta.setCreated(System.currentTimeMillis());
|
||||||
|
meta.setFileLastModified(System.currentTimeMillis());
|
||||||
|
meta.setCompress(tinyEncryptArgs.compress);
|
||||||
|
|
||||||
|
final InputStream inputStream = new ByteArrayInputStream(bytes);
|
||||||
|
try {
|
||||||
|
EncryptedFileUtil.encryptFromInputStream(inputStream, file, meta);
|
||||||
|
log.info("Create file: " + file + " success");
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Create file: " + file + " failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void printOneFileInfo(File f, FileInputStream fis, boolean json) throws IOException {
|
||||||
|
final Tlv tlv = TlvUtil.readTlv(fis);
|
||||||
|
final TinyEncryptMeta meta = tlv.getValueAsBytes().asJSONObject(TinyEncryptMeta.class);
|
||||||
|
log.debug("Tiny encrypt meta: " + JSON.toJSONString(meta, true));
|
||||||
|
if (!log.isDebugEnable() && json) {
|
||||||
|
log.info("Tiny encrypt meta: " + JSON.toJSONString(meta, true));
|
||||||
|
}
|
||||||
|
final StringBuilder sb = new StringBuilder(256);
|
||||||
|
sb.append("Tiny Encrypt File Info\n");
|
||||||
|
sb.append(header("File")).append(f);
|
||||||
|
if ((meta.getCompress() != null) && meta.getCompress()) {
|
||||||
|
sb.append(" [compressed]");
|
||||||
|
}
|
||||||
|
sb.append("\n");
|
||||||
|
if (meta.getFileLength() != null) {
|
||||||
|
sb.append(header("File size")).append(meta.getFileLength()).append(" byte(s)");
|
||||||
|
if (meta.getFileLength() >= 1024) {
|
||||||
|
sb.append(" [").append(ByteUtil.formatBytes(meta.getFileLength())).append("]");
|
||||||
|
}
|
||||||
|
sb.append("\n");
|
||||||
|
}
|
||||||
|
sb.append(header("Enc file summary")).append("Version: ").append(meta.getVersion())
|
||||||
|
.append(", Agent: ").append(meta.getUserAgent())
|
||||||
|
.append("\n");
|
||||||
|
if (meta.getFileLastModified() != null) {
|
||||||
|
sb.append(header("Last modified")).append(new Date(meta.getFileLastModified())).append("\n");
|
||||||
|
}
|
||||||
|
sb.append(header("Enc file created")).append(new Date(meta.getCreated())).append("\n");
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
sb.append(header("Encrypted comment")).append(toYesOrNo(meta.getEncryptedComment())).append("\n");
|
||||||
|
|
||||||
|
log.info(sb.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String header(String h) {
|
||||||
|
final int width = 18;
|
||||||
|
return h + StringUtil.repeat(".", Math.max(width - h.length(), 0)) + ": ";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toYesOrNo(String s) {
|
||||||
|
return toYesOrNo(StringUtil.isNotEmpty(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String toYesOrNo(boolean b) {
|
||||||
|
return b ? "YES" : "NO";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,22 @@
|
|||||||
package me.hatter.tools.tinyencrypt.config;
|
package me.hatter.tools.tinyencrypt.config;
|
||||||
|
|
||||||
|
import me.hatter.tools.commons.string.StringUtil;
|
||||||
|
|
||||||
public class TinyEncryptConfig {
|
public class TinyEncryptConfig {
|
||||||
private String defaultKeyName;
|
private String defaultKeyName;
|
||||||
private String localPublicKeyPem;
|
private String localPublicKeyPem;
|
||||||
private String localPrivateKeyPem;
|
private String localPrivateKeyPem;
|
||||||
|
private String localPrivateKeyPemEncrypted;
|
||||||
|
private String localPrivateKeyPemChallenge;
|
||||||
|
private Boolean turnOffEnvelop;
|
||||||
private String pgpEncryptPublicKeyPem;
|
private String pgpEncryptPublicKeyPem;
|
||||||
|
private String ecdhPublicKeyPoint;
|
||||||
|
@Deprecated
|
||||||
private String pgpDecryptCmd;
|
private String pgpDecryptCmd;
|
||||||
|
private String cardCli;
|
||||||
|
private String ageRecipient;
|
||||||
|
// Optional, default "age" when `ageRecipient` presents
|
||||||
|
private String ageCli;
|
||||||
|
|
||||||
public String getDefaultKeyName() {
|
public String getDefaultKeyName() {
|
||||||
return defaultKeyName;
|
return defaultKeyName;
|
||||||
@@ -31,6 +42,30 @@ public class TinyEncryptConfig {
|
|||||||
this.localPrivateKeyPem = localPrivateKeyPem;
|
this.localPrivateKeyPem = localPrivateKeyPem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getLocalPrivateKeyPemEncrypted() {
|
||||||
|
return localPrivateKeyPemEncrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocalPrivateKeyPemEncrypted(String localPrivateKeyPemEncrypted) {
|
||||||
|
this.localPrivateKeyPemEncrypted = localPrivateKeyPemEncrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocalPrivateKeyPemChallenge() {
|
||||||
|
return localPrivateKeyPemChallenge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocalPrivateKeyPemChallenge(String localPrivateKeyPemChallenge) {
|
||||||
|
this.localPrivateKeyPemChallenge = localPrivateKeyPemChallenge;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean getTurnOffEnvelop() {
|
||||||
|
return turnOffEnvelop;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTurnOffEnvelop(Boolean turnOffEnvelop) {
|
||||||
|
this.turnOffEnvelop = turnOffEnvelop;
|
||||||
|
}
|
||||||
|
|
||||||
public String getPgpEncryptPublicKeyPem() {
|
public String getPgpEncryptPublicKeyPem() {
|
||||||
return pgpEncryptPublicKeyPem;
|
return pgpEncryptPublicKeyPem;
|
||||||
}
|
}
|
||||||
@@ -39,6 +74,14 @@ public class TinyEncryptConfig {
|
|||||||
this.pgpEncryptPublicKeyPem = pgpEncryptPublicKeyPem;
|
this.pgpEncryptPublicKeyPem = pgpEncryptPublicKeyPem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getEcdhPublicKeyPoint() {
|
||||||
|
return ecdhPublicKeyPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEcdhPublicKeyPoint(String ecdhPublicKeyPoint) {
|
||||||
|
this.ecdhPublicKeyPoint = ecdhPublicKeyPoint;
|
||||||
|
}
|
||||||
|
|
||||||
public String getPgpDecryptCmd() {
|
public String getPgpDecryptCmd() {
|
||||||
return pgpDecryptCmd;
|
return pgpDecryptCmd;
|
||||||
}
|
}
|
||||||
@@ -46,4 +89,28 @@ public class TinyEncryptConfig {
|
|||||||
public void setPgpDecryptCmd(String pgpDecryptCmd) {
|
public void setPgpDecryptCmd(String pgpDecryptCmd) {
|
||||||
this.pgpDecryptCmd = pgpDecryptCmd;
|
this.pgpDecryptCmd = pgpDecryptCmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCardCli() {
|
||||||
|
return StringUtil.def(cardCli, pgpDecryptCmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCardCli(String cardCli) {
|
||||||
|
this.cardCli = cardCli;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAgeRecipient() {
|
||||||
|
return ageRecipient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAgeRecipient(String ageRecipient) {
|
||||||
|
this.ageRecipient = ageRecipient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAgeCli() {
|
||||||
|
return ageCli;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAgeCli(String ageCli) {
|
||||||
|
this.ageCli = ageCli;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.3.9";
|
public static final String VERSION = "0.7.3";
|
||||||
|
|
||||||
public static final String ENC_FILE_EXT = ".tinyenc";
|
public static final String ENC_FILE_EXT = ".tinyenc";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package me.hatter.tools.tinyencrypt.encrypt;
|
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.assertion.AssertUtil;
|
||||||
import me.hatter.tools.commons.bytes.Bytes;
|
import me.hatter.tools.commons.bytes.Bytes;
|
||||||
import me.hatter.tools.commons.io.DefaultRollCounter;
|
import me.hatter.tools.commons.io.DefaultRollCounter;
|
||||||
@@ -10,85 +8,127 @@ import me.hatter.tools.commons.io.IOUtil;
|
|||||||
import me.hatter.tools.commons.io.RFile;
|
import me.hatter.tools.commons.io.RFile;
|
||||||
import me.hatter.tools.commons.log.LogTool;
|
import me.hatter.tools.commons.log.LogTool;
|
||||||
import me.hatter.tools.commons.log.LogTools;
|
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.CryptInputStream;
|
||||||
import me.hatter.tools.commons.security.crypt.CryptOutputStream;
|
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.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.string.StringUtil;
|
||||||
import me.hatter.tools.commons.tlv.Tlv;
|
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.NilOutputStream;
|
import me.hatter.tools.tinyencrypt.util.NilOutputStream;
|
||||||
import me.hatter.tools.tinyencrypt.util.SwingWindow;
|
import me.hatter.tools.tinyencrypt.util.SwingWindow;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.charset.StandardCharsets;
|
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;
|
||||||
import java.util.zip.GZIPInputStream;
|
import java.util.zip.GZIPInputStream;
|
||||||
import java.util.zip.GZIPOutputStream;
|
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) {
|
public static boolean decryptToOutputStream(TinyEncryptConfig config, File file, OutputStream os,
|
||||||
|
boolean pgp,
|
||||||
|
boolean age,
|
||||||
|
boolean ecdh,
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
try (FileInputStream fis = new FileInputStream(file)) {
|
try (FileInputStream fis = new FileInputStream(file)) {
|
||||||
Tlv tlv = TlvUtil.readTlv(fis);
|
final Tlv tlv = TlvUtil.readTlv(fis);
|
||||||
TinyEncryptMeta meta = tlv.getValueAsBytes().asJSONObject(TinyEncryptMeta.class);
|
final TinyEncryptMeta meta = tlv.getValueAsBytes().asJSONObject(TinyEncryptMeta.class);
|
||||||
|
if (metaRef != null) {
|
||||||
|
metaRef.set(meta);
|
||||||
|
}
|
||||||
|
|
||||||
byte[] dataKey;
|
final byte[] dataKey;
|
||||||
if (pgp) {
|
if (pgp) {
|
||||||
if (StringUtil.isBlank(meta.getPgpEnvelop())) {
|
if (StringUtil.isBlank(meta.getPgpEnvelop())) {
|
||||||
log.error("File is not encrypted with PGP envelop");
|
log.error("File is not encrypted with PGP envelop");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (StringUtil.isBlank(config.getPgpDecryptCmd())) {
|
if (StringUtil.isBlank(config.getCardCli())) {
|
||||||
log.error("PGP decrypt cmd is not configed");
|
log.error("Card-cli is empty!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (RFile.from(config.getPgpDecryptCmd()).isNotFile()) {
|
if (RFile.from(config.getCardCli()).isNotFile()) {
|
||||||
log.error("PGP decrypt cmd is miss configed");
|
log.error("PGP decrypt cmd is miss configed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
System.out.print("Input PGP PIN: ");
|
final Optional<String> pinOpt = CardCliUtil.readUserPin();
|
||||||
char[] pin = System.console().readPassword();
|
if (!pinOpt.isPresent()) {
|
||||||
if (pin.length < 6) {
|
|
||||||
log.error("PIN must have 6 letters");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ProcessBuilder pb = new ProcessBuilder(
|
final String pin = pinOpt.get();
|
||||||
config.getPgpDecryptCmd(),
|
final Optional<byte[]> dataKeyOpt = CardCliUtil.decryptPgpEnvelop(
|
||||||
"pgp-card-decrypt",
|
config.getCardCli(), pin, meta.getPgpEnvelop());
|
||||||
"--cipher-base64", meta.getPgpEnvelop(),
|
if (!dataKeyOpt.isPresent()) {
|
||||||
"--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;
|
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<String> serialOpt = CardCliUtil.getSerial(config.getCardCli());
|
||||||
|
final Optional<byte[]> dataKeyOpt = AgeCliUtil.decryptBytes(ageCli, meta.getAgeRecipient(), meta.getAgeEnvelop(), serialOpt);
|
||||||
|
if (!dataKeyOpt.isPresent()) {
|
||||||
|
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 {
|
} else {
|
||||||
dataKey = TinyEncryptMetaUtil.decryptDataKey(config, meta);
|
dataKey = TinyEncryptMetaUtil.decryptDataKey(config, meta);
|
||||||
}
|
}
|
||||||
meta.setDataKey(dataKey);
|
meta.setDataKey(dataKey);
|
||||||
|
|
||||||
|
if (StringUtil.isNotEmpty(meta.getEncryptedComment())) {
|
||||||
|
final Bytes decryptedComment = AESCryptTool.gcmDecrypt(meta.getDataKey(), meta.getNonce())
|
||||||
|
.from(Bytes.fromBase64(meta.getEncryptedComment()))
|
||||||
|
.toBytes();
|
||||||
|
log.info("Decrypted comment: >>> " + decryptedComment.string() + " <<<");
|
||||||
|
}
|
||||||
|
|
||||||
try (InputStream newIs = getDecryptInputStream(fis, meta)) {
|
try (InputStream newIs = getDecryptInputStream(fis, meta)) {
|
||||||
boolean isCompressed = (meta.getCompress() != null) && meta.getCompress();
|
final boolean isCompressed = (meta.getCompress() != null) && meta.getCompress();
|
||||||
if (isCompressed) {
|
if (isCompressed) {
|
||||||
GZIPInputStream gzIs = new GZIPInputStream(newIs);
|
GZIPInputStream gzIs = new GZIPInputStream(newIs);
|
||||||
IOUtil.copy(gzIs, os, new DefaultRollCounter().prefix("Decrypting, "));
|
IOUtil.copy(gzIs, os, new DefaultRollCounter().prefix("Decrypting, "));
|
||||||
@@ -107,25 +147,52 @@ 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, boolean ecdh) {
|
||||||
DigestOutputStream outputStream = new DigestOutputStream(new NilOutputStream(), Digests.sha256());
|
final DigestOutputStream outputStream = new DigestOutputStream(new NilOutputStream(), Digests.sha256());
|
||||||
if (!decryptToOutputStream(config, file, outputStream, pgp)) {
|
if (!decryptToOutputStream(config, file, outputStream, pgp, age, ecdh, null)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return outputStream.digest();
|
return outputStream.digest();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void decryptInWindow(TinyEncryptConfig config, File file, boolean pgp) {
|
public static void decryptInWindow(TinyEncryptConfig config, File file, boolean pgp, boolean age, boolean ecdh, boolean editable) {
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
decryptToOutputStream(config, file, baos, pgp);
|
final AtomicReference<TinyEncryptMeta> metaRef = new AtomicReference<>();
|
||||||
SwingWindow.create("Decrypted file: " + file.getName())
|
final boolean decryptSuccess = decryptToOutputStream(config, file, baos, pgp, age, ecdh, metaRef);
|
||||||
|
if (!decryptSuccess) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String decrypted = new String(baos.toByteArray(), StandardCharsets.UTF_8);
|
||||||
|
final String editResult = SwingWindow.create((editable ? "Edit" : "View") + " file: " + file.getName())
|
||||||
.message("File: " + file)
|
.message("File: " + file)
|
||||||
.text(new String(baos.toByteArray(), StandardCharsets.UTF_8))
|
.text(decrypted)
|
||||||
|
.editable(editable)
|
||||||
.show().getResult();
|
.show().getResult();
|
||||||
|
if (editable) {
|
||||||
|
if (editResult == null) {
|
||||||
|
log.warn("You cancel the edit operation");
|
||||||
|
} else if (editResult.equals(decrypted)) {
|
||||||
|
log.warn("Text is not modified");
|
||||||
|
} else {
|
||||||
|
log.debug("Write to encrypt file: " + file);
|
||||||
|
final byte[] bytes = editResult.getBytes(StandardCharsets.UTF_8);
|
||||||
|
final InputStream inputStream = new ByteArrayInputStream(bytes);
|
||||||
|
try {
|
||||||
|
final TinyEncryptMeta meta = metaRef.get();
|
||||||
|
meta.setFileLength((long) bytes.length);
|
||||||
|
meta.setFileLastModified(System.currentTimeMillis());
|
||||||
|
encryptFromInputStream(inputStream, file, meta);
|
||||||
|
log.info("Write to encrypt file: " + file + " success");
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Write to encrypt file: " + file + " failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean decryptFile(TinyEncryptConfig config, File file, boolean pgp) {
|
public static boolean decryptFile(TinyEncryptConfig config, File file, boolean pgp, boolean age, boolean ecdh) {
|
||||||
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);
|
||||||
return false;
|
return false;
|
||||||
@@ -135,18 +202,26 @@ public class EncryptedFileUtil {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
boolean decryptResult;
|
final AtomicReference<TinyEncryptMeta> meta = new AtomicReference<>();
|
||||||
|
final boolean decryptResult;
|
||||||
try (FileOutputStream fos = new FileOutputStream(decFile)) {
|
try (FileOutputStream fos = new FileOutputStream(decFile)) {
|
||||||
decryptResult = decryptToOutputStream(config, file, fos, pgp);
|
decryptResult = decryptToOutputStream(config, file, fos, pgp, age, ecdh, meta);
|
||||||
}
|
}
|
||||||
if (!decryptResult) {
|
if (!decryptResult) {
|
||||||
if (decFile.length() == 0) {
|
if (decFile.length() == 0) {
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
decFile.delete();
|
decFile.delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (meta.get().getFileLastModified() != null) {
|
||||||
|
log.debug("Set file last modified time to: " + new Date(meta.get().getFileLastModified()));
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
decFile.setLastModified(meta.get().getFileLastModified());
|
||||||
|
}
|
||||||
return decryptResult;
|
return decryptResult;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
if (decFile.length() == 0) {
|
if (decFile.length() == 0) {
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
decFile.delete();
|
decFile.delete();
|
||||||
}
|
}
|
||||||
log.error("Decrypt file filed: " + file + ", reason: " + e.getMessage());
|
log.error("Decrypt file filed: " + file + ", reason: " + e.getMessage());
|
||||||
@@ -155,7 +230,9 @@ public class EncryptedFileUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean encryptFile(TinyEncryptConfig config, String keyName, File file, boolean compress, String comment) {
|
public static boolean encryptFile(TinyEncryptConfig config, String keyName, File file,
|
||||||
|
boolean compress, boolean useEnvelop, boolean requireSign,
|
||||||
|
String comment, String encryptedComment) {
|
||||||
File encFile = getEncryptFile(file);
|
File encFile = getEncryptFile(file);
|
||||||
if (encFile == null) {
|
if (encFile == null) {
|
||||||
log.warn("Cannot encrypt .tinyenc file: " + file);
|
log.warn("Cannot encrypt .tinyenc file: " + file);
|
||||||
@@ -166,26 +243,12 @@ public class EncryptedFileUtil {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
TinyEncryptMeta meta = TinyEncryptMetaUtil.create(config, comment);
|
TinyEncryptMeta meta = TinyEncryptMetaUtil.create(config, keyName, comment, encryptedComment, useEnvelop, requireSign);
|
||||||
meta.setFileLength(file.length());
|
meta.setFileLength(file.length());
|
||||||
meta.setFileLastModified(file.lastModified());
|
meta.setFileLastModified(file.lastModified());
|
||||||
meta.setCompress(compress);
|
meta.setCompress(compress);
|
||||||
Tlv tlv = TlvUtil.create(1, TinyEncryptMetaUtil.toString(meta));
|
|
||||||
|
|
||||||
try (FileInputStream fis = new FileInputStream(file)) {
|
try (FileInputStream fis = new FileInputStream(file)) {
|
||||||
try (FileOutputStream fos = new FileOutputStream(encFile)) {
|
encryptFromInputStream(fis, encFile, meta);
|
||||||
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);
|
log.info("Encrypt file success: " + file);
|
||||||
return true;
|
return true;
|
||||||
@@ -196,6 +259,23 @@ public class EncryptedFileUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void encryptFromInputStream(InputStream inputStream, File encFile, TinyEncryptMeta meta) throws IOException {
|
||||||
|
Tlv tlv = TlvUtil.create(1, TinyEncryptMetaUtil.toString(meta));
|
||||||
|
try (FileOutputStream fos = new FileOutputStream(encFile)) {
|
||||||
|
TlvUtil.writeTlv(fos, tlv);
|
||||||
|
fos.flush();
|
||||||
|
try (OutputStream newOs = getEncryptOutputStream(fos, meta)) {
|
||||||
|
if ((meta.getCompress() != null) && meta.getCompress()) {
|
||||||
|
GZIPOutputStream gzOs = new GZIPOutputStream(newOs);
|
||||||
|
IOUtil.copy(inputStream, gzOs, new DefaultRollCounter().prefix("Encrypting, "));
|
||||||
|
gzOs.finish();
|
||||||
|
} else {
|
||||||
|
IOUtil.copy(inputStream, newOs, new DefaultRollCounter().prefix("Encrypting, "));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static File getEncryptFile(File file) {
|
public static File getEncryptFile(File file) {
|
||||||
File absFile = file.getAbsoluteFile();
|
File absFile = file.getAbsoluteFile();
|
||||||
if (absFile.getName().endsWith(TinyEncryptConstant.ENC_FILE_EXT)) {
|
if (absFile.getName().endsWith(TinyEncryptConstant.ENC_FILE_EXT)) {
|
||||||
|
|||||||
@@ -7,8 +7,13 @@ public class TinyEncryptMeta {
|
|||||||
private long created;
|
private long created;
|
||||||
private String userAgent;
|
private String userAgent;
|
||||||
private String comment;
|
private String comment;
|
||||||
|
private String encryptedComment;
|
||||||
private String pgpEnvelop;
|
private String pgpEnvelop;
|
||||||
private String pgpFingerprint;
|
private String pgpFingerprint;
|
||||||
|
private String ecdhEnvelop;
|
||||||
|
private String ecdhPoint;
|
||||||
|
private String ageEnvelop;
|
||||||
|
private String ageRecipient;
|
||||||
private String envelop;
|
private String envelop;
|
||||||
@JSONField(serialize = false)
|
@JSONField(serialize = false)
|
||||||
private byte[] dataKey;
|
private byte[] dataKey;
|
||||||
@@ -49,6 +54,14 @@ public class TinyEncryptMeta {
|
|||||||
this.comment = comment;
|
this.comment = comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getEncryptedComment() {
|
||||||
|
return encryptedComment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEncryptedComment(String encryptedComment) {
|
||||||
|
this.encryptedComment = encryptedComment;
|
||||||
|
}
|
||||||
|
|
||||||
public String getPgpEnvelop() {
|
public String getPgpEnvelop() {
|
||||||
return pgpEnvelop;
|
return pgpEnvelop;
|
||||||
}
|
}
|
||||||
@@ -65,6 +78,38 @@ public class TinyEncryptMeta {
|
|||||||
this.pgpFingerprint = pgpFingerprint;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAgeEnvelop(String ageEnvelop) {
|
||||||
|
this.ageEnvelop = ageEnvelop;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAgeRecipient() {
|
||||||
|
return ageRecipient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAgeRecipient(String ageRecipient) {
|
||||||
|
this.ageRecipient = ageRecipient;
|
||||||
|
}
|
||||||
|
|
||||||
public String getEnvelop() {
|
public String getEnvelop() {
|
||||||
return envelop;
|
return envelop;
|
||||||
}
|
}
|
||||||
@@ -120,6 +165,7 @@ public class TinyEncryptMeta {
|
|||||||
", created=" + created +
|
", created=" + created +
|
||||||
", userAgent='" + userAgent + '\'' +
|
", userAgent='" + userAgent + '\'' +
|
||||||
", comment='" + comment + '\'' +
|
", comment='" + comment + '\'' +
|
||||||
|
", encryptedComment='" + encryptedComment + '\'' +
|
||||||
", envelop='" + envelop + '\'' +
|
", envelop='" + envelop + '\'' +
|
||||||
", compress=" + compress +
|
", compress=" + compress +
|
||||||
", fileLength=" + fileLength +
|
", fileLength=" + fileLength +
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import me.hatter.tools.commons.log.LogTool;
|
|||||||
import me.hatter.tools.commons.log.LogTools;
|
import me.hatter.tools.commons.log.LogTools;
|
||||||
import me.hatter.tools.commons.network.HttpRequest;
|
import me.hatter.tools.commons.network.HttpRequest;
|
||||||
import me.hatter.tools.commons.os.OSUtil;
|
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.digest.Digests;
|
||||||
|
import me.hatter.tools.commons.security.ec.ECUtil;
|
||||||
import me.hatter.tools.commons.security.key.KeyUtil;
|
import me.hatter.tools.commons.security.key.KeyUtil;
|
||||||
import me.hatter.tools.commons.security.random.RandomTool;
|
import me.hatter.tools.commons.security.random.RandomTool;
|
||||||
import me.hatter.tools.commons.security.rsa.RSAUtil;
|
import me.hatter.tools.commons.security.rsa.RSAUtil;
|
||||||
@@ -16,13 +19,17 @@ import me.hatter.tools.commons.security.sign.Signatures;
|
|||||||
import me.hatter.tools.commons.string.StringUtil;
|
import me.hatter.tools.commons.string.StringUtil;
|
||||||
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 java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.security.interfaces.ECPublicKey;
|
||||||
import java.security.interfaces.RSAPublicKey;
|
import java.security.interfaces.RSAPublicKey;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public class TinyEncryptMetaUtil {
|
public class TinyEncryptMetaUtil {
|
||||||
private static final LogTool log = LogTools.getLogTool(TinyEncryptMetaUtil.class);
|
private static final LogTool log = LogTools.getLogTool(TinyEncryptMetaUtil.class);
|
||||||
@@ -37,67 +44,88 @@ public class TinyEncryptMetaUtil {
|
|||||||
return JSON.parseObject(meta, TinyEncryptMeta.class);
|
return JSON.parseObject(meta, TinyEncryptMeta.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void requireLocalPrivateKeyPem(TinyEncryptConfig config) {
|
||||||
|
if (StringUtil.isNotEmpty(config.getLocalPrivateKeyPem())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (StringUtil.isEmpty(config.getLocalPrivateKeyPemChallenge())
|
||||||
|
|| StringUtil.isEmpty(config.getLocalPrivateKeyPemEncrypted())) {
|
||||||
|
throw new JumpOutException("Cannot prepare local private key pem!");
|
||||||
|
}
|
||||||
|
final Optional<byte[]> keyOpt = CardCliUtil.getChall(config.getCardCli(), config.getLocalPrivateKeyPemChallenge());
|
||||||
|
if (!keyOpt.isPresent()) {
|
||||||
|
throw new JumpOutException("Get challenge failed!");
|
||||||
|
}
|
||||||
|
final byte[] key = keyOpt.get();
|
||||||
|
final String localPrivateKeyPem = AESCryptTool.gcmDecrypt(key)
|
||||||
|
.from(Bytes.fromBase64(config.getLocalPrivateKeyPemEncrypted())).toBytes().string();
|
||||||
|
log.info("Decrypt local private key success!");
|
||||||
|
config.setLocalPrivateKeyPem(localPrivateKeyPem);
|
||||||
|
}
|
||||||
|
|
||||||
public static byte[] decryptDataKey(TinyEncryptConfig config, TinyEncryptMeta meta) {
|
public static byte[] decryptDataKey(TinyEncryptConfig config, TinyEncryptMeta meta) {
|
||||||
PrivateKey privateKey = KeyUtil.parsePrivateKeyPEM(config.getLocalPrivateKeyPem());
|
requireLocalPrivateKeyPem(config);
|
||||||
String envelop = meta.getEnvelop();
|
final PrivateKey privateKey = KeyUtil.parsePrivateKeyPEM(config.getLocalPrivateKeyPem());
|
||||||
|
final String envelop = meta.getEnvelop();
|
||||||
|
if (StringUtil.isEmpty(envelop)) {
|
||||||
|
throw new JumpOutException("File encryption envelop is not present");
|
||||||
|
}
|
||||||
|
|
||||||
String timestamp = String.valueOf(System.currentTimeMillis());
|
final String timestamp = String.valueOf(System.currentTimeMillis());
|
||||||
String toBeSigned = envelop + "|" + timestamp;
|
final String toBeSigned = envelop + "|" + timestamp;
|
||||||
Bytes sign = Signatures.sha256(privateKey).sign(toBeSigned);
|
final Bytes sign = Signatures.sha256(privateKey).sign(toBeSigned);
|
||||||
|
|
||||||
List<HttpRequest.KeyValue> keyValues = new ArrayList<>();
|
final List<HttpRequest.KeyValue> keyValues = new ArrayList<>();
|
||||||
keyValues.add(new HttpRequest.KeyValue("envelop", envelop));
|
keyValues.add(new HttpRequest.KeyValue("envelop", envelop));
|
||||||
keyValues.add(new HttpRequest.KeyValue("timestamp", timestamp));
|
keyValues.add(new HttpRequest.KeyValue("timestamp", timestamp));
|
||||||
keyValues.add(new HttpRequest.KeyValue("signature", sign.asBase64()));
|
keyValues.add(new HttpRequest.KeyValue("signature", sign.asBase64()));
|
||||||
log.info("Decrypt data key ...");
|
log.info("Decrypt data key ...");
|
||||||
Bytes response = HttpRequest.fromUrl(KMS_DECRYPT_DATA_KEY).post(keyValues);
|
final Bytes response = HttpRequest.fromUrl(KMS_DECRYPT_DATA_KEY).post(keyValues);
|
||||||
JSONObject responseObject = response.asJSON();
|
final JSONObject responseObject = response.asJSON();
|
||||||
if (responseObject.getIntValue("status") != 200) {
|
if (responseObject.getIntValue("status") != 200) {
|
||||||
throw new JumpOutException("Get data key from kms error, status: "
|
throw new JumpOutException("Get data key from kms error, status: "
|
||||||
+ responseObject.getIntValue("status")
|
+ responseObject.getIntValue("status")
|
||||||
+ ", detail: " + responseObject
|
+ ", detail: " + responseObject
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
JSONObject responseData = responseObject.getJSONObject("data");
|
final JSONObject responseData = responseObject.getJSONObject("data");
|
||||||
return Base64.getDecoder().decode(responseData.getString("dataKey"));
|
return Base64.getDecoder().decode(responseData.getString("dataKey"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TinyEncryptMeta create(TinyEncryptConfig config, String comment) {
|
public static TinyEncryptMeta create(
|
||||||
PublicKey publicKey = KeyUtil.parsePublicKeyPEM(config.getLocalPublicKeyPem());
|
TinyEncryptConfig config, String keyName, String comment, String encryptedComment,
|
||||||
PrivateKey privateKey = KeyUtil.parsePrivateKeyPEM(config.getLocalPrivateKeyPem());
|
boolean useEnvelop,
|
||||||
PublicKey pgpEncryptPublicKey = null;
|
boolean requireSignature) {
|
||||||
if (StringUtil.isNotBlank(config.getPgpEncryptPublicKeyPem())) {
|
PrivateKey privateKey = null;
|
||||||
pgpEncryptPublicKey = KeyUtil.parsePublicKeyPEM(config.getPgpEncryptPublicKeyPem());
|
if (requireSignature) {
|
||||||
|
requireLocalPrivateKeyPem(config);
|
||||||
|
privateKey = KeyUtil.parsePrivateKeyPEM(config.getLocalPrivateKeyPem());
|
||||||
}
|
}
|
||||||
String name = config.getDefaultKeyName();
|
|
||||||
|
|
||||||
String timestamp = String.valueOf(System.currentTimeMillis());
|
byte[] dataKey;
|
||||||
String toBeSigned = name + "|" + timestamp;
|
String envelop = null;
|
||||||
Bytes sign = Signatures.sha256(privateKey).sign(toBeSigned);
|
if (useEnvelop) {
|
||||||
|
PublicKey publicKey = KeyUtil.parsePublicKeyPEM(config.getLocalPublicKeyPem());
|
||||||
|
JSONObject responseObject = fetchJwkEnvelop(config, keyName, privateKey, publicKey);
|
||||||
|
JSONObject responseData = responseObject.getJSONObject("data");
|
||||||
|
|
||||||
List<HttpRequest.KeyValue> keyValues = new ArrayList<>();
|
dataKey = Base64.getDecoder().decode(responseData.getString("dataKey"));
|
||||||
keyValues.add(new HttpRequest.KeyValue("name", name));
|
envelop = responseData.getString("envelopJwe");
|
||||||
keyValues.add(new HttpRequest.KeyValue("timestamp", timestamp));
|
} else {
|
||||||
keyValues.add(new HttpRequest.KeyValue("dataKeyPublicKey", KeyUtil.serializePublicKeyToPEM(publicKey)));
|
log.warn("Use envelop is turned OFF!");
|
||||||
keyValues.add(new HttpRequest.KeyValue("dataKeyRequestSign", sign.asBase64()));
|
dataKey = RandomTool.secureRandom().nextbytes(32);
|
||||||
log.info("Get data key from kms, key name: " + name + " ...");
|
|
||||||
Bytes response = HttpRequest.fromUrl(KMS_GET_DATA_KEY).post(keyValues);
|
|
||||||
JSONObject responseObject = response.asJSON();
|
|
||||||
if (responseObject.getIntValue("status") != 200) {
|
|
||||||
throw new JumpOutException("Get data key from kms error, status: "
|
|
||||||
+ responseObject.getIntValue("status")
|
|
||||||
+ ", detail: " + responseObject
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
JSONObject responseData = responseObject.getJSONObject("data");
|
|
||||||
byte[] dataKey = Base64.getDecoder().decode(responseData.getString("dataKey"));
|
|
||||||
String envelop = responseData.getString("envelopJwe");
|
|
||||||
|
|
||||||
TinyEncryptMeta tinyEncryptMeta = new TinyEncryptMeta();
|
TinyEncryptMeta tinyEncryptMeta = new TinyEncryptMeta();
|
||||||
tinyEncryptMeta.setVersion("1.0");
|
tinyEncryptMeta.setVersion("1.0");
|
||||||
tinyEncryptMeta.setCreated(System.currentTimeMillis());
|
tinyEncryptMeta.setCreated(System.currentTimeMillis());
|
||||||
tinyEncryptMeta.setDataKey(dataKey);
|
tinyEncryptMeta.setDataKey(dataKey);
|
||||||
tinyEncryptMeta.setEnvelop(envelop);
|
tinyEncryptMeta.setEnvelop(envelop);
|
||||||
|
|
||||||
|
PublicKey pgpEncryptPublicKey = null;
|
||||||
|
if (StringUtil.isNotBlank(config.getPgpEncryptPublicKeyPem())) {
|
||||||
|
pgpEncryptPublicKey = KeyUtil.parsePublicKeyPEM(config.getPgpEncryptPublicKeyPem());
|
||||||
|
}
|
||||||
if (pgpEncryptPublicKey != null) {
|
if (pgpEncryptPublicKey != null) {
|
||||||
if (pgpEncryptPublicKey instanceof RSAPublicKey) {
|
if (pgpEncryptPublicKey instanceof RSAPublicKey) {
|
||||||
byte[] pgpEnvelop = RSAUtil.encrypt((RSAPublicKey) pgpEncryptPublicKey, dataKey);
|
byte[] pgpEnvelop = RSAUtil.encrypt((RSAPublicKey) pgpEncryptPublicKey, dataKey);
|
||||||
@@ -107,9 +135,58 @@ public class TinyEncryptMetaUtil {
|
|||||||
log.warn("PGP encrypt public key is not RSAPublicKey: " + pgpEncryptPublicKey.getClass());
|
log.warn("PGP encrypt public key is not RSAPublicKey: " + pgpEncryptPublicKey.getClass());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (StringUtil.isNotBlank(config.getAgeRecipient())) {
|
||||||
|
final String ageCli = StringUtil.def(config.getAgeCli(), "age");
|
||||||
|
final String ageEnvelop = AgeCliUtil.encryptBytes(ageCli, config.getAgeRecipient(), dataKey);
|
||||||
|
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.setNonce(RandomTool.secureRandom().nextbytes(12));
|
||||||
tinyEncryptMeta.setUserAgent("TinyEncrypt v" + TinyEncryptConstant.VERSION + "@" + OSUtil.getCurrentOS().name());
|
tinyEncryptMeta.setUserAgent("TinyEncrypt v" + TinyEncryptConstant.VERSION + "@" + OSUtil.getCurrentOS().name());
|
||||||
tinyEncryptMeta.setComment(comment);
|
tinyEncryptMeta.setComment(comment);
|
||||||
|
if (StringUtil.isNotEmpty(encryptedComment)) {
|
||||||
|
tinyEncryptMeta.setEncryptedComment(
|
||||||
|
AESCryptTool.gcmEncrypt(dataKey, tinyEncryptMeta.getNonce())
|
||||||
|
.from(Bytes.from(encryptedComment)).toBytes().asBase64());
|
||||||
|
}
|
||||||
return tinyEncryptMeta;
|
return tinyEncryptMeta;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static JSONObject fetchJwkEnvelop(TinyEncryptConfig config, String keyName, PrivateKey privateKey, PublicKey publicKey) {
|
||||||
|
String name = StringUtil.def(keyName, config.getDefaultKeyName());
|
||||||
|
|
||||||
|
String timestamp = String.valueOf(System.currentTimeMillis());
|
||||||
|
String toBeSigned = name + "|" + timestamp;
|
||||||
|
Bytes sign = (privateKey == null) ? null : Signatures.sha256(privateKey).sign(toBeSigned);
|
||||||
|
|
||||||
|
List<HttpRequest.KeyValue> keyValues = new ArrayList<>();
|
||||||
|
keyValues.add(new HttpRequest.KeyValue("name", name));
|
||||||
|
keyValues.add(new HttpRequest.KeyValue("timestamp", timestamp));
|
||||||
|
keyValues.add(new HttpRequest.KeyValue("dataKeyPublicKey", KeyUtil.serializePublicKeyToPEM(publicKey)));
|
||||||
|
if (sign == null) {
|
||||||
|
keyValues.add(new HttpRequest.KeyValue("skipDataKeyRequestSignVerify", "true"));
|
||||||
|
} else {
|
||||||
|
keyValues.add(new HttpRequest.KeyValue("dataKeyRequestSign", sign.asBase64()));
|
||||||
|
}
|
||||||
|
String keyFromArguments = (StringUtil.isEmpty(keyName) ? "" : "[*]");
|
||||||
|
log.info("Get data key from kms, key name: " + name + keyFromArguments + ", with sign: " + (sign != null) + " ...");
|
||||||
|
Bytes response = HttpRequest.fromUrl(KMS_GET_DATA_KEY).post(keyValues);
|
||||||
|
JSONObject responseObject = response.asJSON();
|
||||||
|
if (responseObject.getIntValue("status") != 200) {
|
||||||
|
throw new JumpOutException("Get data key from kms error, status: "
|
||||||
|
+ responseObject.getIntValue("status")
|
||||||
|
+ ", detail: " + responseObject
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return responseObject;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package me.hatter.tools.tinyencrypt.util;
|
||||||
|
|
||||||
|
import me.hatter.tools.commons.assertion.AssertUtil;
|
||||||
|
import me.hatter.tools.commons.exception.JumpOutException;
|
||||||
|
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.util.Optional;
|
||||||
|
|
||||||
|
public class AgeCliUtil {
|
||||||
|
private static final LogTool log = LogTools.getLogTool(AgeCliUtil.class);
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
System.out.println(
|
||||||
|
decryptBytes("age", "age1yubikey1qtwna67eqmyu7q9s3mpf7lkkrqzdrnqazdfdjftmv2qercy0cdchc7jcpu5", "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHBpdi1wMjU2IHFNWDVVUSBBNGMwMTl3\neXhQSXQ1dnhxSzFUNk4rNlJ6QkJCcnJqazZzTVAzcS9sSktyRQpBOXNQSVFudDBQ\nd2kyMFpySGxIUS8yemVwdkJxdEpoZHl3NTJuUzFaTjJJCi0tLSBwbW45LzUyem9j\nZWpROUFRT2huVDArS2hjaWFBeVd4S0xaTzh5eFo5QlhRCo2zDaVK/7YuOShVT0iw\n43LFOEP3T9v53YKhuUqjmm+6af93U2H/ppZVZluXrYPpR6+WHL4vdxflExP4yH1e\n8C+hFKSSdvxQe7cE8lHG\n-----END AGE ENCRYPTED FILE-----", Optional.empty())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<byte[]> decryptBytes(String ageCli, String ageRecipient, String ageEnvelop, Optional<String> serialOpt) {
|
||||||
|
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 = serialOpt.map(s -> {
|
||||||
|
final RFile recipientAgeFileWithSerial = RFile.fromUserHome(".tinyencrypt/" + ageRecipient + ".age." + s);
|
||||||
|
return recipientAgeFileWithSerial.isFile() ? recipientAgeFileWithSerial : null;
|
||||||
|
}).orElseGet(() -> RFile.fromUserHome(".tinyencrypt/" + ageRecipient + ".age"));
|
||||||
|
|
||||||
|
if (!recipientAgeFile.isFile()) {
|
||||||
|
throw new JumpOutException("Age key file required: " + recipientAgeFile.file().getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
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 be null");
|
||||||
|
final ProcessBuilder pb = new ProcessBuilder(
|
||||||
|
"sh",
|
||||||
|
"-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));
|
||||||
|
}
|
||||||
|
}
|
||||||
136
src/main/java/me/hatter/tools/tinyencrypt/util/CardCliUtil.java
Normal file
136
src/main/java/me/hatter/tools/tinyencrypt/util/CardCliUtil.java
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
package me.hatter.tools.tinyencrypt.util;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import me.hatter.tools.commons.bytes.Bytes;
|
||||||
|
import me.hatter.tools.commons.exception.JumpOutException;
|
||||||
|
import me.hatter.tools.commons.io.IOUtil;
|
||||||
|
import me.hatter.tools.commons.log.LogTool;
|
||||||
|
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("Please input user PIN: ");
|
||||||
|
final char[] pin = System.console().readPassword();
|
||||||
|
if (pin.length < 6) {
|
||||||
|
log.warn("Input user PIN has " + pin.length + " char(s)");
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.of(new String(pin));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<String> getSerial(String cardCli) {
|
||||||
|
if (StringUtil.isEmpty(cardCli)) {
|
||||||
|
throw new JumpOutException("Card-cli is empty!");
|
||||||
|
}
|
||||||
|
final ProcessBuilder pb = new ProcessBuilder(
|
||||||
|
cardCli,
|
||||||
|
"list",
|
||||||
|
"--json");
|
||||||
|
log.debug("Start: " + cardCli);
|
||||||
|
|
||||||
|
final Optional<String> outputsOpt = runProcess(pb);
|
||||||
|
|
||||||
|
if ((!outputsOpt.isPresent()) || outputsOpt.get().trim().isEmpty()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
final JSONObject jo = JSON.parseObject(outputsOpt.get());
|
||||||
|
return Optional.of(jo.getString("serial"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<byte[]> getChall(String cardCli, String challenge) {
|
||||||
|
if (StringUtil.isEmpty(cardCli)) {
|
||||||
|
throw new JumpOutException("Card-cli is empty!");
|
||||||
|
}
|
||||||
|
final ProcessBuilder pb = new ProcessBuilder(
|
||||||
|
cardCli,
|
||||||
|
"chall",
|
||||||
|
"--challenge-hex", challenge,
|
||||||
|
"--json", "--sha256");
|
||||||
|
log.info("Start: " + cardCli);
|
||||||
|
|
||||||
|
final Optional<String> outputsOpt = runProcess(pb);
|
||||||
|
|
||||||
|
if ((!outputsOpt.isPresent()) || outputsOpt.get().trim().isEmpty()) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
final JSONObject jo = JSON.parseObject(outputsOpt.get());
|
||||||
|
return Optional.of(Bytes.fromHex(jo.getString("response_sha256_hex")).bytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<byte[]> decryptPgpEnvelop(String cardCli, String pin, String pgpEnvelop) {
|
||||||
|
final ProcessBuilder pb = new ProcessBuilder(
|
||||||
|
cardCli,
|
||||||
|
"pgp-card-decrypt",
|
||||||
|
"--cipher-base64", pgpEnvelop,
|
||||||
|
"--pin", pin,
|
||||||
|
"--json");
|
||||||
|
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("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 {
|
||||||
|
log.debug("Start process: " + pb.command());
|
||||||
|
p = pb.start();
|
||||||
|
final int ret = p.waitFor();
|
||||||
|
if (ret == 0) {
|
||||||
|
log.info("Finished command, ret code: " + ret);
|
||||||
|
} else {
|
||||||
|
log.warn("Finished command, ret code: " + ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log.isDebugEnable()) {
|
||||||
|
final byte[] errorBytes = IOUtil.readToBytes(p.getErrorStream());
|
||||||
|
final String errorOutputs = new String(errorBytes, StandardCharsets.UTF_8);
|
||||||
|
log.debug("Read cmd error outputs: [[[~" + errorOutputs + "~]]]");
|
||||||
|
}
|
||||||
|
|
||||||
|
final byte[] jsonBytes = IOUtil.readToBytes(p.getInputStream());
|
||||||
|
final String outputs = new String(jsonBytes, StandardCharsets.UTF_8);
|
||||||
|
if (log.isDebugEnable()) {
|
||||||
|
log.debug("Read cmd outputs: [[[~" + outputs + "~]]]");
|
||||||
|
}
|
||||||
|
return Optional.of(outputs);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error in run card-cli: " + e, e);
|
||||||
|
if (p != null) {
|
||||||
|
log.error("err out: " + Bytes.from(IOUtil.readToBytes(p.getErrorStream())));
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,12 +13,14 @@ public class MacDockerHelper {
|
|||||||
static {
|
static {
|
||||||
if (ClassLoaderUtil.isClassCanBeLoad(COM_APPLE_EAWT_APPLICATION)) {
|
if (ClassLoaderUtil.isClassCanBeLoad(COM_APPLE_EAWT_APPLICATION)) {
|
||||||
try {
|
try {
|
||||||
Class<?> comAppleEawtApplication = ClassLoaderUtil.loadClass(COM_APPLE_EAWT_APPLICATION);
|
final Class<?> comAppleEawtApplication = ClassLoaderUtil.loadClass(COM_APPLE_EAWT_APPLICATION);
|
||||||
Method getApplicationMetohd = ReflectUtil.getDeclaredMethod(comAppleEawtApplication, "getApplication", new Class[0]);
|
final Method getApplicationMetohd = ReflectUtil.getDeclaredMethod(comAppleEawtApplication, "getApplication", new Class[0]);
|
||||||
Object application = ReflectUtil.invokeMethod(getApplicationMetohd, null, new Object[0]);
|
//noinspection RedundantArrayCreation
|
||||||
byte[] iconBytes = RResource.from(SwingWindow.class.getClassLoader()).rStream("icon.png").bytesAndClose();
|
final Object application = ReflectUtil.invokeMethod(getApplicationMetohd, null, new Object[0]);
|
||||||
Image image = Toolkit.getDefaultToolkit().createImage(iconBytes);
|
final byte[] iconBytes = RResource.from(SwingWindow.class.getClassLoader()).rStream("icon.png").bytesAndClose();
|
||||||
Method setDockIconImageMethod = ReflectUtil.getDeclaredMethod(comAppleEawtApplication, "setDockIconImage", new Class[]{Image.class});
|
final Image image = Toolkit.getDefaultToolkit().createImage(iconBytes);
|
||||||
|
final Method setDockIconImageMethod = ReflectUtil.getDeclaredMethod(comAppleEawtApplication, "setDockIconImage", new Class[]{Image.class});
|
||||||
|
//noinspection RedundantArrayCreation
|
||||||
ReflectUtil.invokeMethod(setDockIconImageMethod, application, new Object[]{image});
|
ReflectUtil.invokeMethod(setDockIconImageMethod, application, new Object[]{image});
|
||||||
} catch (Error e) {
|
} catch (Error e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ public class NilOutputStream extends OutputStream {
|
|||||||
public void write(int b) throws IOException {
|
public void write(int b) throws IOException {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("CStyleArrayDeclaration")
|
||||||
@Override
|
@Override
|
||||||
public void write(byte b[]) throws IOException {
|
public void write(byte b[]) throws IOException {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("CStyleArrayDeclaration")
|
||||||
@Override
|
@Override
|
||||||
public void write(byte b[], int off, int len) throws IOException {
|
public void write(byte b[], int off, int len) throws IOException {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import java.awt.event.WindowEvent;
|
|||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
public class SwingWindow {
|
public class SwingWindow {
|
||||||
|
@SuppressWarnings("FieldMayBeFinal")
|
||||||
private String title;
|
private String title;
|
||||||
private String text;
|
private String text;
|
||||||
private String message;
|
private String message;
|
||||||
@@ -49,6 +50,7 @@ public class SwingWindow {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("UnusedReturnValue")
|
||||||
public String getResult() {
|
public String getResult() {
|
||||||
try {
|
try {
|
||||||
countDownLatch.await();
|
countDownLatch.await();
|
||||||
@@ -60,7 +62,7 @@ public class SwingWindow {
|
|||||||
|
|
||||||
public SwingWindow show() {
|
public SwingWindow show() {
|
||||||
countDownLatch = new CountDownLatch(1);
|
countDownLatch = new CountDownLatch(1);
|
||||||
JFrame frame = new JFrame((StringUtil.isEmpty(title) ? "" : (title + " - ")) + "Tiny Encrypt Window");
|
final JFrame frame = new JFrame((StringUtil.isEmpty(title) ? "" : (title + " - ")) + "Tiny Encrypt Window");
|
||||||
frame.addWindowListener(new WindowListenerImpl() {
|
frame.addWindowListener(new WindowListenerImpl() {
|
||||||
@Override
|
@Override
|
||||||
public void windowClosing(WindowEvent e) {
|
public void windowClosing(WindowEvent e) {
|
||||||
@@ -69,12 +71,12 @@ public class SwingWindow {
|
|||||||
countDownLatch.countDown();
|
countDownLatch.countDown();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
JTextArea text = new JTextArea(StringUtil.def(this.text, ""), 30, 80);
|
final JTextArea text = new JTextArea(StringUtil.def(this.text, ""), 30, 80);
|
||||||
text.setWrapStyleWord(true);
|
text.setWrapStyleWord(true);
|
||||||
text.setLineWrap(true);
|
text.setLineWrap(true);
|
||||||
text.setEditable(editable);
|
text.setEditable(editable);
|
||||||
JScrollPane textScrollPane = new JScrollPane(text);
|
final JScrollPane textScrollPane = new JScrollPane(text);
|
||||||
JLabel label = new JLabel(StringUtil.def(message, "Tiny Encrypt default message."));
|
final JLabel label = new JLabel(StringUtil.def(message, "Tiny Encrypt default message."));
|
||||||
JButton btnOk = null;
|
JButton btnOk = null;
|
||||||
if (editable) {
|
if (editable) {
|
||||||
btnOk = new JButton("OK!");
|
btnOk = new JButton("OK!");
|
||||||
@@ -86,13 +88,13 @@ public class SwingWindow {
|
|||||||
countDownLatch.countDown();
|
countDownLatch.countDown();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
JButton btnCancel = new JButton("Cancel");
|
final JButton btnCancel = new JButton("Cancel");
|
||||||
btnCancel.addActionListener((e) -> {
|
btnCancel.addActionListener((e) -> {
|
||||||
frame.setVisible(false);
|
frame.setVisible(false);
|
||||||
frame.dispose();
|
frame.dispose();
|
||||||
countDownLatch.countDown();
|
countDownLatch.countDown();
|
||||||
});
|
});
|
||||||
JPanel pane = new JPanel();
|
final JPanel pane = new JPanel();
|
||||||
if (btnOk != null) {
|
if (btnOk != null) {
|
||||||
pane.add(btnOk);
|
pane.add(btnOk);
|
||||||
}
|
}
|
||||||
@@ -104,7 +106,7 @@ public class SwingWindow {
|
|||||||
|
|
||||||
frame.pack();
|
frame.pack();
|
||||||
|
|
||||||
Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
|
final Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
|
||||||
frame.setLocation(
|
frame.setLocation(
|
||||||
(dim.width / 2) - (frame.getSize().width / 2),
|
(dim.width / 2) - (frame.getSize().width / 2),
|
||||||
(dim.height / 2) - (frame.getSize().height / 2)
|
(dim.height / 2) - (frame.getSize().height / 2)
|
||||||
|
|||||||
29
src/test/java/me/hatter/tools/tinyencrypt/EcdhDemo.java
Normal file
29
src/test/java/me/hatter/tools/tinyencrypt/EcdhDemo.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package me.hatter.tools.tinyencrypt;
|
||||||
|
|
||||||
|
import me.hatter.tools.commons.bytes.Bytes;
|
||||||
|
import me.hatter.tools.commons.security.ec.ECUtil;
|
||||||
|
import me.hatter.tools.commons.security.key.KeyUtil;
|
||||||
|
|
||||||
|
import javax.crypto.KeyAgreement;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
|
||||||
|
public class EcdhDemo {
|
||||||
|
// https://stackoverflow.com/questions/21081713/diffie-hellman-key-exchange-in-java
|
||||||
|
// https://git.hatter.ink/hatter/simple-rust-tests/src/branch/master/__crypto/yubikey-rs-demo/src/main.rs
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
final KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH");
|
||||||
|
final byte[] privateKeyBytes = Bytes.fromHex("0101010101010101010101010101010101010101010101010101010101010101").bytes();
|
||||||
|
final PrivateKey privateKey = ECUtil.getECPrivateKey(ECUtil.CURVE_SECP256R1, new BigInteger(privateKeyBytes));
|
||||||
|
keyAgreement.init(privateKey);
|
||||||
|
|
||||||
|
final PublicKey publicKey = KeyUtil.parsePublicKeyPEM("-----BEGIN PUBLIC KEY-----\n" +
|
||||||
|
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE3T7r2QbJzwCwjsKfftYYBNHMHRNS\n" +
|
||||||
|
"2SV7YoGR4I/DcXxPrjKYzVxIKc7IvzqUbn22C3hX4Sh/aguuaz8jQvAH0A==\n" +
|
||||||
|
"-----END PUBLIC KEY-----");
|
||||||
|
keyAgreement.doPhase(publicKey, true);
|
||||||
|
System.out.println("e8df486cc3faeabca31bb717b0ae5d038cf2bf3e80569479ccdf69300e0e0cd3");
|
||||||
|
System.out.println(Bytes.from(keyAgreement.generateSecret()).asHex());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user