frasier 0.5.1
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.
- data.tar.gz.sig +1 -0
- data/.gitignore +18 -0
- data/.travis.yml +6 -0
- data/Gemfile +15 -0
- data/LICENSE.txt +22 -0
- data/README.md +71 -0
- data/Rakefile +31 -0
- data/bin/frasier +8 -0
- data/certs/pjaspers.pem +19 -0
- data/checksum/frasier-0.5.1.gem.sha512 +1 -0
- data/frasier.gemspec +26 -0
- data/lib/frasier.rb +20 -0
- data/lib/frasier/book.rb +22 -0
- data/lib/frasier/cli.rb +108 -0
- data/lib/frasier/dice_list.rb +59 -0
- data/lib/frasier/generator.rb +44 -0
- data/lib/frasier/library.rb +40 -0
- data/lib/frasier/version.rb +3 -0
- data/test/book_test.rb +23 -0
- data/test/dice_list_test.rb +44 -0
- data/test/files/my_man_jeeves +7295 -0
- data/test/files/small_sample +7 -0
- data/test/generator_test.rb +29 -0
- data/test/helper.rb +11 -0
- data/test/library_test.rb +37 -0
- metadata +144 -0
- metadata.gz.sig +0 -0
data.tar.gz.sig
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
��#��pv�άV�;,����K���x��w&'�m;���ѿTļ��c=_az�o�r�@�>pS��9c7:�̑���`AE����k����B�'y��VS�d2���a����N�EG����ik���>�k�(�F��W��&yHC�+���`x'�
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in frasier.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
group :development do
|
7
|
+
gem 'pry', '~> 0.9.12.6'
|
8
|
+
end
|
9
|
+
|
10
|
+
group :test do
|
11
|
+
# Adding rake for Travis.
|
12
|
+
gem 'rake'
|
13
|
+
gem 'minitest', '~> 5.3.2'
|
14
|
+
gem 'mocha', '~> 1.0.0'
|
15
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 pjaspers
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
[](https://travis-ci.org/pjaspers/frasier)
|
2
|
+
|
3
|
+
## What does it do?
|
4
|
+
|
5
|
+
Generates phrases using words out of a book. Using high tech computering.
|
6
|
+
|
7
|
+
Those phrases can be used as passphrases, you can set how many words and which books, so you can claim to have passphrase inspired by Great Literature.
|
8
|
+
|
9
|
+
For example using 'The Great Gatsby' it generates easy to remember* phrases like:
|
10
|
+
|
11
|
+
- `gloved modelling pool daylight longer`
|
12
|
+
- `wrong flirtation great bembergs happier`
|
13
|
+
- `obscurity flat quarter nodding libel`
|
14
|
+
|
15
|
+
`gem install frasier` and you're good to go.
|
16
|
+
|
17
|
+
* Note: not actually easy to remember.
|
18
|
+
|
19
|
+
## Isn't this Diceware™?
|
20
|
+
|
21
|
+
Inspired by [Diceware™](http://world.std.com/~reinhold/diceware.html), except instead of being cryptogaphically sound, this uses not a carefully created word list but uses text from books.
|
22
|
+
|
23
|
+
```
|
24
|
+
Diceware™ is a method for picking passphrases that uses dice to select
|
25
|
+
words at random from a special list called the Diceware Word
|
26
|
+
List. Each word in the list is preceded by a five digit number. All
|
27
|
+
the digits are between one and six, allowing you to use the outcomes
|
28
|
+
of five dice rolls to select one unique word from the list.
|
29
|
+
```
|
30
|
+
|
31
|
+
It uses a simple `rand` to simulate the dice roll, the original [Diceware™](http://world.std.com/~reinhold/diceware.html) recommends against this.
|
32
|
+
|
33
|
+
Having said all that, it's safer then creating "th3Ult1mat2Pa$$word" for each service you encounter.
|
34
|
+
|
35
|
+
|
36
|
+
## How do I get books?
|
37
|
+
|
38
|
+
[Project Gutenberg](http://www.gutenberg.org) has some classics which can be freely downloaded as plain text, after that save them in `~/.config/frasier/` and you're good to go.
|
39
|
+
|
40
|
+
## What's with the name?
|
41
|
+
|
42
|
+
You're on a need to know basis.
|
43
|
+
|
44
|
+
## Usage
|
45
|
+
|
46
|
+
```
|
47
|
+
Usage: frasier [options]
|
48
|
+
-n, --number [NUMBER] Generate passphrase with <n> words
|
49
|
+
-l, --list-books List available books
|
50
|
+
-b, --book [NAME] Specify book to generate from
|
51
|
+
-i, --info Show entropy info
|
52
|
+
-h, --help Show this message
|
53
|
+
--version Show version
|
54
|
+
```
|
55
|
+
|
56
|
+
## Signed gem
|
57
|
+
|
58
|
+
`frasier` is cryptographically signed. To be sure the gem you install hasn’t been tampered with:
|
59
|
+
|
60
|
+
1. Download certificate https://raw.github.com/pjaspers/frasier/certs/pjaspers.pem
|
61
|
+
2. Add `gem cert –add pjaspers.pem`
|
62
|
+
3. gem install frasier -P HighSecurity
|
63
|
+
|
64
|
+
## Contributing
|
65
|
+
|
66
|
+
1. Fork it
|
67
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
68
|
+
3. Do some awesome computering
|
69
|
+
4. Commit your changes (`git commit -am 'Add some feature'`)
|
70
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
71
|
+
6. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
def abort_with_error(msg)
|
4
|
+
abort "\n\e[31mError:\e[0m #{msg}"
|
5
|
+
end
|
6
|
+
|
7
|
+
task :test do
|
8
|
+
$LOAD_PATH.unshift('lib', 'test')
|
9
|
+
Dir.glob('./test/**/*_test.rb') { |f| require f }
|
10
|
+
end
|
11
|
+
|
12
|
+
task :default => :test
|
13
|
+
|
14
|
+
task :console do
|
15
|
+
require 'pry'
|
16
|
+
require './lib/frasier'
|
17
|
+
ARGV.clear
|
18
|
+
Pry.start Frasier
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
task :checksum do
|
23
|
+
require 'digest/sha2'
|
24
|
+
abort_with_error "Set 'GEM' with name-0.x.x to calculate checksum" unless ENV["GEM"]
|
25
|
+
name = ENV["GEM"]
|
26
|
+
built_gem_path = 'pkg/frasier-0.5.1.gem'
|
27
|
+
checksum = Digest::SHA512.new.hexdigest(File.read(built_gem_path))
|
28
|
+
checksum_path = 'checksum/frasier-0.5.1.gem.sha512'
|
29
|
+
File.open(checksum_path, 'w' ) {|f| f.write(checksum) }
|
30
|
+
puts "Wrote checksum to #{checksum_path}"
|
31
|
+
end
|
data/bin/frasier
ADDED
data/certs/pjaspers.pem
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIIDKDCCAhCgAwIBAgIBADANBgkqhkiG9w0BAQUFADA6MQ0wCwYDVQQDDARwaWV0
|
3
|
+
MRUwEwYKCZImiZPyLGQBGRYFamFzcGUxEjAQBgoJkiaJk/IsZAEZFgJyczAeFw0x
|
4
|
+
NDA0MDcyMTQyMDNaFw0xNTA0MDcyMTQyMDNaMDoxDTALBgNVBAMMBHBpZXQxFTAT
|
5
|
+
BgoJkiaJk/IsZAEZFgVqYXNwZTESMBAGCgmSJomT8ixkARkWAnJzMIIBIjANBgkq
|
6
|
+
hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+SQ5IzhMaUM/jrUBAP5l0/Z5ttLQb2lT
|
7
|
+
hotS1Yraz3hclbGhBhDcQilW11GSftUbWhaiMQwNXqaEN9ctmzE5JukOdwhtNSLq
|
8
|
+
IDw+TEEXbg/VImlFhryg9vunffPeOBUlnxdY484DOushNN50H/LA+jAoFST3+oZq
|
9
|
+
CXuA3yFi8ZTPqhHrEn1bkycUfLX+TOm07ZfCPly9fAVYhOYaVaPeYkHAO83+Ic63
|
10
|
+
9YGqD1sFeZ7hfSxqfYEBxpiM0+qztqHCVNrehkzX2oUmY06eVpSu2mzLYmcCD58f
|
11
|
+
inVDRBe/3ygkACxZCA7pjIuqHOpKxSbYMrgEkrAtpA7lkIMaveMY9QIDAQABozkw
|
12
|
+
NzAJBgNVHRMEAjAAMB0GA1UdDgQWBBSSJbKSj/62BYq/1utxyqsShRum8jALBgNV
|
13
|
+
HQ8EBAMCBLAwDQYJKoZIhvcNAQEFBQADggEBAJkG60vFt/GBuMvTdMtFCw4jJiRo
|
14
|
+
8n6dMQrL2kZdD0ZNeidLfTMzhflv+9ZrWfcFWzfqaGgKJckBd2wlfQiR78ts9zom
|
15
|
+
bX9B9n/Ad32womLnb0aeTuEHX6Vy4qs4zl+VnXy4g1NQ2lfcnksUsjBM8v8GYTjy
|
16
|
+
ssiOQnzrWxNyjR01uY5g/ON/IlRKNpihe7Qze1oGWdopZP6Lwfhir5ShbDETCKQv
|
17
|
+
CuJWUOdBRsHbSraLW8n/vc/fZGqVzDAghs26bxmwicw+4/VgO2soYka81t8pUuG6
|
18
|
+
vLWWrtmIv8+cmMgvltP5AR5j2oYHYPPA9YFXuO9t1JVqJiNHm9JNRseEoBc=
|
19
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1 @@
|
|
1
|
+
1a4ea27f78e2adfdfd6805e971af94a43f6e9f94aa7ccb6bca9ebef783e2f449115f9ace8b5615ade181d50d43ae2f3e81cc29a6addcae743f87f571b8bdc2dd
|
data/frasier.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'frasier/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "frasier"
|
8
|
+
spec.version = Frasier::VERSION
|
9
|
+
spec.authors = ["pjaspers"]
|
10
|
+
spec.email = ["piet@jaspe.rs"]
|
11
|
+
spec.description = %q{Passphrase generator using plain text books}
|
12
|
+
spec.summary = %q{Inspired by Diceware™, except instead of being cryptogaphically sound, this uses not a carefully created word list but uses text from books.}
|
13
|
+
spec.homepage = "https://github.com/pjaspers/frasier"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.cert_chain = ['certs/pjaspers.pem']
|
22
|
+
spec.signing_key = File.expand_path("~/.ssh/gem-private_key.pem") if $0 =~ /gem\z/
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
end
|
data/lib/frasier.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "frasier/version"
|
2
|
+
require "frasier/library"
|
3
|
+
require "frasier/book"
|
4
|
+
require "frasier/dice_list"
|
5
|
+
require "frasier/generator"
|
6
|
+
require "frasier/cli"
|
7
|
+
|
8
|
+
module Frasier
|
9
|
+
|
10
|
+
# At Cornell University, they have an incredible piece of scientific
|
11
|
+
# equipment known as the "tunneling electron microscope."
|
12
|
+
#
|
13
|
+
# Now, this microscope is so powerful that by firing electrons, you
|
14
|
+
# can actually see images of the atom, the infinite decimally minute
|
15
|
+
# building block of our universe.
|
16
|
+
#
|
17
|
+
# Roger, if I were using that microscope right now, I still wouldn't
|
18
|
+
# be able to locate my interest in your problem.
|
19
|
+
|
20
|
+
end
|
data/lib/frasier/book.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module Frasier
|
2
|
+
|
3
|
+
class Book
|
4
|
+
attr_accessor :title, :path
|
5
|
+
|
6
|
+
def initialize(path)
|
7
|
+
raise Errno::ENOENT unless File.exist?(path || "")
|
8
|
+
@title = title_from_path(path)
|
9
|
+
@path = path
|
10
|
+
end
|
11
|
+
|
12
|
+
def dice_word_list
|
13
|
+
DiceList.new(File.open(path)).word_list
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def title_from_path(path)
|
19
|
+
File.basename(path).split("_").map(&:capitalize).join(" ")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/frasier/cli.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
require "ostruct"
|
2
|
+
require "optparse"
|
3
|
+
|
4
|
+
module Frasier
|
5
|
+
|
6
|
+
class CLI
|
7
|
+
def self.parse(args)
|
8
|
+
options = OpenStruct.new
|
9
|
+
options.number_of_words = 5
|
10
|
+
|
11
|
+
opts = OptionParser.new do |opts|
|
12
|
+
opts.banner = "Usage: frasier [options]"
|
13
|
+
|
14
|
+
opts.on("-n", "--number [NUMBER]", Integer, "Generate passphrase with <n> words") do |n|
|
15
|
+
options.number_of_words = n.to_i
|
16
|
+
end
|
17
|
+
|
18
|
+
opts.on("-l", "--list-books", "List available books") do |list|
|
19
|
+
books = Library.new.books
|
20
|
+
longest_title = books.map(&:title).max.length
|
21
|
+
puts books.map{|b| " %s - %s" % [b.title.ljust(longest_title), File.basename(b.path)]}
|
22
|
+
exit
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on("-b", "--book [NAME]", String, "Specify book to generate from") do |book|
|
26
|
+
lib = Library.new
|
27
|
+
unless lib.book_with_name(book)
|
28
|
+
puts "I don't know that book"
|
29
|
+
exit
|
30
|
+
end
|
31
|
+
options.book = book
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on("-i", "--info", "Show entropy info") do |info|
|
35
|
+
options.info = info
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
39
|
+
puts opts
|
40
|
+
exit
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on_tail("--version", "Show version") do
|
44
|
+
puts Frasier::VERSION
|
45
|
+
exit
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
if Library.new.books.empty?
|
50
|
+
puts <<BLURB
|
51
|
+
|
52
|
+
Please install some books into ~/.config/frasier/
|
53
|
+
Example:
|
54
|
+
curl -L http://www.gutenberg.org/ebooks/8164.txt.utf-8 -o ~/.config/frasier/my_man_jeeves
|
55
|
+
BLURB
|
56
|
+
exit
|
57
|
+
end
|
58
|
+
|
59
|
+
opts.parse!(args)
|
60
|
+
options
|
61
|
+
end
|
62
|
+
|
63
|
+
def initialize(options)
|
64
|
+
library = Library.new
|
65
|
+
@book = library.book_with_name(options.book) if options.book
|
66
|
+
@book = library.random_book unless @book
|
67
|
+
number_of_words = options.number_of_words
|
68
|
+
|
69
|
+
@generator = Generator.new(@book.dice_word_list, number_of_words)
|
70
|
+
print_passphrase(options.info)
|
71
|
+
end
|
72
|
+
|
73
|
+
def print_passphrase(info = true)
|
74
|
+
phrase = @generator.passphrase
|
75
|
+
# Try to copy it
|
76
|
+
copy(phrase) if copy_command
|
77
|
+
if info
|
78
|
+
number_of_guesses = 100000
|
79
|
+
duration_in_years = (@generator.duration_to_guess(number_of_guesses)/60.0/60.0/24.0/360.0).round(2)
|
80
|
+
puts "From: #{@book.title}"
|
81
|
+
puts "Bits of entropy: #{@generator.bits_of_entropy}"
|
82
|
+
puts "At 100 000 guesses/s, it would take %s years to guess" % duration_in_years
|
83
|
+
puts ""
|
84
|
+
end
|
85
|
+
puts red(phrase)
|
86
|
+
end
|
87
|
+
|
88
|
+
def copy_command
|
89
|
+
os = RbConfig::CONFIG['host_os']
|
90
|
+
return 'pbcopy' if os =~ /mac|darwin/
|
91
|
+
return 'xclip -selection clipboard' if os =~ /linux|bsd|cygwin/
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def copy(value)
|
96
|
+
return unless copy_command
|
97
|
+
begin
|
98
|
+
IO.popen(copy_command,"w") {|cc| cc.write(value)}
|
99
|
+
value
|
100
|
+
rescue Errno::ENOENT
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def red(s)
|
105
|
+
"\e[31m#{s}\e[0m"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require "set"
|
3
|
+
module Frasier
|
4
|
+
|
5
|
+
class DiceList
|
6
|
+
|
7
|
+
def initialize(io)
|
8
|
+
if io.respond_to? :each_line
|
9
|
+
@io = io
|
10
|
+
else
|
11
|
+
raise ArgumentError, "#{io} does not respond to `each_line`"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# [Full list of requirements](http://world.std.com/%7Ereinhold/dicewarekit.html)
|
16
|
+
#
|
17
|
+
# Not following all the rules since most of the books don't seem to have
|
18
|
+
# enough unique words to make a sizeable list. We'd need at least 7776 words,
|
19
|
+
# but since you can use multiple sources that's not a hard rule.
|
20
|
+
#
|
21
|
+
def valid_words
|
22
|
+
return @valid_words if @valid_words && @valid_words.length > 0
|
23
|
+
|
24
|
+
@valid_words = Set.new
|
25
|
+
@io.each_line do |l|
|
26
|
+
# Funny story, in place methods are faster.
|
27
|
+
l.gsub!(/[^[:alnum:]^[:blank:]]/, "")
|
28
|
+
l.downcase!
|
29
|
+
l.strip!
|
30
|
+
# Only 'short' words
|
31
|
+
l.split(" ").reject{|w| w.length < 3 || w.length > 10}.each do |w|
|
32
|
+
@valid_words.add(w)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
@valid_words
|
36
|
+
end
|
37
|
+
|
38
|
+
def word_list
|
39
|
+
return @word_list if @word_list
|
40
|
+
|
41
|
+
# The coolest line in this whole source.
|
42
|
+
#
|
43
|
+
# Get all permuations with `1,2,3,4,5,6` (e.g. from '11111' to '66666')
|
44
|
+
# only take as many as we need.
|
45
|
+
indexes = (1..6).to_a.repeated_permutation(5).map(&:join).first(valid_words.length)
|
46
|
+
|
47
|
+
# Zip the permutations with the words
|
48
|
+
# So this creates a hash like:
|
49
|
+
#
|
50
|
+
# {'11111': 'first',
|
51
|
+
# ...
|
52
|
+
# '66665': 'another',
|
53
|
+
# '66666': 'last'}
|
54
|
+
|
55
|
+
@word_list = Hash[indexes.zip(valid_words)]
|
56
|
+
@word_list
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|