encry 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/console +11 -0
- data/bin/encry +5 -0
- data/bin/setup +8 -0
- data/lib/encry/inputs.rb +29 -0
- data/lib/encry/options.rb +15 -0
- data/lib/encry/outputs.rb +37 -0
- data/lib/encry/parser.rb +78 -0
- data/lib/encry/password.rb +33 -0
- data/lib/encry/validator.rb +36 -0
- data/lib/encry/version.rb +5 -0
- data/lib/encry.rb +28 -0
- metadata +86 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 68c4b6dd313beaac728223a384002fd1a51f48a13aab0ebc88c2bec8091ac560
|
|
4
|
+
data.tar.gz: 7ae4c056a844e939a23f21d291ace1878cbae81ca43dab2bb060c6faa600fce0
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 91af72c2891c7186600e35075f8b70f9ff96c7ca6aa36bdf3d30e3f363de2258280d5d036c26cdff0224b0f48e983e29152f0705207ba9fc44d5175e363ba63a
|
|
7
|
+
data.tar.gz: e94174bf2c1a1b68f4fded3d7c99b963dfccc080452ecb98d19679391a95cc70e53bd00ca34d160da425a3ea2d446c8033e68725ec08bfd0085322e1e2e17270
|
data/bin/console
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'encry'
|
|
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
|
+
require 'irb'
|
|
11
|
+
IRB.start(__FILE__)
|
data/bin/encry
ADDED
data/bin/setup
ADDED
data/lib/encry/inputs.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'io/console'
|
|
4
|
+
|
|
5
|
+
module Encry
|
|
6
|
+
class Inputs
|
|
7
|
+
def self.get_main_word(options)
|
|
8
|
+
return options[:main_word] if options[:main_word]
|
|
9
|
+
|
|
10
|
+
print 'Enter main word: '
|
|
11
|
+
main_word = $stdin.noecho(&:gets).tap { puts }.chomp
|
|
12
|
+
|
|
13
|
+
abort('Main word is empty') if main_word.strip.empty?
|
|
14
|
+
|
|
15
|
+
main_word
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.get_site_word(argv)
|
|
19
|
+
return argv[0] if argv[0]
|
|
20
|
+
|
|
21
|
+
print 'Enter site word: '
|
|
22
|
+
site_word = gets.chomp
|
|
23
|
+
|
|
24
|
+
abort('Site word is empty') if site_word.strip.empty?
|
|
25
|
+
|
|
26
|
+
site_word
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'clipboard'
|
|
4
|
+
|
|
5
|
+
module Encry
|
|
6
|
+
class Outputs
|
|
7
|
+
def initialize(site_word, password, options)
|
|
8
|
+
@site_word = site_word
|
|
9
|
+
@password = password
|
|
10
|
+
@options = options
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def password
|
|
14
|
+
show_debug_info if @options[:debug]
|
|
15
|
+
|
|
16
|
+
if @options[:show]
|
|
17
|
+
puts @password
|
|
18
|
+
elsif @options[:copy]
|
|
19
|
+
Clipboard.copy(@password)
|
|
20
|
+
puts 'Copied!' unless @options[:quiet]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def show_debug_info
|
|
27
|
+
return unless @options[:debug]
|
|
28
|
+
|
|
29
|
+
puts "[DEBUG] Site: #{@site_word}"
|
|
30
|
+
puts "[DEBUG] Digest: #{@options[:digest]}"
|
|
31
|
+
puts "[DEBUG] Iterations: #{@options[:iterations]}"
|
|
32
|
+
puts "[DEBUG] Key length: #{@options[:key_length]}"
|
|
33
|
+
puts "[DEBUG] Output length: #{@options[:length]}"
|
|
34
|
+
puts "[DEBUG] Final password: #{@password}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
data/lib/encry/parser.rb
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'optparse'
|
|
4
|
+
require_relative 'validator'
|
|
5
|
+
|
|
6
|
+
module Encry
|
|
7
|
+
class Parser
|
|
8
|
+
def initialize(options)
|
|
9
|
+
@options = options
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def run(argv)
|
|
13
|
+
begin
|
|
14
|
+
build.parse!(argv)
|
|
15
|
+
rescue OptionParser::InvalidOption, OptionParser::InvalidArgument, OptionParser::MissingArgument => e
|
|
16
|
+
warn "Error: #{e.message}"
|
|
17
|
+
warn 'Use `--help` to see available @options'
|
|
18
|
+
exit 1
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Validator.run(@options)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def build
|
|
27
|
+
OptionParser.new do |opts|
|
|
28
|
+
opts.banner = 'Usage: encry [site] [options]'
|
|
29
|
+
|
|
30
|
+
add_password_options(opts)
|
|
31
|
+
add_input_options(opts)
|
|
32
|
+
add_output_options(opts)
|
|
33
|
+
add_misc_options(opts)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def add_password_options(opts)
|
|
38
|
+
opts.on('-l', '--length N', Integer, 'Output password length (default: 16)') { |v| @options[:length] = v }
|
|
39
|
+
opts.on('-i', '--iterations N', Integer, 'KDF iterations (default: 100000)') { |v| @options[:iterations] = v }
|
|
40
|
+
opts.on('-d', '--digest NAME', String, 'Digest algorithm (sha256, sha512...)') { |v| @options[:digest] = v }
|
|
41
|
+
opts.on('--key-length N', Integer, 'Derived key length in bytes (default: 32)') { |v| @options[:key_length] = v }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def add_input_options(opts)
|
|
45
|
+
opts.on('--main WORD', String, 'Main word (insecure)') { |v| @options[:main_word] = v }
|
|
46
|
+
opts.on('--env VAR', String, 'Read main word from ENV') { |v| @options[:main_word] = ENV.fetch(v, nil) }
|
|
47
|
+
opts.on('--stdin', 'Read main word from STDIN') do
|
|
48
|
+
print 'Enter main word: '
|
|
49
|
+
@options[:main_word] = gets.chomp
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def add_output_options(opts)
|
|
54
|
+
opts.on('--show', 'Show password instead of copying') do
|
|
55
|
+
@options[:show] = true
|
|
56
|
+
@options[:copy] = false
|
|
57
|
+
end
|
|
58
|
+
opts.on('--no-copy', "Don't copy to clipboard") { @options[:copy] = false }
|
|
59
|
+
opts.on('--quiet', 'No output') { @options[:quiet] = true }
|
|
60
|
+
opts.on('--debug', 'Show debug information') { @options[:debug] = true }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def add_misc_options(opts)
|
|
64
|
+
opts.on('--version', 'Show version') do
|
|
65
|
+
puts Encry::VERSION
|
|
66
|
+
exit
|
|
67
|
+
end
|
|
68
|
+
opts.on('-h', '--help', 'Show help') do
|
|
69
|
+
puts opts
|
|
70
|
+
exit
|
|
71
|
+
end
|
|
72
|
+
opts.on('--list-digests', 'List supported digest algorithms') do
|
|
73
|
+
puts Validator::AVAILABLE_DIGESTS.sort.join("\n")
|
|
74
|
+
exit
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
require 'base64'
|
|
5
|
+
|
|
6
|
+
module Encry
|
|
7
|
+
class Password
|
|
8
|
+
def self.generate(main_word, site_word, options)
|
|
9
|
+
key = derive_key(main_word, site_word, options)
|
|
10
|
+
|
|
11
|
+
base64 = Base64.urlsafe_encode64(key)
|
|
12
|
+
|
|
13
|
+
base64[0, options[:length]]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.derive_key(main_word, site_word, options)
|
|
17
|
+
salt = Digest::SHA256.digest(site_word)
|
|
18
|
+
|
|
19
|
+
begin
|
|
20
|
+
OpenSSL::KDF.pbkdf2_hmac(
|
|
21
|
+
main_word,
|
|
22
|
+
salt: salt,
|
|
23
|
+
iterations: options[:iterations],
|
|
24
|
+
length: options[:key_length],
|
|
25
|
+
hash: options[:digest].downcase
|
|
26
|
+
)
|
|
27
|
+
rescue ArgumentError => e
|
|
28
|
+
warn "Error: #{e.message}"
|
|
29
|
+
exit 1
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Encry
|
|
4
|
+
class Validator
|
|
5
|
+
AVAILABLE_DIGESTS = %w[
|
|
6
|
+
blake2b512
|
|
7
|
+
blake2s256
|
|
8
|
+
md5
|
|
9
|
+
rmd160
|
|
10
|
+
sha1
|
|
11
|
+
sha224
|
|
12
|
+
sha256
|
|
13
|
+
sha3-224
|
|
14
|
+
sha3-256
|
|
15
|
+
sha3-384
|
|
16
|
+
sha3-512
|
|
17
|
+
sha384
|
|
18
|
+
sha512
|
|
19
|
+
sha512-224
|
|
20
|
+
sha512-256
|
|
21
|
+
sm3
|
|
22
|
+
].freeze
|
|
23
|
+
|
|
24
|
+
def self.run(options)
|
|
25
|
+
unless AVAILABLE_DIGESTS.include?(options[:digest].downcase)
|
|
26
|
+
warn "Invalid digest algorithm: #{options[:digest]}"
|
|
27
|
+
warn "Available algorithms: #{AVAILABLE_DIGESTS.sort.join(', ')}"
|
|
28
|
+
exit 1
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
abort('Error: --key-length option must be greater than 0') if options[:key_length] <= 0
|
|
32
|
+
abort('Error: --iterations option must be greater than 0') if options[:iterations] <= 0
|
|
33
|
+
abort('Error: --length option must be greater than 0') if options[:length] <= 0
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/encry.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'encry/version'
|
|
4
|
+
|
|
5
|
+
module Encry
|
|
6
|
+
autoload :Parser, File.expand_path('encry/parser.rb', __dir__)
|
|
7
|
+
autoload :Password, File.expand_path('encry/password.rb', __dir__)
|
|
8
|
+
autoload :Options, File.expand_path('encry/options.rb', __dir__)
|
|
9
|
+
autoload :Inputs, File.expand_path('encry/inputs.rb', __dir__)
|
|
10
|
+
autoload :Outputs, File.expand_path('encry/outputs.rb', __dir__)
|
|
11
|
+
|
|
12
|
+
class Error < StandardError; end
|
|
13
|
+
|
|
14
|
+
def self.run(argv)
|
|
15
|
+
options = Options::DEFAULT.dup
|
|
16
|
+
|
|
17
|
+
parser = Parser.new(options)
|
|
18
|
+
parser.run(argv)
|
|
19
|
+
|
|
20
|
+
site_word = Inputs.get_site_word(argv)
|
|
21
|
+
main_word = Inputs.get_main_word(options)
|
|
22
|
+
|
|
23
|
+
password = Password.generate(main_word, site_word, options)
|
|
24
|
+
|
|
25
|
+
output = Outputs.new(site_word, password, options)
|
|
26
|
+
output.password
|
|
27
|
+
end
|
|
28
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: encry
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- statevdev
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2025-06-19 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: clipboard
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: base64
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
description: CLI tool to derive deterministic passwords using a master key and site
|
|
42
|
+
keyword
|
|
43
|
+
email:
|
|
44
|
+
- statev.dev@gmail.com
|
|
45
|
+
executables:
|
|
46
|
+
- encry
|
|
47
|
+
extensions: []
|
|
48
|
+
extra_rdoc_files: []
|
|
49
|
+
files:
|
|
50
|
+
- bin/console
|
|
51
|
+
- bin/encry
|
|
52
|
+
- bin/setup
|
|
53
|
+
- lib/encry.rb
|
|
54
|
+
- lib/encry/inputs.rb
|
|
55
|
+
- lib/encry/options.rb
|
|
56
|
+
- lib/encry/outputs.rb
|
|
57
|
+
- lib/encry/parser.rb
|
|
58
|
+
- lib/encry/password.rb
|
|
59
|
+
- lib/encry/validator.rb
|
|
60
|
+
- lib/encry/version.rb
|
|
61
|
+
homepage: https://github.com/statevdev/encry
|
|
62
|
+
licenses:
|
|
63
|
+
- MIT
|
|
64
|
+
metadata:
|
|
65
|
+
homepage_uri: https://github.com/statevdev/encry
|
|
66
|
+
source_code_uri: https://github.com/statevdev/encry
|
|
67
|
+
post_install_message:
|
|
68
|
+
rdoc_options: []
|
|
69
|
+
require_paths:
|
|
70
|
+
- lib
|
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: 3.1.0
|
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - ">="
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: '0'
|
|
81
|
+
requirements: []
|
|
82
|
+
rubygems_version: 3.3.7
|
|
83
|
+
signing_key:
|
|
84
|
+
specification_version: 4
|
|
85
|
+
summary: Deterministic password generator
|
|
86
|
+
test_files: []
|