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 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
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'encry'
5
+ Encry.run(ARGV)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Encry
4
+ class Options
5
+ DEFAULT = {
6
+ length: 16,
7
+ iterations: 100_000,
8
+ digest: 'sha256',
9
+ key_length: 32,
10
+ show: false,
11
+ copy: true,
12
+ quiet: false
13
+ }.freeze
14
+ end
15
+ 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
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Encry
4
+ VERSION = '0.1.0'
5
+ 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: []