pws 0.1.3 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2010 Jan Lelis
1
+ Copyright (c) 2010-2012 Jan Lelis
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README CHANGED
@@ -1,5 +1,13 @@
1
- pws password safe
1
+ pws is a command-line password safe/manager written in Ruby. Install it with
2
2
 
3
- http://rbjl.net/41-tutorial-build-your-own-password-safe-with-ruby
3
+ $ gem install pws
4
+
5
+ Please run `pws help` for usage information.
6
+
7
+ Trust the code by reading the source! Originally based on: http://rbjl.net/41-tutorial-build-your-own-password-safe-with-ruby
8
+
9
+ Cucumber specs loosely based on https://github.com/thecatwasnot/passwordsafe/blob/master/features/add.feature by thecatwasnot - thanks.
10
+
11
+ Copyright: 2010-2012 Jan Lelis, MIT-LICENSE
4
12
 
5
13
  J-_-L
data/Rakefile CHANGED
@@ -1,19 +1,34 @@
1
- require 'rubygems'
2
- require 'rake'
3
-
4
- begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "pws"
8
- gem.summary = %Q{pws is a little password safe. See pws help for more information}
9
- gem.email = "mail@janlelis.de"
10
- gem.homepage = "http://rbjl.net/41-tutorial-build-your-own-password-safe-with-ruby"
11
- gem.authors = ["Jan Lelis"]
12
- gem.executables = %w[pws]
13
- gem.add_dependency 'clipboard'
14
- gem.add_dependency 'zucker'
15
- end
16
- Jeweler::GemcutterTasks.new
17
- rescue LoadError
18
- puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
1
+ require 'fileutils'
2
+
3
+ NAME = Dir['*.gemspec'].first
4
+
5
+ def gemspec
6
+ @gemspec ||= eval(File.read(NAME), binding, NAME)
7
+ end
8
+
9
+ desc "Build the gem"
10
+ task :gem => :gemspec do
11
+ sh "gem build #{NAME}"
12
+ FileUtils.mkdir_p 'pkg'
13
+ FileUtils.mv "#{gemspec.name}-#{gemspec.version}.gem", 'pkg'
14
+ end
15
+
16
+ desc "Install the gem locally"
17
+ task :install => :gem do
18
+ sh %{gem install pkg/#{gemspec.name}-#{gemspec.version} --no-rdoc --no-ri}
19
19
  end
20
+
21
+ desc "Generate the gemspec"
22
+ task :generate do
23
+ puts gemspec.to_ruby
24
+ end
25
+
26
+ desc "Validate the gemspec"
27
+ task :gemspec do
28
+ gemspec.validate
29
+ end
30
+
31
+ require 'cucumber/rake/task'
32
+ Cucumber::Rake::Task.new(:spec)
33
+
34
+ task :default => :spec
data/bin/pws CHANGED
@@ -1,259 +1,89 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- # pws
4
- # see http://rbjl.net/41-tutorial-build-your-own-password-safe-with-ruby for more information
3
+ require_relative '../lib/pws'
5
4
 
6
- require 'rubygems' if RUBY_VERSION[2] == ?8
5
+ action_or_namespace = $*.shift
7
6
 
8
- require 'openssl'
9
- require 'fileutils'
10
- require 'clipboard' # gem install clipboard
11
- require 'zucker/alias_for' # gem install zucker
12
- require 'zucker/kernel'
13
- require 'zucker/version'
14
-
15
- class PasswordSafe
16
- VERSION = "0.1.3".freeze
17
-
18
- Entry = Struct.new :description, :password
19
-
20
- class NoAccess < StandardError; end
21
-
22
- # Creates a new password safe. Takes the path to the password file, by default: ~/.pws
23
- def initialize( filename = File.expand_path('~/.pws') )
24
- @pwfile = filename
25
-
26
- access_safe
27
- read_safe
28
- end
29
-
30
- # Add a password entry, params: name, description (optional), password (optional, opens prompt if not given)
31
- def add(key, description = nil, password = nil)
32
- if @pwdata[key]
33
- print "Do you really want to overwrite the exisiting password entry? (press <Enter> to proceed)"
34
- return unless (a = $stdin.getc) == 10 || a == 13
35
- else
36
- @pwdata[key] = Entry.new
37
- end
38
- @pwdata[key].password = password || ask_for_password( "please enter a password for #{key}" )
39
- @pwdata[key].description = description
40
- write_safe
41
-
42
- puts "The password safe has been updated"
43
- end
44
- aliases_for :add, :a, :set, :create, :update, :[]= # using zucker/alias_for
45
-
46
- # Gets the password entry and copies it to the clipboard. The second parameter is the time in seconds it stays there
47
- def get(key, seconds = 20)
48
- if pw_plaintext = @pwdata[key] && @pwdata[key].password
49
- Clipboard.copy pw_plaintext
50
- if seconds && seconds.to_i > 0
51
- puts "The password is available in your clipboard for #{seconds.to_i} seconds"
52
- sleep seconds.to_i
53
- Clipboard.clear
54
- else
55
- puts "The password has been copied to your clipboard"
56
- end
57
- else
58
- puts "No password entry found for #{key}"
59
- end
60
- end
61
- aliases_for :get, :g, :entry, :[]
62
-
63
- # Removes a specific password entry
64
- def remove(key)
65
- if @pwdata.delete key
66
- puts "#{key} has been removed"
67
- else
68
- puts "Nothing removed"
69
- end
70
- end
71
- aliases_for :remove, :r, :delete
72
-
73
- # Shows a password entry list
74
- def show
75
- puts "Available passwords \n" +
76
-
77
- if @pwdata.empty?
78
- ' (none)'
79
- else
80
- @pwdata.map{ |key, pwentry|
81
- " #{key}" + if pwentry.description then ": #{pwentry.description}" else '' end
82
- }*"\n"
83
- end
84
- end
85
- aliases_for :show, :s, :list
86
-
87
- # Shows descriptions of some password entries
88
- def description(*keys)
89
- keys.each{ |key|
90
- if @pwdata[key]
91
- puts "#{key}: #{@pwdata[key].description || key}"
92
- else
93
- puts "No password entry found for #{key}"
94
- end
95
- }
96
- end
97
-
98
- # Changes the master password
99
- def master
100
- @pwhash = Encryptor.hash ask_for_password 'please enter a new master password'
101
- write_safe
102
- puts 'The master password has been changed'
103
- end
104
- aliases_for :master, :m
105
-
106
- # Adds a password entry with a fresh generated random password
107
- def generate( key, description = nil, length = 128, chars = (32..126).map(&:chr) )
108
- add key, nil, (1..length).map{ chars[rand chars.size] }.join # possible in 1.9: chars.sample
109
- end
110
- alias_for :generate, :gen
111
-
112
- # Prevents accidental displaying, e.g. in irb
113
- def to_s
114
- '#<just another password safe>'
115
- end
116
- alias_for :to_s, :inspect
7
+ if action_or_namespace =~ /^-.*$/
8
+ @action = $*.shift
9
+ @namespace = $& unless $& == ?-
10
+ else
11
+ @action = action_or_namespace
12
+ @namespace = nil
13
+ end
117
14
 
118
- private
15
+ @action = @action ? @action.to_sym : :show
16
+ @args = [*$*]
119
17
 
120
- # Tries to load and decrypt the password safe from the pwfile
121
- def read_safe
122
- pwdata_encrypted = File.read @pwfile
123
- pwdata_dump = Encryptor.decrypt( pwdata_encrypted, @pwhash )
124
- @pwdata = remove_dummy_data( Marshal.load(pwdata_dump) ) || {}
125
- rescue
126
- fail NoAccess, 'Could not decrypt/load the password safe!'
127
- end
128
-
129
- # Tries to encrypt and save the password safe into the pwfile
130
- def write_safe
131
- pwdata_dump = Marshal.dump add_dummy_data( @pwdata || {} )
132
- pwdata_encrypted = Encryptor.encrypt pwdata_dump, @pwhash
133
- File.open( @pwfile, 'w' ){ |f| f.write pwdata_encrypted }
134
- rescue
135
- fail NoAccess, 'Could not encrypt/safe the password safe!'
136
- end
18
+ begin
19
+ case @action
20
+ when :v, :version
21
+ puts "pws #{PWS::VERSION} by " + Paint["J-_-L", :bold] + " <https://github.com/janlelis/pws>"
22
+ when :help, :actions, :commands
23
+ puts \
24
+ <<HELP
25
+
26
+ #{Paint["Usage", :underline]}
27
+
28
+ #{Paint['pws', :bold]} [-namespace] action [arguments]
29
+
30
+ #{Paint["Info", :underline]}
31
+
32
+ pws allows you to manage passwords in encryted password files (safes). It
33
+ operates on the file specified in the environment variable PWS or on "~/.pws".
34
+ You can apply a namespace as first parameter that will be appended to the
35
+ filename, e.g. `pws -work show` with usual env would use "~/.pws-work".
137
36
 
138
- # Checks if the file is accessible or create a new one
139
- def access_safe
140
- if !File.file? @pwfile
141
- puts "No password safe detected, creating one at #@pwfile"
142
- FileUtils.touch @pwfile
143
- @pwhash = Encryptor.hash ask_for_password 'please enter a new master password'
144
- write_safe
37
+ #{Paint["Available actions", :underline]}
38
+
39
+ #{Paint['ls', :bold]} / list / show / status
40
+ Lists all available password entries.
41
+
42
+ #{Paint['add', :bold]} / set / store / create ( name, password = nil )
43
+ Stores a new password entry. The second argument can be the password, but
44
+ it's recommended to not pass it, but enter it interactively.
45
+
46
+ #{Paint['get', :bold]} / entry / copy / password / for ( name, seconds = 10 )
47
+ Copies the password for <name> to the clipboard. The second argument specifies,
48
+ how long the password is kept in the clipboard (0 = no deletion).
49
+
50
+ #{Paint['gen', :bold]} / generate ( name, seconds = 10, length = 64, char_pool )
51
+ Generates a new password for <name> and then copies it to the clipboard, like
52
+ get (the second argument is the time - it gets passed to get). The third
53
+ argument sets the password length. The fourth argument allows you to pass a
54
+ character pool that is used for generating the passwords.
55
+
56
+ #{Paint['rm', :bold]} / remove / del / delete ( name )
57
+ Removes a password entry.
58
+
59
+ #{Paint['mv', :bold]} / move / rename ( old_name, new_name )
60
+ Renames a password entry.
61
+
62
+ #{Paint['master', :bold]} ( password = nil )
63
+ Changes the master password.
64
+
65
+ #{Paint['v', :bold]} / version
66
+ Displays version and website.
67
+
68
+ #{Paint['help', :bold]} / actions / commands
69
+ Displays this help.
70
+
71
+ HELP
72
+ else # redirect to safe
73
+ if PWS.public_instance_methods(false).include?(@action)
74
+ PWS.new(nil, @namespace).send @action, *@args
145
75
  else
146
- @pwhash = Encryptor.hash ask_for_password 'master password'
147
- end
148
- rescue
149
- raise NoAccess, "Could not access the password safe at #@pwfile!"
150
- end
151
-
152
- # Adds some redundancy
153
- def add_dummy_data(pwdata)
154
- (5000 - pwdata.size).abs.times.map{ rand 42424242 } + # or whatever
155
- [pwdata]
156
- end
157
-
158
- def remove_dummy_data(pwdata)
159
- pwdata.last
160
- end
161
-
162
- # Prompts the user for a password
163
- def ask_for_password(prompt = 'new password')
164
- print "#{prompt}: ".capitalize
165
- system 'stty -echo' # no more terminal output
166
- pw_plaintext = ($stdin.gets||'').chop # gets without $stdin would mistakenly read_safe from ARGV
167
- system 'stty echo' # restore terminal output
168
- puts
169
-
170
- pw_plaintext
171
- end
172
-
173
- class << Encryptor = Module.new
174
- CIPHER = 'aes-256-cbc'
175
-
176
- def decrypt( iv_and_data, pwhash )
177
- iv, data = iv_and_data[0,16], iv_and_data[16..-1]
178
- crypt :decrypt, data, pwhash, iv
179
- end
180
-
181
- def encrypt( data, pwhash )
182
- iv = random_iv
183
- encrypted_data = crypt :encrypt, data, pwhash, iv
184
- iv + encrypted_data
185
- end
186
-
187
- def hash( plaintext )
188
- OpenSSL::Digest::SHA512.new( plaintext ).digest
189
- end
190
-
191
- # you need a random iv for cbc mode. It is prepended to the encrypted text.
192
- def random_iv
193
- a = OpenSSL::Cipher.new CIPHER
194
- a.random_iv
76
+ pa "Unknown action: #@action\nPlease see `pws help` for a list of available commands!", :red
195
77
  end
196
-
197
- private
198
-
199
- # Encrypts or decrypts the data with the password hash as key
200
- # NOTE: encryption exceptions do not get caught!
201
- def crypt( decrypt_or_encrypt, data, pwhash, iv )
202
- c = OpenSSL::Cipher.new CIPHER
203
- c.send decrypt_or_encrypt.to_sym
204
- c.key = pwhash
205
- c.iv = iv
206
- c.update( data ) << c.final
207
- end
208
- end
209
- end
210
-
211
- # Command line action
212
- if standalone? # using zucker/kernel (instead of __FILE__ == $0)
213
- if $*.empty?
214
- action = :show
215
- args = []
216
- else
217
- action = $*.shift[/^-{0,2}(.*)$/, 1].to_sym # also accept first argument, if it is prefixed with - or --
218
- args = [*$*]
219
- end
220
-
221
- begin
222
- case action
223
- when :h, :help, :commands
224
- puts %q{Available commands
225
- s/show/list shows all available entry names
226
- g/get/entry( name, seconds = 20 )
227
- copies the password of the entry into the clipboard
228
- d/description( names ) shows a description for the password entries
229
- a/add/set/create( name, description = nil, password = nil )
230
- creates or updates an entry. second parameter is a
231
- description. third parameter can be a password, but
232
- it's recommended to not use it and enter it, when prompted
233
- gen/generate( name, description = nil, length=128, chars = (32..126).map(&:chr) )
234
- creates or updates an entry, but generates a new
235
- random password. you can customize the length and
236
- used characters
237
- r/remove/delete( name ) deletes an password entry
238
- m/master changes the master password
239
- v/version displays version
240
- h/help/commands displays this help}
241
- when :v, :version
242
- puts "pws #{PasswordSafe::VERSION}\n J-_-L"
243
- else # redirect to safe
244
- if PasswordSafe.public_instance_methods(false).include?(
245
- if RubyVersion.is?(1.8) then action.to_s else action end ) # using zucker/version
246
-
247
- pws = PasswordSafe.new
248
- pws.send action, *args
249
- else
250
- puts "Unknown command: #{action}. Use 'help' to get a command list!"
251
- end
252
- end
253
-
254
- rescue PasswordSafe::NoAccess => e
255
- warn e.message
256
78
  end
79
+ rescue PWS::NoAccess
80
+ # pa $!.message.capitalize, :red, :bold
81
+ pa "NO ACCESS", :red, :bold
82
+ rescue ArgumentError
83
+ pa $!.message.capitalize, :red
84
+ rescue Interrupt
85
+ system 'stty echo' if $stdin.tty? # ensure terminal's working
86
+ pa "..canceled", :red
257
87
  end
258
88
 
259
89
  # J-_-L
data/lib/pws.rb CHANGED
@@ -1 +1,213 @@
1
- load File.expand_path( '../bin/pws', File.dirname(__FILE__) )
1
+ require_relative 'pws/version'
2
+ require_relative 'pws/encryptor'
3
+
4
+ require 'fileutils'
5
+ require 'clipboard'
6
+ require 'securerandom'
7
+ require 'zucker/alias_for'
8
+ require 'paint/pa'
9
+
10
+ class PWS
11
+ class NoAccess < StandardError; end
12
+
13
+ # Creates a new password safe. Takes the path to the password file, by default: ~/.pws
14
+ # Second parameter allows namespaces that get appended to the file name (uses another safe)
15
+ # You can pass the master password as third parameter (not recommended)
16
+ def initialize(filename = nil, namespace = nil, password = nil)
17
+ @pw_file = File.expand_path(filename || ENV["PWS"] || '~/.pws')
18
+ @pw_file << namespace if namespace
19
+ access_safe(password)
20
+ read_safe
21
+ end
22
+
23
+ # Shows a password entry list
24
+ def show
25
+ if @pw_data.empty?
26
+ pa %[There aren't any passwords stored at #{@pw_file}, yet], :red
27
+ else
28
+ puts Paint["Entries", :underline] + %[ in ] + @pw_file
29
+ puts @pw_data.keys.sort.map{ |key| %[- #{key}\n] }.join
30
+ end
31
+ return true
32
+ end
33
+ aliases_for :show, :ls, :list, :status
34
+
35
+ # Add a password entry, params: name, password (optional, opens prompt if not given)
36
+ def add(key, password = nil)
37
+ if @pw_data[key]
38
+ pa %[There is already a password stored for #{key}. You need to remove it before creating a new one!], :red
39
+ return false
40
+ else
41
+ @pw_data[key] = password || ask_for_password(%[please enter a password for #{key}], :yellow)
42
+ if @pw_data[key].empty?
43
+ pa %[Cannot add an empty password!], :red
44
+ return false
45
+ else
46
+ write_safe
47
+ pa %[The password for #{key} has been added], :green
48
+ return true
49
+ end
50
+ end
51
+ end
52
+ aliases_for :add, :set, :store, :create, :[]= # using zucker/alias_for
53
+
54
+ # Gets the password entry and copies it to the clipboard. The second parameter is the time in seconds it stays there
55
+ def get(key, seconds = 10)
56
+ if pw_plaintext = @pw_data[key]
57
+ if seconds && seconds.to_i > 0
58
+ original_clipboard_content = Clipboard.paste
59
+ Clipboard.copy pw_plaintext
60
+ pa %[The password for #{key} is now available in your clipboard for #{seconds.to_i} second#{?s if seconds.to_i > 1}], :green
61
+ begin
62
+ sleep seconds.to_i
63
+ rescue Interrupt
64
+ Clipboard.copy original_clipboard_content
65
+ raise
66
+ end
67
+ Clipboard.copy original_clipboard_content
68
+ return true
69
+ else
70
+ Clipboard.copy pw_plaintext
71
+ pa %[The password for #{key} has been copied to your clipboard], :green
72
+ return true
73
+ end
74
+ else
75
+ pa %[No password found for #{key}!], :red
76
+ return false
77
+ end
78
+ end
79
+ aliases_for :get, :entry, :copy, :password, :for, :[]
80
+
81
+ # Adds a password entry with a freshly generated random password
82
+ def generate(
83
+ key,
84
+ seconds = 10,
85
+ length = 64,
86
+ char_pool = (32..126).map(&:chr).join.gsub(/\s/, '')
87
+ )
88
+ char_pool_size = char_pool.size
89
+ new_pw = (1..length.to_i).map{
90
+ char_pool[SecureRandom.random_number(char_pool_size)]
91
+ }.join
92
+
93
+ if add(key, new_pw)
94
+ get(key, seconds)
95
+ end
96
+ end
97
+ alias_for :generate, :gen
98
+
99
+ # Removes a specific password entry
100
+ def remove(key)
101
+ if @pw_data.delete key
102
+ write_safe
103
+ pa %[The password for #{key} has been removed], :green
104
+ return true
105
+ else
106
+ pa %[No password found for #{key}!], :red
107
+ return false
108
+ end
109
+ end
110
+ aliases_for :remove, :rm, :del, :delete
111
+
112
+ # Removes a specific password entry
113
+ def rename(old_key, new_key)
114
+ if !@pw_data[old_key]
115
+ pa %[No password found for #{old_key}!], :red
116
+ return false
117
+ elsif @pw_data[new_key]
118
+ pa %[There is already a password stored for #{new_key}. You need to remove it before naming another one #{new_key}!], :red
119
+ return false
120
+ else
121
+ @pw_data[new_key] = @pw_data.delete(old_key)
122
+ write_safe
123
+ pa %[The password entry #{old_key} has been renamed to #{new_key}], :green
124
+ return true
125
+ end
126
+ end
127
+ aliases_for :rename, :mv, :move
128
+
129
+ # Changes the master password
130
+ def master(password = nil)
131
+ @pw_hash = Encryptor.hash password || ask_for_password(%[please enter a new master password], :yellow, :bold)
132
+ write_safe
133
+ pa %[The master password has been changed], :green
134
+ return true
135
+ end
136
+
137
+ # Prevents accidental displaying, e.g. in irb
138
+ def to_s
139
+ %[#<password safe>]
140
+ end
141
+ alias_for :to_s, :inspect
142
+
143
+ private
144
+
145
+ # Tries to load and decrypt the password safe from the pwfile
146
+ def read_safe
147
+ pwdata_raw = File.read(@pw_file)
148
+ pwdata_encrypted = pwdata_raw.force_encoding("ascii")
149
+ pwdata_dump = Encryptor.decrypt(pwdata_encrypted, @pw_hash)
150
+ pwdata_with_redundancy = Marshal.load(pwdata_dump)
151
+ @pw_data = remove_redundancy(pwdata_with_redundancy)
152
+ pa %[ACCESS GRANTED], :green
153
+ rescue
154
+ fail NoAccess, %[Could not load and decrypt the password safe!]
155
+ end
156
+
157
+ # Tries to encrypt and save the password safe into the pwfile
158
+ def write_safe
159
+ pwdata_with_redundancy = add_redundancy(@pw_data || {})
160
+ pwdata_dump = Marshal.dump(pwdata_with_redundancy)
161
+ pwdata_encrypted = Encryptor.encrypt(pwdata_dump, @pw_hash)
162
+ File.open(@pw_file, 'w'){ |f| f.write(pwdata_encrypted) }
163
+ rescue
164
+ fail NoAccess, %[Could not encrypt and save the password safe!]
165
+ end
166
+
167
+ # Checks if the file is accessible or create a new one
168
+ def access_safe(password = nil)
169
+ if !File.file? @pw_file
170
+ pa %[No password safe detected, creating one at #@pw_file], :blue, :bold
171
+ @pw_hash = Encryptor.hash password || ask_for_password(%[please enter a new master password], :yellow, :bold)
172
+ write_safe
173
+ else
174
+ print %[Access password safe at #@pw_file | ]
175
+ @pw_hash = Encryptor.hash password || ask_for_password(%[master password])
176
+ end
177
+ end
178
+
179
+ # Adds some redundancy (to conceal how much you have stored)
180
+ def add_redundancy(pw_data)
181
+ entries = 8000 + SecureRandom.random_number(4000)
182
+ position = SecureRandom.random_number(entries)
183
+
184
+ ret = entries.times.map{ # or whatever... just create noise ;)
185
+ { SecureRandom.uuid.chars.to_a.shuffle.join => SecureRandom.uuid.chars.to_a.shuffle.join }
186
+ }
187
+ ret[position] = pw_data
188
+ ret << position
189
+
190
+ ret
191
+ end
192
+
193
+ # And remove it
194
+ def remove_redundancy(pw_data)
195
+ position = pw_data[-1]
196
+ pw_data[position]
197
+ end
198
+
199
+ # Prompts the user for a password
200
+ def ask_for_password(prompt = 'new password', *colors)
201
+ print Paint["#{prompt}:".capitalize, *colors] + " "
202
+ system 'stty -echo' if $stdin.tty? # no more terminal output
203
+ pw_plaintext = ($stdin.gets||'').chop # gets without $stdin would mistakenly read_safe from ARGV
204
+ system 'stty echo' if $stdin.tty? # restore terminal output
205
+ puts "\e[999D\e[K\e[1A" if $stdin.tty? # re-use prompt line in terminal
206
+
207
+ pw_plaintext
208
+ end
209
+ end
210
+
211
+ # Command line action in bin/pws
212
+
213
+ # J-_-L
@@ -0,0 +1,40 @@
1
+ require 'openssl'
2
+
3
+ module PWS::Encryptor
4
+ class << self
5
+ CIPHER = 'aes-256-cbc'
6
+
7
+ def decrypt( iv_and_data, pwhash )
8
+ iv, data = iv_and_data[0,16], iv_and_data[16..-1]
9
+ crypt :decrypt, data, pwhash, iv
10
+ end
11
+
12
+ def encrypt( data, pwhash )
13
+ iv = random_iv
14
+ encrypted_data = crypt :encrypt, data, pwhash, iv
15
+ iv + encrypted_data
16
+ end
17
+
18
+ def hash( plaintext )
19
+ OpenSSL::Digest::SHA512.new( plaintext ).digest
20
+ end
21
+
22
+ # you need a random iv for cbc mode. It is prepended to the encrypted text.
23
+ def random_iv
24
+ a = OpenSSL::Cipher.new CIPHER
25
+ a.random_iv
26
+ end
27
+
28
+ private
29
+
30
+ # Encrypts or decrypts the data with the password hash as key
31
+ # NOTE: encryption exceptions do not get caught here!
32
+ def crypt( decrypt_or_encrypt, data, pwhash, iv )
33
+ c = OpenSSL::Cipher.new CIPHER
34
+ c.send decrypt_or_encrypt.to_sym
35
+ c.key = pwhash
36
+ c.iv = iv
37
+ c.update( data ) << c.final
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,3 @@
1
+ class PWS
2
+ VERSION = '0.9.0'
3
+ end
data/pws.gemspec CHANGED
@@ -1,52 +1,33 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
1
  # -*- encoding: utf-8 -*-
2
+ name = 'pws'
5
3
 
4
+ require File.dirname(__FILE__) + "/lib/#{name}/version"
5
+
6
6
  Gem::Specification.new do |s|
7
- s.name = %q{pws}
8
- s.version = "0.1.3"
7
+ s.required_ruby_version = '>= 1.9'
8
+ s.name = name
9
+ s.version = PWS::VERSION
10
+ s.authors = ["Jan Lelis"]
11
+ s.email = "mail@janlelis.de"
12
+ s.homepage = 'https://github.com/janlelis/pws'
13
+ s.summary = "pws is a cli password safe."
14
+ s.description = "pws is a command-line password safe. Please run `pws help` for usage information."
15
+ s.files = Dir.glob(%w[{lib,test}/**/*.rb bin/* [A-Z]*.{txt,rdoc} ext/**/*.{rb,c}]) + %w{Rakefile pws.gemspec}
16
+ s.extra_rdoc_files = ["README", "LICENSE"]
17
+ s.license = 'MIT'
18
+ s.executables = ['pws']
19
+ s.add_dependency 'clipboard', '~> 1.0.0'
20
+ s.add_dependency 'zucker', '>= 12.1'
21
+ s.add_dependency 'paint', '>= 0.8.4'
22
+ s.add_development_dependency 'rake'
23
+ s.add_development_dependency 'cucumber'
24
+ s.add_development_dependency 'aruba'
9
25
 
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["Jan Lelis"]
12
- s.date = %q{2010-11-02}
13
- s.default_executable = %q{pws}
14
- s.email = %q{mail@janlelis.de}
15
- s.executables = ["pws"]
16
- s.extra_rdoc_files = [
17
- "LICENSE",
18
- "README"
19
- ]
20
- s.files = [
21
- ".gitignore",
22
- "LICENSE",
23
- "README",
24
- "Rakefile",
25
- "VERSION",
26
- "bin/pws",
27
- "lib/pws.rb",
28
- "pws.gemspec"
29
- ]
30
- s.homepage = %q{http://rbjl.net/41-tutorial-build-your-own-password-safe-with-ruby}
31
- s.rdoc_options = ["--charset=UTF-8"]
32
- s.require_paths = ["lib"]
33
- s.rubygems_version = %q{1.3.7}
34
- s.summary = %q{pws is a little password safe. See pws help for more information}
35
-
36
- if s.respond_to? :specification_version then
37
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
38
- s.specification_version = 3
39
-
40
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
41
- s.add_runtime_dependency(%q<clipboard>, [">= 0"])
42
- s.add_runtime_dependency(%q<zucker>, [">= 0"])
43
- else
44
- s.add_dependency(%q<clipboard>, [">= 0"])
45
- s.add_dependency(%q<zucker>, [">= 0"])
46
- end
47
- else
48
- s.add_dependency(%q<clipboard>, [">= 0"])
49
- s.add_dependency(%q<zucker>, [">= 0"])
50
- end
26
+ len = s.homepage.size
27
+ s.post_install_message = \
28
+ (" ┌── " + "info ".ljust(len-2,'%') + "─┐\n" +
29
+ " J-_-L │ " + s.homepage + " │\n" +
30
+ " ├── " + "usage ".ljust(len-2,'%') + "─┤\n" +
31
+ " │ " + "pws help".ljust(len,' ') + " │\n" +
32
+ " └─" + '─'*len + "─┘").gsub('%', '─')
51
33
  end
52
-
metadata CHANGED
@@ -1,102 +1,125 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: pws
3
- version: !ruby/object:Gem::Version
4
- hash: 29
5
- prerelease: false
6
- segments:
7
- - 0
8
- - 1
9
- - 3
10
- version: 0.1.3
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ prerelease:
11
6
  platform: ruby
12
- authors:
7
+ authors:
13
8
  - Jan Lelis
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2010-11-02 00:00:00 +01:00
19
- default_executable: pws
20
- dependencies:
21
- - !ruby/object:Gem::Dependency
12
+ date: 2012-01-19 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
22
15
  name: clipboard
23
- prerelease: false
24
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: &17531100 !ruby/object:Gem::Requirement
25
17
  none: false
26
- requirements:
27
- - - ">="
28
- - !ruby/object:Gem::Version
29
- hash: 3
30
- segments:
31
- - 0
32
- version: "0"
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.0
33
22
  type: :runtime
34
- version_requirements: *id001
35
- - !ruby/object:Gem::Dependency
23
+ prerelease: false
24
+ version_requirements: *17531100
25
+ - !ruby/object:Gem::Dependency
36
26
  name: zucker
27
+ requirement: &17530380 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '12.1'
33
+ type: :runtime
37
34
  prerelease: false
38
- requirement: &id002 !ruby/object:Gem::Requirement
35
+ version_requirements: *17530380
36
+ - !ruby/object:Gem::Dependency
37
+ name: paint
38
+ requirement: &17529720 !ruby/object:Gem::Requirement
39
39
  none: false
40
- requirements:
41
- - - ">="
42
- - !ruby/object:Gem::Version
43
- hash: 3
44
- segments:
45
- - 0
46
- version: "0"
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: 0.8.4
47
44
  type: :runtime
48
- version_requirements: *id002
49
- description:
45
+ prerelease: false
46
+ version_requirements: *17529720
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: &17528920 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *17528920
58
+ - !ruby/object:Gem::Dependency
59
+ name: cucumber
60
+ requirement: &17528420 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *17528420
69
+ - !ruby/object:Gem::Dependency
70
+ name: aruba
71
+ requirement: &17527960 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *17527960
80
+ description: pws is a command-line password safe. Please run `pws help` for usage
81
+ information.
50
82
  email: mail@janlelis.de
51
- executables:
83
+ executables:
52
84
  - pws
53
85
  extensions: []
54
-
55
- extra_rdoc_files:
56
- - LICENSE
86
+ extra_rdoc_files:
57
87
  - README
58
- files:
59
- - .gitignore
60
88
  - LICENSE
61
- - README
62
- - Rakefile
63
- - VERSION
64
- - bin/pws
89
+ files:
90
+ - lib/pws/encryptor.rb
91
+ - lib/pws/version.rb
65
92
  - lib/pws.rb
93
+ - bin/pws
94
+ - Rakefile
66
95
  - pws.gemspec
67
- has_rdoc: true
68
- homepage: http://rbjl.net/41-tutorial-build-your-own-password-safe-with-ruby
69
- licenses: []
70
-
71
- post_install_message:
72
- rdoc_options:
73
- - --charset=UTF-8
74
- require_paths:
96
+ - README
97
+ - LICENSE
98
+ homepage: https://github.com/janlelis/pws
99
+ licenses:
100
+ - MIT
101
+ post_install_message: ! " ┌── info ─────────────────────────┐\n J-_-L │ https://github.com/janlelis/pws
102
+ │\n ├── usage ────────────────────────┤\n │ pws help │\n
103
+ \ └─────────────────────────────────┘"
104
+ rdoc_options: []
105
+ require_paths:
75
106
  - lib
76
- required_ruby_version: !ruby/object:Gem::Requirement
107
+ required_ruby_version: !ruby/object:Gem::Requirement
77
108
  none: false
78
- requirements:
79
- - - ">="
80
- - !ruby/object:Gem::Version
81
- hash: 3
82
- segments:
83
- - 0
84
- version: "0"
85
- required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ! '>='
111
+ - !ruby/object:Gem::Version
112
+ version: '1.9'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
114
  none: false
87
- requirements:
88
- - - ">="
89
- - !ruby/object:Gem::Version
90
- hash: 3
91
- segments:
92
- - 0
93
- version: "0"
115
+ requirements:
116
+ - - ! '>='
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
94
119
  requirements: []
95
-
96
120
  rubyforge_project:
97
- rubygems_version: 1.3.7
121
+ rubygems_version: 1.8.11
98
122
  signing_key:
99
123
  specification_version: 3
100
- summary: pws is a little password safe. See pws help for more information
124
+ summary: pws is a cli password safe.
101
125
  test_files: []
102
-
data/.gitignore DELETED
@@ -1,21 +0,0 @@
1
- ## MAC OS
2
- .DS_Store
3
-
4
- ## TEXTMATE
5
- *.tmproj
6
- tmtags
7
-
8
- ## EMACS
9
- *~
10
- \#*
11
- .\#*
12
-
13
- ## VIM
14
- *.swp
15
-
16
- ## PROJECT::GENERAL
17
- coverage
18
- rdoc
19
- pkg
20
-
21
- ## PROJECT::SPECIFIC
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.1.3