aspis 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.gitignore +1 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +13 -0
- data/README.md +99 -0
- data/Rakefile +4 -0
- data/aspis.1 +73 -0
- data/aspis.gemspec +31 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/certs/jsfierro.pem +26 -0
- data/exe/aspis +6 -0
- data/lib/aspis.rb +11 -0
- data/lib/aspis/aspis_init.rb +101 -0
- data/lib/aspis/asymmetric_decrypt.rb +25 -0
- data/lib/aspis/asymmetric_encrypt.rb +27 -0
- data/lib/aspis/generate_keys.rb +25 -0
- data/lib/aspis/symmetric_decrypt.rb +38 -0
- data/lib/aspis/symmetric_encrypt.rb +62 -0
- data/lib/aspis/version.rb +5 -0
- metadata +132 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 185a743d7eac9bf51be9f4fe226230f1f80c40a62a4bb0f0369220225df4b1ce
|
4
|
+
data.tar.gz: ae52af9fd222c6d38c749e0c768bc27f33f141e0dd628e750b689ed31e10eabd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 36b039f303d773fef53aeeaef29d0991a30d7c4093d9e52e6d755e4876ce1c5836efdbfa3b348329b7d1101f6b9c6aa27853009ee2a353885a1c14af161cf22e
|
7
|
+
data.tar.gz: 2a987e228220b1996b5de171e05f1bdc91ad7f10892b65903df24c3ea30ae5f4153506421f562f336f19310a551042cc2e1e512ca4516e7a88feb5164db96021
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data.tar.gz.sig
ADDED
Binary file
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Gemfile.lock
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright (c) 2020, Joseph Fierro <joseph.fierro@logosnetworks.com>
|
2
|
+
|
3
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose
|
4
|
+
with or without fee is hereby granted, provided that the above copyright notice
|
5
|
+
and this permission notice appear in all copies.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
8
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
9
|
+
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
10
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
11
|
+
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
12
|
+
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
13
|
+
THIS SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
## aspis
|
2
|
+
aspis is an encryption filter gem that encrypts anything it receives from stdin and sends the ciphertext to stdout.
|
3
|
+
It relies on Libsodium (via RbNaCl) for its cryptographic primitives. Output is in JSON format.
|
4
|
+
|
5
|
+
- Cipher: XSalsa20-Poly1305
|
6
|
+
- Public Keys: Curve25519
|
7
|
+
- Key exchange: X25519 (static-static Diffie Hellman)
|
8
|
+
- Password-based key derivation: Argon2i
|
9
|
+
- Nonces are randomly generated.
|
10
|
+
|
11
|
+
### Setup
|
12
|
+
aspis requires Libsodium and the Ruby gem RbNaCl.
|
13
|
+
|
14
|
+
To improve security, aspis is cryptographically signed.
|
15
|
+
It is strongly encouraged to verify this signature before installing the
|
16
|
+
aspis gem.
|
17
|
+
|
18
|
+
First, add my cert:
|
19
|
+
```
|
20
|
+
gem cert --add <(curl -Ls https://raw.github.com/jsfierro/aspis/certs/jsfierro.pem)
|
21
|
+
```
|
22
|
+
Then install the gem:
|
23
|
+
```
|
24
|
+
gem install aspis -P MediumSecurity
|
25
|
+
```
|
26
|
+
"MediumSecurity" will verify the signature on aspis, but not its dependencies in case they are
|
27
|
+
not signed. "HighSecurity" will require signatures on all dependencies as well.
|
28
|
+
|
29
|
+
Or to build and install from Github:
|
30
|
+
```
|
31
|
+
$ git clone https://github.com/jsfierro/aspis
|
32
|
+
$ cd aspis
|
33
|
+
# bundle exec rake install
|
34
|
+
```
|
35
|
+
|
36
|
+
### Usage
|
37
|
+
Basic syntax:
|
38
|
+
```
|
39
|
+
aspis [-n] [-o] [-m] -e|-d|-g
|
40
|
+
```
|
41
|
+
The flags -e or -d tell aspis to encrypt or decrypt respectively.
|
42
|
+
The -g flag creates a new keypair in the ~/.aspis directory, creating that directory if necessary.
|
43
|
+
|
44
|
+
If used with the -n option, aspis will look for the password in the environment
|
45
|
+
variable ASPIS_PASS rather than asking for it on the command line. This is useful if
|
46
|
+
you want to use aspis in a script, but environment variables are not as secure as they may seem.
|
47
|
+
It is best to disable your shell's history if using this option to prevent something like
|
48
|
+
```
|
49
|
+
export ASPIS_PASS=p@ssw0rd
|
50
|
+
```
|
51
|
+
from appearing in the shell's history file.
|
52
|
+
|
53
|
+
To manually tweak Argon2i's CPU ops and memory parameters, use the -o and -m
|
54
|
+
options. Memory is specified in MiB.
|
55
|
+
|
56
|
+
Simple example of encrypting a message with a password-derived key:
|
57
|
+
```
|
58
|
+
echo "hello" | aspis -e
|
59
|
+
```
|
60
|
+
Encrypt file.pdf and write the output to file.enc:
|
61
|
+
```
|
62
|
+
aspis -e file.pdf > file.enc
|
63
|
+
```
|
64
|
+
Encrypt a file piped via cat(1) and send it over the network with netcat:
|
65
|
+
```
|
66
|
+
cat file.pdf | aspis -e | nc 192.168.1.1 4444
|
67
|
+
```
|
68
|
+
Alice encrypts a file for Bob, using his public key and her default private key located in ~/.aspis:
|
69
|
+
```
|
70
|
+
aspis -e -p bob-pubkey message-for-bob.txt > message-for-bob.enc
|
71
|
+
```
|
72
|
+
Bob would decrypt the above file like so:
|
73
|
+
```
|
74
|
+
aspis -d -p alice-pubkey message-for-bob.enc > message-for-bob.txt
|
75
|
+
```
|
76
|
+
To send an encrypted email to Bob, you can use aspis with mutt like so:
|
77
|
+
```
|
78
|
+
echo "top secret message for Bob" | aspis -e -p bob-pubkey | mutt -s "Urgent, read immediately" bob@mailercorp.com
|
79
|
+
```
|
80
|
+
Bob would then open the email in mutt, and press "|" to pipe the message to an external command. To decrypt, he would
|
81
|
+
grep for the aspis portion of the email, then pipe that into aspis:
|
82
|
+
```
|
83
|
+
grep ciphertext | aspis -d -p alice-pubkey
|
84
|
+
```
|
85
|
+
The now decrypted message body will be displayed.
|
86
|
+
|
87
|
+
### Security considerations
|
88
|
+
aspis uses the NaCl crypto_box function underneath (via Libsodium) for its public key cryptography.
|
89
|
+
Key exchanges in asymmetric mode are static-static Diffie Hellman, which means that the shared key between 2 parties will be the same for all
|
90
|
+
of their communication. One desirable property of static-static DH (or "full static" as it is sometimes known) is that it provides
|
91
|
+
mutual authentication. Once Alice and Bob have each other's public keys, they can be sure that future messages 1.) can only be decrypted by Alice or Bob, and 2.) are actually from Alice or Bob.
|
92
|
+
|
93
|
+
The unfortunate part of this scheme is that their shared key is always the same, until one of them changes their long-term keys. This means that if an
|
94
|
+
attacker is able to compromise either Alice or Bob's key, it exposes all past messages between them. A scheme that provides forward secrecy would only expose messages encrypted with the compromised key, not all messages. However, this is harder to do for the asynchronous communication for which aspis is likely to be used.
|
95
|
+
|
96
|
+
|
97
|
+
I took the view that key impersonation is easier for an attacker and thus more likely than key compromise, and chose a scheme that provides mutual authentication to counter this. Yes, I am aware of its drawbacks.
|
98
|
+
|
99
|
+
Consult the NaCl paper for formal discussion of the cryptography used in aspis: https://cr.yp.to/highspeed/naclcrypto-20090310.pdf
|
data/Rakefile
ADDED
data/aspis.1
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
.\"
|
2
|
+
.\"Copyright (c) 2020 Joseph Fierro <joseph.fierro@logosnetworks.com>
|
3
|
+
.\"
|
4
|
+
.\"Permission to use, copy, modify, and distribute this software for any
|
5
|
+
.\"purpose with or without fee is hereby granted, provided that the above
|
6
|
+
.\"copyright notice and this permission notice appear in all copies.
|
7
|
+
.\"
|
8
|
+
.\"THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
9
|
+
.\"WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
10
|
+
.\"MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
11
|
+
.\"ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
12
|
+
.\"WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
13
|
+
.\"ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
14
|
+
.\"OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
15
|
+
.Dd $Mdocdate: May 9 2020 $
|
16
|
+
.Dt ASPIS 1
|
17
|
+
.Os
|
18
|
+
.Sh NAME
|
19
|
+
.Nm aspis
|
20
|
+
.Nd Encryption filter utility
|
21
|
+
.Sh SYNOPSIS
|
22
|
+
.Nm aspis
|
23
|
+
.Fl h
|
24
|
+
.Nm aspis
|
25
|
+
.Op Fl n
|
26
|
+
.Op Fl o Ar ops
|
27
|
+
.Op Fl m Ar memory
|
28
|
+
.Op Fl p Ar public_key
|
29
|
+
.Op Fl k Ar private_key
|
30
|
+
.Fl e
|
31
|
+
.Fl d
|
32
|
+
.Fl g
|
33
|
+
.Sh DESCRIPTION
|
34
|
+
.Nm
|
35
|
+
is an encryption utility that encrypts anything it reads from stdin and sends the ciphertext to
|
36
|
+
stdout in JSON format. It relies on Libsodium (via RbNaCl) for its crypto primitives.
|
37
|
+
|
38
|
+
The following options select the operation:
|
39
|
+
.Bl -tag -width Dsssigfile
|
40
|
+
.It Fl h
|
41
|
+
Print out usage information and exit
|
42
|
+
.It Fl n
|
43
|
+
The nopass option. Looks for passphrases in the environment variable ASPIS_PASS
|
44
|
+
instead of asking for them on the command line. This is useful for embedding
|
45
|
+
.Nm
|
46
|
+
in scripts, but is less secure. It is recommended to disable shell history when using
|
47
|
+
this feature to prevent something like "export ASPIS_PASS=p@ssw0rd" from being written
|
48
|
+
to your shell's history file.
|
49
|
+
.It Fl e
|
50
|
+
Encrypt. Data will be read from stdin, and encrypted with XSalsa20-Poly1305
|
51
|
+
with a key derived via Argon2i. Ciphertext and the header info will be sent to stdout
|
52
|
+
in JSON format.
|
53
|
+
.It Fl d
|
54
|
+
Decrypt.
|
55
|
+
.It Fl g
|
56
|
+
Generate new key pair. This will look for the directory ~/.aspis, create it if necessary,
|
57
|
+
and create the files "public_key" and "private_key" there. The public_key can be freely distributed.
|
58
|
+
.Nm
|
59
|
+
will read from stdin and write the plaintext to stdout.
|
60
|
+
.It Fl o Ar ops
|
61
|
+
The ops parameter (number of iterations) for Argon2i. The default is 10, and minimum is 3.
|
62
|
+
.It Fl m Ar memory
|
63
|
+
The memory to be consumed by Argon2i, in MiB. Default is 1024, and minimum is 64.
|
64
|
+
.It Fl p Ar public_key
|
65
|
+
When encrypting, the recipient's public key. When decrypting a message sent to you, the sender's public key.
|
66
|
+
.It Fl k Ar private_key
|
67
|
+
Your private key. If this option is omitted,
|
68
|
+
.Nm
|
69
|
+
will look in the ~/.aspis directory for a file called "private_key" and use that by default.
|
70
|
+
.El
|
71
|
+
.Pp
|
72
|
+
.Sh AUTHOR
|
73
|
+
Joseph Fierro <joseph.fierro@logosnetworks.com>
|
data/aspis.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'aspis/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'aspis'
|
9
|
+
spec.version = Aspis::VERSION
|
10
|
+
spec.authors = ['Joseph Fierro']
|
11
|
+
spec.email = ['joseph.fierro@logosnetworks.com']
|
12
|
+
spec.cert_chain = ['certs/jsfierro.pem']
|
13
|
+
spec.signing_key = File.expand_path("~/.ssh/gem-private_key.pem") if $0 =~ /gem\z/
|
14
|
+
|
15
|
+
spec.summary = 'command line encryption tool using rbnacl'
|
16
|
+
spec.homepage = 'https://github.com/jsfierro/aspis'
|
17
|
+
spec.license = 'ISC'
|
18
|
+
|
19
|
+
# Specify which files should be added to the gem when it is released.
|
20
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
21
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
22
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
23
|
+
end
|
24
|
+
spec.bindir = 'exe'
|
25
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
|
+
spec.require_paths = ['lib']
|
27
|
+
|
28
|
+
spec.add_runtime_dependency 'rbnacl', '>= 7.0.0'
|
29
|
+
spec.add_development_dependency 'bundler', '>= 1.17'
|
30
|
+
spec.add_development_dependency 'rake', '>= 13.0.1'
|
31
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'aspis'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require 'irb'
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/certs/jsfierro.pem
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIEbjCCAtagAwIBAgIBATANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDDCVqb3Nl
|
3
|
+
cGguZmllcnJvL0RDPWxvZ29zbmV0d29ya3MvREM9Y29tMB4XDTIwMDUxOTIzNDIx
|
4
|
+
MloXDTIxMDUxOTIzNDIxMlowMDEuMCwGA1UEAwwlam9zZXBoLmZpZXJyby9EQz1s
|
5
|
+
b2dvc25ldHdvcmtzL0RDPWNvbTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoC
|
6
|
+
ggGBAKMlytqAlJVr/1C2pfb9ecMSd9RSHhKB0FtBT19jUvGs5drds4vZOywVdVf6
|
7
|
+
BEstZSnqUfwXkC8Y96nlYBlb7oVxHnNhoYijsxxAHDVJlhNvj1+zZMMtpgwdxCW8
|
8
|
+
Ei9pJcVpIIABY31bG2ribxMoxThplGBmHeF+kMwszvH1EXGZRd+CZ8/7hgV27j4w
|
9
|
+
Got0/6fxMN+OkfhA75sGjF5b7CiXqG290aLa5ts2xD81vBVUQz3X4wcddZe/TCEX
|
10
|
+
GvJjDQi78DoBbjOZUmbqTFBA7+rUJhTjfjnJ7jKfxPcTjkUZWX6egZSLHEyzYhdk
|
11
|
+
XjG8R6YanIEA0mhJZ0v7ood8diNii7TrvdcrkmzRxQW1Unp0sa17rNIoxehDE5fa
|
12
|
+
ZVRWytp52okUVbMYBoV9Ukg5pvJXU8EAfIf4yclc/PMV2k2qaxfPFxdBhma9hrlF
|
13
|
+
FDVGlM6SGzVFggpD04a4OWATqWvjoT2Nl9H9o3PRX/Vq8Iiv6r+JnMcC09tG2O+h
|
14
|
+
i48HwQIDAQABo4GSMIGPMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
|
15
|
+
BBQaUvmMaP4VQp+k+WduTXxeEkCQJDAqBgNVHREEIzAhgR9qb3NlcGguZmllcnJv
|
16
|
+
QGxvZ29zbmV0d29ya3MuY29tMCoGA1UdEgQjMCGBH2pvc2VwaC5maWVycm9AbG9n
|
17
|
+
b3NuZXR3b3Jrcy5jb20wDQYJKoZIhvcNAQELBQADggGBADmpMf6ax1fn549BNxOT
|
18
|
+
BrErMUCmxil+X+4J3JYdHqa2xFSc6l032TfNkt+uuXoBBIGJHw2rqZj6Su8fxpjU
|
19
|
+
UCX5ZRY7flIEdFwSP4KrcVb8E7dQE7VQaU2UU/DUdds2Sec8g3b3oXuEMcUQITIK
|
20
|
+
NF85RYv9WRUyRLI7yqs5HqGxxeLtYRZ9vaqNzMs/h658QfPmFWqsWL73Dx808jvx
|
21
|
+
2pKrAvcVNEJ9mNI5SKy/6VNHzBBMTn1JBgTCEdpos5vMuA0PMQlgvc3GiPHw3gLH
|
22
|
+
T9FE7HMc1aiO9utsli+EMOJ9wr4TLoN7e3J4ndH8jGg/46VbqIVGL/jQsFQCDqKI
|
23
|
+
nM4hcwmd/0ss5wQBOx+5n36nR+Adb1HHOVXdX2P5ktxUEd4Xkt51q+NxO//RYngB
|
24
|
+
GJRqwrLlLH1jf+y9mULfzRHWpAl2aXcxCW57d/+fCCeRz7+v9Qz/Eq2ieKucVeNi
|
25
|
+
sEIE5PVGOPAiOJxBYbphEwugUb/AFjziPepwMN0jDLx6CQ==
|
26
|
+
-----END CERTIFICATE-----
|
data/exe/aspis
ADDED
data/lib/aspis.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aspis/version'
|
4
|
+
require 'aspis/asymmetric_encrypt'
|
5
|
+
require 'aspis/asymmetric_decrypt'
|
6
|
+
require 'aspis/symmetric_encrypt'
|
7
|
+
require 'aspis/symmetric_decrypt'
|
8
|
+
require 'aspis/generate_keys'
|
9
|
+
require 'aspis/aspis_init'
|
10
|
+
|
11
|
+
aspis_init if __FILE__ == $PROGRAM_NAME
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2020 Joseph Fierro <joseph.fierro@logosnetworks.com>
|
4
|
+
#
|
5
|
+
# Permission to use, copy, modify, and distribute this software for any
|
6
|
+
# purpose with or without fee is hereby granted, provided that the above
|
7
|
+
# copyright notice and this permission notice appear in all copies.
|
8
|
+
#
|
9
|
+
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
10
|
+
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
11
|
+
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
12
|
+
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
13
|
+
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
14
|
+
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
15
|
+
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
16
|
+
require 'optparse'
|
17
|
+
|
18
|
+
require_relative 'asymmetric_encrypt.rb'
|
19
|
+
require_relative 'asymmetric_decrypt.rb'
|
20
|
+
require_relative 'symmetric_encrypt.rb'
|
21
|
+
require_relative 'symmetric_decrypt.rb'
|
22
|
+
require_relative 'generate_keys.rb'
|
23
|
+
require_relative 'version.rb'
|
24
|
+
|
25
|
+
module AspisInit
|
26
|
+
def self.init
|
27
|
+
options = {}
|
28
|
+
OptionParser.new do |opts|
|
29
|
+
opts.banner = "Encryption filter utility.\n"
|
30
|
+
|
31
|
+
opts.version = Aspis::VERSION
|
32
|
+
|
33
|
+
opts.on('-e', '--encrypt', 'Encrypt') do
|
34
|
+
options[:mode] = 'encrypt'
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on('-d', '--decrypt', 'Decrypt') do
|
38
|
+
options[:mode] = 'decrypt'
|
39
|
+
end
|
40
|
+
|
41
|
+
opts.on('-g', '--generate', 'Generate key pair') do
|
42
|
+
options[:mode] = 'generate'
|
43
|
+
end
|
44
|
+
|
45
|
+
opts.on('-n', '--nopass', 'Use env variable ASPIS_PASS for passphrase') do
|
46
|
+
options[:ask_pass] = false
|
47
|
+
end
|
48
|
+
|
49
|
+
opts.on('-p', '--pubkey public_key', String, "Recipient's public key") do |p|
|
50
|
+
options[:public_key] = p
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on('-k', '--privatekey private_key', String, "Sender's private key") do |k|
|
54
|
+
options[:private_key] = k
|
55
|
+
end
|
56
|
+
|
57
|
+
opts.on('-o', '--opslimit opslimit', Integer, 'Argon2i ops parameter') do |o|
|
58
|
+
options[:opslimit] = o
|
59
|
+
end
|
60
|
+
|
61
|
+
opts.on('-m', '--memlimit memlimit', Integer, 'Argon2i memory in MiB') do |m|
|
62
|
+
options[:memlimit] = m
|
63
|
+
end
|
64
|
+
|
65
|
+
opts.on('-h', '--help', 'Displays help') do
|
66
|
+
puts opts
|
67
|
+
exit
|
68
|
+
end
|
69
|
+
end.parse!
|
70
|
+
|
71
|
+
abort('Fatal error: must enter -g, -e, or -d') unless options[:mode]
|
72
|
+
|
73
|
+
run(options)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.run(options)
|
77
|
+
if options[:public_key]
|
78
|
+
unless options[:private_key]
|
79
|
+
aspis_dir = File.expand_path('~/.aspis')
|
80
|
+
options[:private_key] = aspis_dir + '/private_key'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
case options[:mode]
|
85
|
+
when 'encrypt'
|
86
|
+
if options[:public_key]
|
87
|
+
puts AsymmetricEncrypt.encrypt(ARGF.read, options[:public_key], options[:private_key], options[:ask_pass])
|
88
|
+
else
|
89
|
+
puts SymmetricEncrypt.encrypt(ARGF.read, options[:opslimit], options[:memlimit], options[:ask_pass])
|
90
|
+
end
|
91
|
+
when 'decrypt'
|
92
|
+
if options[:public_key]
|
93
|
+
puts AsymmetricDecrypt.decrypt(ARGF.read, options[:public_key], options[:private_key], options[:ask_pass])
|
94
|
+
else
|
95
|
+
puts SymmetricDecrypt.decrypt(ARGF.read, options[:ask_pass])
|
96
|
+
end
|
97
|
+
when 'generate'
|
98
|
+
GenerateKeys.generate(options[:opslimit], options[:memlimit], options[:ask_pass])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rbnacl'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'base64'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
require_relative 'symmetric_decrypt.rb'
|
9
|
+
|
10
|
+
module AsymmetricDecrypt
|
11
|
+
def self.decrypt(input, public_key, private_key, ask_pass)
|
12
|
+
sender_public_key = File.read(public_key)
|
13
|
+
sender_public_key = Base64.decode64(sender_public_key)
|
14
|
+
|
15
|
+
recipient_private_key = File.read(private_key)
|
16
|
+
recipient_private_key = SymmetricDecrypt.decrypt(recipient_private_key, ask_pass)
|
17
|
+
|
18
|
+
box = RbNaCl::SimpleBox.from_keypair(sender_public_key, recipient_private_key)
|
19
|
+
|
20
|
+
input = JSON.parse(input)
|
21
|
+
ciphertext = input['ciphertext']
|
22
|
+
ciphertext = Base64.decode64(ciphertext)
|
23
|
+
box.decrypt(ciphertext)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rbnacl'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'base64'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
require_relative 'symmetric_decrypt.rb'
|
9
|
+
require_relative 'version.rb'
|
10
|
+
|
11
|
+
module AsymmetricEncrypt
|
12
|
+
def self.encrypt(plaintext, public_key, private_key, ask_pass)
|
13
|
+
recipient_public_key = File.read(public_key)
|
14
|
+
recipient_public_key = Base64.decode64(recipient_public_key)
|
15
|
+
|
16
|
+
sender_private_key = File.read(private_key)
|
17
|
+
sender_private_key = SymmetricDecrypt.decrypt(sender_private_key, ask_pass)
|
18
|
+
|
19
|
+
box = RbNaCl::SimpleBox.from_keypair(recipient_public_key, sender_private_key)
|
20
|
+
ciphertext = box.encrypt(plaintext)
|
21
|
+
ciphertext = Base64.strict_encode64(ciphertext)
|
22
|
+
|
23
|
+
output = { version: Aspis::VERSION,
|
24
|
+
ciphertext: ciphertext }
|
25
|
+
JSON.generate(output)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rbnacl'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'base64'
|
6
|
+
|
7
|
+
require_relative 'symmetric_encrypt.rb'
|
8
|
+
|
9
|
+
module GenerateKeys
|
10
|
+
def self.generate(opslimit, memlimit, ask_pass)
|
11
|
+
aspis_dir = File.expand_path('~/.aspis')
|
12
|
+
FileUtils.mkdir_p(aspis_dir) unless Dir.exist?(aspis_dir)
|
13
|
+
|
14
|
+
private_key = RbNaCl::PrivateKey.generate
|
15
|
+
public_key = private_key.public_key
|
16
|
+
public_key = Base64.strict_encode64(public_key)
|
17
|
+
|
18
|
+
# Encrypt private key before writing to disk
|
19
|
+
private_key = SymmetricEncrypt.encrypt(private_key, opslimit, memlimit, ask_pass)
|
20
|
+
|
21
|
+
File.write(aspis_dir + '/private_key', private_key)
|
22
|
+
File.write(aspis_dir + '/public_key', public_key)
|
23
|
+
puts('Keys created in ~/.aspis')
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rbnacl'
|
4
|
+
require 'json'
|
5
|
+
require 'base64'
|
6
|
+
|
7
|
+
module SymmetricDecrypt
|
8
|
+
def self.decrypt(input, ask_pass)
|
9
|
+
input = JSON.parse(input)
|
10
|
+
|
11
|
+
salt = input['salt']
|
12
|
+
salt = Base64.decode64(salt)
|
13
|
+
|
14
|
+
ops = input['ops']
|
15
|
+
mem = input['mem']
|
16
|
+
key_size = input['key_size']
|
17
|
+
|
18
|
+
ciphertext = input['ciphertext']
|
19
|
+
ciphertext = Base64.decode64(ciphertext)
|
20
|
+
|
21
|
+
password = if ask_pass == false
|
22
|
+
ENV['ASPIS_PASS']
|
23
|
+
else
|
24
|
+
IO.console.getpass 'Enter passphrase: '
|
25
|
+
end
|
26
|
+
|
27
|
+
key = RbNaCl::PasswordHash.argon2i(
|
28
|
+
password,
|
29
|
+
salt,
|
30
|
+
ops,
|
31
|
+
mem,
|
32
|
+
key_size
|
33
|
+
)
|
34
|
+
|
35
|
+
box = RbNaCl::SimpleBox.from_secret_key(key)
|
36
|
+
box.decrypt(ciphertext)
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rbnacl'
|
4
|
+
require 'json'
|
5
|
+
require 'base64'
|
6
|
+
require 'io/console'
|
7
|
+
|
8
|
+
module SymmetricEncrypt
|
9
|
+
def self.timingsafe_compare(secret1, secret2)
|
10
|
+
check = secret1.bytesize ^ secret2.bytesize
|
11
|
+
secret1.bytes.zip(secret2.bytes) { |x, y| check |= x ^ y.to_i }
|
12
|
+
check.zero?
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.kdf_gen(salt, opslimit, memlimit, ask_pass)
|
16
|
+
if ask_pass == false
|
17
|
+
password = ENV['ASPIS_PASS']
|
18
|
+
else
|
19
|
+
password = IO.console.getpass('Enter passphrase: ')
|
20
|
+
password2 = IO.console.getpass('Confirm passphrase: ')
|
21
|
+
unless timingsafe_compare(password, password2)
|
22
|
+
abort('Passphrases do not match')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
RbNaCl::PasswordHash.argon2i(
|
27
|
+
password,
|
28
|
+
salt,
|
29
|
+
opslimit,
|
30
|
+
memlimit,
|
31
|
+
32
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.encrypt(plaintext, opslimit, memlimit, ask_pass)
|
36
|
+
opslimit ||= 10
|
37
|
+
abort('Error: KDF opslimit must be >= 3') if opslimit < 3
|
38
|
+
|
39
|
+
memlimit ||= 1024
|
40
|
+
abort('Error: KDF memory must be >= 64 MiB') if memlimit < 64
|
41
|
+
memlimit = 1_048_576 * memlimit
|
42
|
+
|
43
|
+
salt = RbNaCl::Random.random_bytes(RbNaCl::PasswordHash::Argon2::SALTBYTES)
|
44
|
+
|
45
|
+
key = kdf_gen(salt, opslimit, memlimit, ask_pass)
|
46
|
+
|
47
|
+
box = RbNaCl::SimpleBox.from_secret_key(key)
|
48
|
+
ciphertext = box.encrypt(plaintext)
|
49
|
+
|
50
|
+
ciphertext = Base64.strict_encode64(ciphertext)
|
51
|
+
salt = Base64.strict_encode64(salt)
|
52
|
+
|
53
|
+
output = { version: Aspis::VERSION,
|
54
|
+
salt: salt,
|
55
|
+
ops: opslimit,
|
56
|
+
mem: memlimit,
|
57
|
+
key_size: 32,
|
58
|
+
ciphertext: ciphertext }
|
59
|
+
|
60
|
+
JSON.generate(output)
|
61
|
+
end
|
62
|
+
end
|
metadata
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aspis
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joseph Fierro
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain:
|
11
|
+
- |
|
12
|
+
-----BEGIN CERTIFICATE-----
|
13
|
+
MIIEbjCCAtagAwIBAgIBATANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDDCVqb3Nl
|
14
|
+
cGguZmllcnJvL0RDPWxvZ29zbmV0d29ya3MvREM9Y29tMB4XDTIwMDUxOTIzNDIx
|
15
|
+
MloXDTIxMDUxOTIzNDIxMlowMDEuMCwGA1UEAwwlam9zZXBoLmZpZXJyby9EQz1s
|
16
|
+
b2dvc25ldHdvcmtzL0RDPWNvbTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoC
|
17
|
+
ggGBAKMlytqAlJVr/1C2pfb9ecMSd9RSHhKB0FtBT19jUvGs5drds4vZOywVdVf6
|
18
|
+
BEstZSnqUfwXkC8Y96nlYBlb7oVxHnNhoYijsxxAHDVJlhNvj1+zZMMtpgwdxCW8
|
19
|
+
Ei9pJcVpIIABY31bG2ribxMoxThplGBmHeF+kMwszvH1EXGZRd+CZ8/7hgV27j4w
|
20
|
+
Got0/6fxMN+OkfhA75sGjF5b7CiXqG290aLa5ts2xD81vBVUQz3X4wcddZe/TCEX
|
21
|
+
GvJjDQi78DoBbjOZUmbqTFBA7+rUJhTjfjnJ7jKfxPcTjkUZWX6egZSLHEyzYhdk
|
22
|
+
XjG8R6YanIEA0mhJZ0v7ood8diNii7TrvdcrkmzRxQW1Unp0sa17rNIoxehDE5fa
|
23
|
+
ZVRWytp52okUVbMYBoV9Ukg5pvJXU8EAfIf4yclc/PMV2k2qaxfPFxdBhma9hrlF
|
24
|
+
FDVGlM6SGzVFggpD04a4OWATqWvjoT2Nl9H9o3PRX/Vq8Iiv6r+JnMcC09tG2O+h
|
25
|
+
i48HwQIDAQABo4GSMIGPMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQW
|
26
|
+
BBQaUvmMaP4VQp+k+WduTXxeEkCQJDAqBgNVHREEIzAhgR9qb3NlcGguZmllcnJv
|
27
|
+
QGxvZ29zbmV0d29ya3MuY29tMCoGA1UdEgQjMCGBH2pvc2VwaC5maWVycm9AbG9n
|
28
|
+
b3NuZXR3b3Jrcy5jb20wDQYJKoZIhvcNAQELBQADggGBADmpMf6ax1fn549BNxOT
|
29
|
+
BrErMUCmxil+X+4J3JYdHqa2xFSc6l032TfNkt+uuXoBBIGJHw2rqZj6Su8fxpjU
|
30
|
+
UCX5ZRY7flIEdFwSP4KrcVb8E7dQE7VQaU2UU/DUdds2Sec8g3b3oXuEMcUQITIK
|
31
|
+
NF85RYv9WRUyRLI7yqs5HqGxxeLtYRZ9vaqNzMs/h658QfPmFWqsWL73Dx808jvx
|
32
|
+
2pKrAvcVNEJ9mNI5SKy/6VNHzBBMTn1JBgTCEdpos5vMuA0PMQlgvc3GiPHw3gLH
|
33
|
+
T9FE7HMc1aiO9utsli+EMOJ9wr4TLoN7e3J4ndH8jGg/46VbqIVGL/jQsFQCDqKI
|
34
|
+
nM4hcwmd/0ss5wQBOx+5n36nR+Adb1HHOVXdX2P5ktxUEd4Xkt51q+NxO//RYngB
|
35
|
+
GJRqwrLlLH1jf+y9mULfzRHWpAl2aXcxCW57d/+fCCeRz7+v9Qz/Eq2ieKucVeNi
|
36
|
+
sEIE5PVGOPAiOJxBYbphEwugUb/AFjziPepwMN0jDLx6CQ==
|
37
|
+
-----END CERTIFICATE-----
|
38
|
+
date: 2020-05-20 00:00:00.000000000 Z
|
39
|
+
dependencies:
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: rbnacl
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 7.0.0
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 7.0.0
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: bundler
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '1.17'
|
61
|
+
type: :development
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '1.17'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: rake
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 13.0.1
|
75
|
+
type: :development
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: 13.0.1
|
82
|
+
description:
|
83
|
+
email:
|
84
|
+
- joseph.fierro@logosnetworks.com
|
85
|
+
executables:
|
86
|
+
- aspis
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- Gemfile
|
92
|
+
- LICENSE.txt
|
93
|
+
- README.md
|
94
|
+
- Rakefile
|
95
|
+
- aspis.1
|
96
|
+
- aspis.gemspec
|
97
|
+
- bin/console
|
98
|
+
- bin/setup
|
99
|
+
- certs/jsfierro.pem
|
100
|
+
- exe/aspis
|
101
|
+
- lib/aspis.rb
|
102
|
+
- lib/aspis/aspis_init.rb
|
103
|
+
- lib/aspis/asymmetric_decrypt.rb
|
104
|
+
- lib/aspis/asymmetric_encrypt.rb
|
105
|
+
- lib/aspis/generate_keys.rb
|
106
|
+
- lib/aspis/symmetric_decrypt.rb
|
107
|
+
- lib/aspis/symmetric_encrypt.rb
|
108
|
+
- lib/aspis/version.rb
|
109
|
+
homepage: https://github.com/jsfierro/aspis
|
110
|
+
licenses:
|
111
|
+
- ISC
|
112
|
+
metadata: {}
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
requirements: []
|
128
|
+
rubygems_version: 3.0.3
|
129
|
+
signing_key:
|
130
|
+
specification_version: 4
|
131
|
+
summary: command line encryption tool using rbnacl
|
132
|
+
test_files: []
|
metadata.gz.sig
ADDED
Binary file
|