kyle 0.0.4 → 0.0.5
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 +4 -4
- data/.gitignore +18 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +47 -0
- data/LICENSE +21 -0
- data/README.md +244 -0
- data/bin/kyle +2 -2
- data/kyle.gemspec +22 -0
- data/lib/constants.rb +15 -0
- data/lib/kyle.rb +73 -232
- data/lib/kyle_commands.rb +159 -0
- data/spec/kyle_commands_spec.rb +33 -0
- data/spec/kyle_spec.rb +67 -0
- data/spec/spec_helper.rb +5 -0
- metadata +43 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3f04555392f57d6f09a00cc5f1e2a5bc5f185efe
|
4
|
+
data.tar.gz: 5369fc8ad09357dd2791e3945c967f7f95290ae0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5d2565eeb14b542d6afbbb0e940734329168172caa165535ac2679716a3d9fb9108b68af11ce49b89b9a92bc15ff38f860f4043895f898875ac0527d141e2799
|
7
|
+
data.tar.gz: af2beb34df3ae60876544336624936850695812c1f5a2b4d97e2ba3db9d46bb0d39ed3033f35c47b8316382936935e9deca50ad7a9031bcfd6f99b97414fab6d
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
kyle (0.0.3)
|
5
|
+
highline (~> 1.6, >= 1.6.20)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
ast (2.0.0)
|
11
|
+
astrolabe (1.3.0)
|
12
|
+
parser (>= 2.2.0.pre.3, < 3.0)
|
13
|
+
diff-lcs (1.2.5)
|
14
|
+
highline (1.6.21)
|
15
|
+
parser (2.2.0.pre.4)
|
16
|
+
ast (>= 1.1, < 3.0)
|
17
|
+
slop (~> 3.4, >= 3.4.5)
|
18
|
+
powerpack (0.0.9)
|
19
|
+
rainbow (2.0.0)
|
20
|
+
rspec (3.0.0)
|
21
|
+
rspec-core (~> 3.0.0)
|
22
|
+
rspec-expectations (~> 3.0.0)
|
23
|
+
rspec-mocks (~> 3.0.0)
|
24
|
+
rspec-core (3.0.3)
|
25
|
+
rspec-support (~> 3.0.0)
|
26
|
+
rspec-expectations (3.0.3)
|
27
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
28
|
+
rspec-support (~> 3.0.0)
|
29
|
+
rspec-mocks (3.0.3)
|
30
|
+
rspec-support (~> 3.0.0)
|
31
|
+
rspec-support (3.0.3)
|
32
|
+
rubocop (0.26.0)
|
33
|
+
astrolabe (~> 1.3)
|
34
|
+
parser (>= 2.2.0.pre.4, < 3.0)
|
35
|
+
powerpack (~> 0.0.6)
|
36
|
+
rainbow (>= 1.99.1, < 3.0)
|
37
|
+
ruby-progressbar (~> 1.4)
|
38
|
+
ruby-progressbar (1.5.1)
|
39
|
+
slop (3.6.0)
|
40
|
+
|
41
|
+
PLATFORMS
|
42
|
+
ruby
|
43
|
+
|
44
|
+
DEPENDENCIES
|
45
|
+
kyle!
|
46
|
+
rspec (~> 3.0.0)
|
47
|
+
rubocop (~> 0.26)
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Harun Esur
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,244 @@
|
|
1
|
+
Kyle
|
2
|
+
====
|
3
|
+
A password manager for paranoids.
|
4
|
+
|
5
|
+
### Overview
|
6
|
+
|
7
|
+
Kyle differs from other password managers, since:
|
8
|
+
|
9
|
+
* It doesn't store any passwords so there is nothing to steal/crack for
|
10
|
+
attackers.
|
11
|
+
* However, you can't store any given password, but must set one generated by
|
12
|
+
Kyle.
|
13
|
+
|
14
|
+
Kyle differs from other password generators, since:
|
15
|
+
|
16
|
+
* Generated passwords are not random, but a brute-force method can take
|
17
|
+
thousands of years to crack one:
|
18
|
+
|
19
|
+
e.g. on the test vectors Bill Gates' password tooks 12.11 seconds on a MacBook
|
20
|
+
Pro Early 2013 with 2,4 GHZ Intel Core i7. So even for a lazy master-key with
|
21
|
+
8 chars includes small-case-letters and numbers, there are
|
22
|
+
`36^8+36^7+36^5+36^4+36^3+36^2+36 = 2901713047668` combinations; with 12.11s
|
23
|
+
per combination, it would take ***1,114,274 years*** to try all combinations.
|
24
|
+
|
25
|
+
* It doesn't use any specific hash or encryption algorithm, using a mixture of
|
26
|
+
several, chosen using the input info and key.
|
27
|
+
|
28
|
+
### Installation
|
29
|
+
|
30
|
+
```bash
|
31
|
+
$ gem install kyle
|
32
|
+
```
|
33
|
+
|
34
|
+
### Usage
|
35
|
+
|
36
|
+
Just type `kyle` on the command line to run, and pick any password depending
|
37
|
+
on your favourite animal.
|
38
|
+
|
39
|
+
```bash
|
40
|
+
$ kyle
|
41
|
+
Hostname:
|
42
|
+
abc.com
|
43
|
+
Account:
|
44
|
+
superuser
|
45
|
+
Port:
|
46
|
+
80
|
47
|
+
Key:
|
48
|
+
|
49
|
+
|
50
|
+
Ape _,o_iMmO5L!ZRlQH
|
51
|
+
Bat EZPBcTf6oo-jzWpM
|
52
|
+
Bear .ZmYlZ4PQpdOfish
|
53
|
+
Whale wb%EOphi7uqySwRZ
|
54
|
+
Crow eTXvLc.4FgTdIEJ%
|
55
|
+
Dog .Q,PBaMeFRO8nG-a
|
56
|
+
Cat ,lHFEMVXo%SjTlsm
|
57
|
+
Wasp e0CyUAHvs9-ljGFr
|
58
|
+
Fox 2%yxWtBZz-cOVW@b
|
59
|
+
Gull avuR86nGjG6DNkkX
|
60
|
+
Jackal +zhRwHPWCHknxlZp
|
61
|
+
Lion xMxPwb0E+5vQ_q4x
|
62
|
+
Panda qj7GQqJP7EKjU*gG
|
63
|
+
Rat kvniGIszq758@Sie
|
64
|
+
Shark 1aF3.iiV,e*OTGpT
|
65
|
+
Spider *nrvUtila0wnmb22
|
66
|
+
Turtle wYQerXRYffJJGvxZ
|
67
|
+
Wolf MD!VTDkikYxZvzM!
|
68
|
+
Zebra asVw!Q/5!QvqxiRf
|
69
|
+
```
|
70
|
+
|
71
|
+
You can also specify the hostname, account, port, and animal when typing the
|
72
|
+
command:
|
73
|
+
|
74
|
+
```bash
|
75
|
+
$ kyle abc.com superuser 80 jackal
|
76
|
+
Key:
|
77
|
+
|
78
|
+
|
79
|
+
+zhRwHPWCHknxlZp
|
80
|
+
```
|
81
|
+
|
82
|
+
Arguments must in this order, but any missing ones will be prompted for.
|
83
|
+
|
84
|
+
Adding the `-c` flag will prompt for the key twice, so you can be sure you
|
85
|
+
didn't make a typo.
|
86
|
+
|
87
|
+
Adding the `-r` flag saves the hostname/account/port combination in `~/.kyle`.
|
88
|
+
|
89
|
+
Adding `-a ` flag lets you choose from one of savedhost/account/port records saved with -r;
|
90
|
+
|
91
|
+
|
92
|
+
#### Batch usage
|
93
|
+
|
94
|
+
```bash
|
95
|
+
$ kyle -b path/to/file.kyle animal
|
96
|
+
Key:
|
97
|
+
|
98
|
+
|
99
|
+
hostname:account:port (animal) = password
|
100
|
+
hostname:account:port (animal) = password
|
101
|
+
```
|
102
|
+
|
103
|
+
Where `file.kyle` contains triples of hostname, account, port separated by
|
104
|
+
semi-colons (`;`), one per line. E.g.:
|
105
|
+
|
106
|
+
```
|
107
|
+
facebook.com;zuckerberg;80
|
108
|
+
amazon.com;bezos;443
|
109
|
+
```
|
110
|
+
|
111
|
+
### Changelog
|
112
|
+
|
113
|
+
#### 0.0.5
|
114
|
+
|
115
|
+
* Merge version with Isaac Seymour's efforts to unify multiple development lines that includes;
|
116
|
+
|
117
|
+
* Refactored code to have proper Rspec tests, and respect Rubocop conventions
|
118
|
+
|
119
|
+
* Added ability to specify hostname, account, port, and animal as args to the
|
120
|
+
executable
|
121
|
+
|
122
|
+
#### 0.0.4
|
123
|
+
|
124
|
+
* Added -a (Auto) flag
|
125
|
+
|
126
|
+
#### 0.0.2
|
127
|
+
|
128
|
+
* Added -b (BATCH) mode which help you generate bulk passwords;
|
129
|
+
|
130
|
+
* Added -r option to add entered values to <USER_HOME>/.kyle file
|
131
|
+
|
132
|
+
### Algorithm
|
133
|
+
|
134
|
+
#### Overall
|
135
|
+
|
136
|
+
```
|
137
|
+
|
138
|
+
+--------+ +-------+ +----+ +----------+
|
139
|
+
|HOSTNAME| |ACCOUNT| |PORT| |MASTER-KEY|
|
140
|
+
+---+----+ +---+---+ +--+-+ +-----+----+
|
141
|
+
| | | |
|
142
|
+
v v v v
|
143
|
+
+------+ +------+ +------+ +------+
|
144
|
+
|I.HASH| |I.HASH| |I.HASH| |I.HASH|
|
145
|
+
+------+ +------+ +------+ +------+
|
146
|
+
| | | |
|
147
|
+
v v v v
|
148
|
+
+------+ +------+ +------+ +------+
|
149
|
+
|I.HASH| |I.HASH| |I.HASH| |I.HASH|
|
150
|
+
+------+ +------+ +------+ +------+
|
151
|
+
+ + + +
|
152
|
+
| | | |
|
153
|
+
+-----+++----+ +-----+++----+
|
154
|
+
| |
|
155
|
+
v v
|
156
|
+
+------+ +------+
|
157
|
+
|I.ENC.| |I.ENC.|
|
158
|
+
+------+ +------+
|
159
|
+
+ +
|
160
|
+
| |
|
161
|
+
+------------+++-------+
|
162
|
+
|
|
163
|
+
v
|
164
|
+
+------+
|
165
|
+
|I.ENC.|
|
166
|
+
+------+ +------------+
|
167
|
+
+ |ANIMAL NAMES|
|
168
|
+
| +------------+
|
169
|
+
ENC | A1..AN |
|
170
|
+
| +------------+
|
171
|
+
v
|
172
|
+
+--(A1..AN)------------------------------+
|
173
|
+
|RES = PBKDF2_HMAC_SHA1(ENC,RES,10000,32)|
|
174
|
+
+----------------------------------------+
|
175
|
+
+
|
176
|
+
v
|
177
|
+
+-----------------------------+
|
178
|
+
|HASH_TO_PASSWORD(SHA512(RES))|
|
179
|
+
+-----------------------------+
|
180
|
+
+ + +
|
181
|
+
| | |
|
182
|
+
(A1..AN)
|
183
|
+
| | |
|
184
|
+
v v v
|
185
|
+
|
186
|
+
MULTIPLE PASSES
|
187
|
+
|
188
|
+
```
|
189
|
+
|
190
|
+
#### Iterative Hash
|
191
|
+
|
192
|
+
```
|
193
|
+
+-------------+
|
194
|
+
Iterative Hash |ALGORITHMS |
|
195
|
+
+-------------+
|
196
|
+
+-------------+ +--> |SHA512 |
|
197
|
+
|Text=(t1..tn)| | +-------------+
|
198
|
+
+------+------+ +--> |SHA384 |
|
199
|
+
| | +-------------+
|
200
|
+
| +--> |SHA256 |
|
201
|
+
v | +-------------+
|
202
|
+
+--(i=1..n)-----------------------------------------+ +--> |SHA224 |
|
203
|
+
|HASH=ALGORITHM[(ti % ALGORITHMS.SIZE)](Text | HASH)|+-+ +-------------+
|
204
|
+
+---------------------------------------------------+ +--> |SHA1 |
|
205
|
+
+ | +-------------+
|
206
|
+
| +--> |SHA2 |
|
207
|
+
| | +-------------+
|
208
|
+
v +--> |MD5 |
|
209
|
+
| +-------------+
|
210
|
+
HASH +--> |MD4 |
|
211
|
+
| +-------------+
|
212
|
+
+--> |RIPEMD160 |
|
213
|
+
+-------------+
|
214
|
+
```
|
215
|
+
|
216
|
+
#### Iterative Encryption
|
217
|
+
|
218
|
+
```
|
219
|
+
+-------------+
|
220
|
+
Iterative Encryption |ALGORITHMS |
|
221
|
+
+-------------+
|
222
|
+
+-------------+ +--> |DES3 |
|
223
|
+
|TEXT=(t1..tn)| | +-------------+
|
224
|
+
|KEY=(k1..kn) | +--> |DESX |
|
225
|
+
+-------------+ | +-------------+
|
226
|
+
| +--> |DES |
|
227
|
+
v | +-------------+
|
228
|
+
+--(i=1..n)----------------------------------------------+ +--> |CAST |
|
229
|
+
|ENC.=ALGORITHM[(ti % ALGORITHMS.SIZE)]((Text | ENC.),KEY|--------->| +-------------+
|
230
|
+
+-------------------------------------------+------------+ +--> |BLOWFISH |
|
231
|
+
+ | ^ | +-------------+
|
232
|
+
| | | +--> |AES128 |
|
233
|
+
| | | | +-------------+
|
234
|
+
| v | +--> |AES192 |
|
235
|
+
| +--------------+------------+ | +-------------+
|
236
|
+
| |KEY=PBKDF2("kyle",10000,32)| +--> |AES256 |
|
237
|
+
| | IV=SHA512(KEY) | | +-------------+
|
238
|
+
| +---------------------------+ +--> |RC4 |
|
239
|
+
| +-------------+
|
240
|
+
v
|
241
|
+
|
242
|
+
ENCRYPTED
|
243
|
+
```
|
244
|
+
|
data/bin/kyle
CHANGED
data/kyle.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.name = 'kyle'
|
3
|
+
gem.version = '0.0.5'
|
4
|
+
gem.date = '2014-09-17'
|
5
|
+
|
6
|
+
gem.summary = 'Kyle'
|
7
|
+
gem.description = 'A password manager for paranoids.'
|
8
|
+
gem.authors = ['Harun Esur', 'Isaac Seymour']
|
9
|
+
gem.email = 'harun.esur@sceptive.com'
|
10
|
+
gem.homepage = 'http://sceptive.com'
|
11
|
+
gem.license = 'MIT'
|
12
|
+
gem.requirements << 'Ruby should be compiled with openssl support.'
|
13
|
+
|
14
|
+
gem.executables << 'kyle'
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split("\n")
|
17
|
+
|
18
|
+
gem.add_runtime_dependency 'highline', '~> 1.6', '>= 1.6.20'
|
19
|
+
|
20
|
+
gem.add_development_dependency 'rubocop', '~> 0.26'
|
21
|
+
gem.add_development_dependency 'rspec', '~> 3.0.0'
|
22
|
+
end
|
data/lib/constants.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Algorithms, password characters, animals
|
2
|
+
module Constants
|
3
|
+
ENC_ALGS = %w(des3 desx des cast bf aes128 aes192 aes256 rc4).freeze
|
4
|
+
HASH_ALGS = %w(sha512 sha384 sha256 sha224 sha1 sha md5 md4 ripemd160).freeze
|
5
|
+
|
6
|
+
LETTERS = %w(q w e r t y u i o p a s d f g h j k l z x c v b n m).freeze
|
7
|
+
BIG_LETTERS = %w(Q W E R T Y U I O P A S D F G H J K L Z X C V B N M).freeze
|
8
|
+
NUMBERS = %w(0 1 2 3 4 5 6 7 8 9).freeze
|
9
|
+
SPECIAL_CHARS = %w(. @ + - * / % _ ! ,).freeze
|
10
|
+
|
11
|
+
PASSWORD_CHARS = (LETTERS + BIG_LETTERS + NUMBERS + SPECIAL_CHARS).freeze
|
12
|
+
|
13
|
+
ANIMALS = %w(Ape Bat Bear Whale Crow Dog Cat Wasp Fox Gull Jackal Lion Panda
|
14
|
+
Rat Shark Spider Turtle Wolf Zebra).freeze
|
15
|
+
end
|
data/lib/kyle.rb
CHANGED
@@ -1,277 +1,118 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
require 'openssl'
|
3
3
|
require 'highline/import'
|
4
|
+
require 'constants'
|
4
5
|
|
6
|
+
# Password generation from 4 inputs
|
7
|
+
class Kyle
|
8
|
+
attr_accessor :hostname, :account, :port, :key
|
5
9
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
10
|
+
def initialize(hostname, account, port, key)
|
11
|
+
@hostname = hostname
|
12
|
+
@account = account
|
13
|
+
@port = port
|
14
|
+
@key = key
|
15
|
+
end
|
11
16
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
]
|
17
|
+
def passwords
|
18
|
+
@passwords ||= generate
|
19
|
+
end
|
16
20
|
|
17
|
-
|
18
|
-
|
19
|
-
"a","s","d","f","g","h","j","k","l","z",
|
20
|
-
"x","c","v","b","n","m"
|
21
|
-
]
|
21
|
+
def generate
|
22
|
+
passwords = {}
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
"A","S","D","F","G","H","J","K","L","Z",
|
26
|
-
"X","C","V","B","N","M"
|
27
|
-
]
|
24
|
+
salt = encrypted_inputs
|
25
|
+
cipher = salt
|
28
26
|
|
29
|
-
|
30
|
-
|
31
|
-
]
|
27
|
+
Constants::ANIMALS.each do |a|
|
28
|
+
cipher = OpenSSL::PKCS5.pbkdf2_hmac_sha1(cipher, salt, 10_000, 32)
|
32
29
|
|
33
|
-
|
34
|
-
|
35
|
-
]
|
30
|
+
passwords[a] = Kyle.hash_to_password(cipher)
|
31
|
+
end
|
36
32
|
|
37
|
-
|
33
|
+
passwords
|
34
|
+
end
|
38
35
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
$schars.each { |c| $passctable << c }
|
36
|
+
def hashed_hostname
|
37
|
+
Kyle.hash_twice(hostname)
|
38
|
+
end
|
43
39
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
"Turtle", "Wolf", "Zebra"
|
48
|
-
]
|
40
|
+
def hashed_account
|
41
|
+
Kyle.hash_twice(account)
|
42
|
+
end
|
49
43
|
|
50
|
-
|
44
|
+
def hashed_port
|
45
|
+
Kyle.hash_twice(port)
|
46
|
+
end
|
51
47
|
|
52
|
-
def
|
53
|
-
|
48
|
+
def hashed_key
|
49
|
+
Kyle.hash_twice(key)
|
54
50
|
end
|
55
51
|
|
52
|
+
def encrypted_inputs
|
53
|
+
Kyle.iterative_encrypt(
|
54
|
+
Kyle.iterative_encrypt(hashed_hostname, hashed_account),
|
55
|
+
Kyle.iterative_encrypt(hashed_port, hashed_key))
|
56
|
+
end
|
56
57
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
58
|
+
#################
|
59
|
+
# Class methods #
|
60
|
+
#################
|
61
|
+
def self.encrypt(alg, val, key)
|
62
|
+
cipher = OpenSSL::Cipher::Cipher.new(alg)
|
63
|
+
cipher.key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(key, 'kyle', 10_000, 32)
|
64
|
+
cipher.iv = sha512ize(key)
|
65
|
+
cipher.encrypt
|
66
|
+
return cipher.update(val) + cipher.final
|
67
|
+
rescue OpenSSL::Cipher::CipherError => e
|
68
|
+
puts "Error: #{e.message}"
|
68
69
|
end
|
69
70
|
|
70
|
-
def self.
|
71
|
+
def self.iterative_encrypt(val, key)
|
71
72
|
ret = val
|
73
|
+
|
72
74
|
val.each_byte do |c|
|
73
|
-
|
75
|
+
alg = Constants::ENC_ALGS[c % Constants::ENC_ALGS.length]
|
76
|
+
ret = encrypt(alg, ret, key)
|
74
77
|
end
|
75
|
-
|
76
|
-
|
78
|
+
|
79
|
+
ret
|
77
80
|
end
|
78
81
|
|
79
|
-
def self.
|
80
|
-
|
81
|
-
OpenSSL::Digest.digest(alg,val)
|
82
|
+
def self.hash(alg, val)
|
83
|
+
OpenSSL::Digest.digest(alg, val)
|
82
84
|
end
|
83
85
|
|
84
86
|
def self.iterative_hash(val)
|
85
87
|
ret = val
|
88
|
+
|
86
89
|
val.each_byte do |c|
|
87
|
-
|
90
|
+
alg = Constants::HASH_ALGS[c % Constants::HASH_ALGS.length]
|
91
|
+
ret = hash(alg, ret)
|
88
92
|
end
|
89
|
-
|
93
|
+
|
94
|
+
ret
|
90
95
|
end
|
91
96
|
|
92
97
|
def self.sha512ize(val)
|
93
|
-
|
98
|
+
hash('sha512', val)
|
94
99
|
end
|
95
100
|
|
96
|
-
def self.
|
101
|
+
def self.hash_twice(val)
|
97
102
|
iterative_hash(iterative_hash(val))
|
98
103
|
end
|
99
104
|
|
100
|
-
def self.
|
101
|
-
ret =
|
102
|
-
|
103
|
-
# to be sure it is long enough
|
104
|
-
h = sha512ize(val)
|
105
|
-
|
106
|
-
h.each_byte do |c|
|
107
|
-
ret += $passctable[c % ($passctable.size)]
|
108
|
-
end
|
109
|
-
|
110
|
-
return ret[0..15]
|
111
|
-
end
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
def self.generate(hostname,account,port,key)
|
116
|
-
|
117
|
-
ret = Array.new
|
118
|
-
|
119
|
-
harr = [ di_hash(hostname), di_hash(account), di_hash(port), di_hash(key) ]
|
120
|
-
|
121
|
-
v1 = iterative_enc(harr[0],harr[1])
|
122
|
-
v2 = iterative_enc(harr[2],harr[3])
|
105
|
+
def self.hash_to_password(val)
|
106
|
+
ret = ''
|
123
107
|
|
124
|
-
|
125
|
-
|
126
|
-
c = v
|
127
|
-
$animalnames.each do |animal|
|
128
|
-
c = OpenSSL::PKCS5.pbkdf2_hmac_sha1(c, v, 10000, 32)
|
129
|
-
|
130
|
-
ret << hash2pass(c)
|
131
|
-
|
132
|
-
end
|
133
|
-
|
134
|
-
return ret
|
135
|
-
end
|
136
|
-
|
137
|
-
$testarr = [
|
138
|
-
[
|
139
|
-
"microsoft.com","bill.gates","666","iKnowWhatYouDidToIBMLastSummer",
|
140
|
-
"WW0nQIY.0Rn6D8d2", "nXzU19faM7*gv7,I", "cgu0q7*DuT65r3rY", "ILZ,5vZLl/wRxf9y",
|
141
|
-
"Od..eF2k6_l3XHxe", "sedWPnJsSb4DEJw-", "JEr_SzZBoofgI7Tb", "xbbg@ebdz3FA.n6S",
|
142
|
-
"5EMS!XZP7WfPLKpO", "LcwDHUu0/ynzdSWE", "5+fDOoY.yrE+ESbj", "VWKqetfVKZtT,FI9",
|
143
|
-
"EDe7XvMZ%8tt2vsT", "vks.HPeDVkklS_qb", "hcVznOxvT5YvxIlU", "iBTr-I42uz7h7XnA",
|
144
|
-
"fLV,g6%@1G7xpQil", "toMFvN@Zd,b*KBC%", "FYbi/6Udx_4mO3D0"
|
145
|
-
],
|
146
|
-
[
|
147
|
-
$passctable.join,$passctable.join,$passctable.join,$passctable.join,
|
148
|
-
"cuoTJm!UuIYCcQxs", "3HrQ3s/j53A+Rssm", "KK+IV7QE9Imd65hi", "M@FH%uduAIvn/0Fg",
|
149
|
-
"C@ffelLPbsh!ps68", "mF%j06cgTaD63v5C", "5pRiaZDk%SY6quet", "d56XIGF8PhHu!TfI",
|
150
|
-
"71UES8Six9G48hsc", "kmtu%gr1.k%T!tPy", "zE8*qE+uU.PsOkYY", "77Rbxcaiwp4Fwm.M",
|
151
|
-
"qWT0K%tFP8w7_H0S", "2uGyZI.SzAOujmkw", ".QhaGPzV_jnnRj@F", "4FsOh0GfaM4MVUUH",
|
152
|
-
"_3r0DzAdYvEp@dlA", "qcWgE@nt9SgUEsjP", "8F1BJ.OlV5Kj!wmM"
|
153
|
-
]
|
154
|
-
]
|
155
|
-
|
156
|
-
def self.test_it()
|
157
|
-
puts "Testing..."
|
158
|
-
|
159
|
-
failed = false
|
160
|
-
(0..1).each do |idx|
|
161
|
-
vals = generate($testarr[idx][0],$testarr[idx][1],$testarr[idx][2],$testarr[idx][3])
|
162
|
-
vals.each.with_index(0) do |v,i|
|
163
|
-
if ($testarr[idx][i+4] != v)
|
164
|
-
puts "Test failed: #{$testarr[idx][i+4]} <> #{v}"
|
165
|
-
failed = true
|
166
|
-
end
|
167
|
-
end
|
168
|
-
end
|
169
|
-
puts "Finished #{failed ? "and Failed" : "successfully"}"
|
170
|
-
end
|
171
|
-
|
172
|
-
def self.getkey()
|
173
|
-
|
174
|
-
key = "a"
|
175
|
-
key2 = "b"
|
108
|
+
# to be sure it is long enough
|
109
|
+
hashed = sha512ize(val)
|
176
110
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
if (key != key2)
|
182
|
-
puts "Passes do not match!!"
|
183
|
-
end
|
111
|
+
hashed.each_byte do |c|
|
112
|
+
ret += Constants::PASSWORD_CHARS[c % Constants::PASSWORD_CHARS.length]
|
113
|
+
break if ret.length >= 16
|
184
114
|
end
|
185
115
|
|
186
|
-
|
187
|
-
|
188
|
-
end
|
189
|
-
|
190
|
-
def self.run(args)
|
191
|
-
|
192
|
-
puts "Kyle - A password manager for paranoids. ( 0.0.4 )"
|
193
|
-
puts ""
|
194
|
-
|
195
|
-
if (args.size > 0 && args[0] == "test")
|
196
|
-
test_it()
|
197
|
-
elsif (args.size == 3 && args[0].to_s.downcase == "-b" && args[1] != nil && args[2] != nil)
|
198
|
-
|
199
|
-
# Batch MODE
|
200
|
-
# -b record_file favourite_animal_name
|
201
|
-
|
202
|
-
key = getkey()
|
203
|
-
|
204
|
-
text=File.open(args[1]).read
|
205
|
-
text.gsub!(/\r\n?/, "\n")
|
206
|
-
text.each_line do |line|
|
207
|
-
|
208
|
-
hostname, account, port = line.split(';')
|
209
|
-
|
210
|
-
port = port.gsub(/\n/,"")
|
211
|
-
|
212
|
-
vals = generate(hostname,account,port,key)
|
213
|
-
|
214
|
-
$animalnames.each.with_index(0) do |animal,i|
|
215
|
-
|
216
|
-
puts "#{hostname}:#{account}:#{port} = #{vals[i]}" if (animal.downcase == args[2].downcase)
|
217
|
-
|
218
|
-
end
|
219
|
-
|
220
|
-
|
221
|
-
end
|
222
|
-
|
223
|
-
else
|
224
|
-
|
225
|
-
hostname = ""
|
226
|
-
account = ""
|
227
|
-
port = ""
|
228
|
-
|
229
|
-
# Record given parameters to .kyle file at home
|
230
|
-
kyle_r_path = File.join(Dir.home,".kyle")
|
231
|
-
|
232
|
-
if (args.size > 0 && args[0].to_s.downcase == "-a")
|
233
|
-
recs = []
|
234
|
-
iA = 0
|
235
|
-
File.open(kyle_r_path).each do |line|
|
236
|
-
recs << line.rstrip!
|
237
|
-
puts "#{iA} - #{line}"
|
238
|
-
iA+=1
|
239
|
-
end
|
240
|
-
|
241
|
-
puts("")
|
242
|
-
idx = ask("Selection:")
|
243
|
-
|
244
|
-
r = recs[idx.to_i].split(";")
|
245
|
-
|
246
|
-
hostname = r[0]
|
247
|
-
account = r[1]
|
248
|
-
port = r[2]
|
249
|
-
|
250
|
-
else
|
251
|
-
hostname = ask("Hostname:")
|
252
|
-
account = ask("Account:")
|
253
|
-
port = ask("Port:")
|
254
|
-
end
|
255
|
-
key = getkey()
|
256
|
-
|
257
|
-
if (args.size > 0 && args[0].to_s.downcase == "-r")
|
258
|
-
|
259
|
-
|
260
|
-
line_to_add = "#{hostname};#{account};#{port}"
|
261
|
-
|
262
|
-
File.open(kyle_r_path, 'a') do |file|
|
263
|
-
file.puts line_to_add
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
puts "Calculating..."
|
268
|
-
vals = generate(hostname,account,port,key)
|
269
|
-
|
270
|
-
$animalnames.each.with_index(0) do |animal,i|
|
271
|
-
|
272
|
-
puts "#{animal}\t#{vals[i]}"
|
273
|
-
|
274
|
-
end
|
275
|
-
end
|
116
|
+
ret[0..15]
|
276
117
|
end
|
277
118
|
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'kyle'
|
2
|
+
|
3
|
+
# Command line access to Kyle
|
4
|
+
class KyleCommands
|
5
|
+
attr_accessor :args
|
6
|
+
|
7
|
+
def initialize(args = [])
|
8
|
+
@args = args
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
puts 'Kyle - A password manager for paranoids. ( 0.0.5 )'
|
13
|
+
puts ''
|
14
|
+
|
15
|
+
batch_generate if batch?
|
16
|
+
single_generate_choose if single_by_choose?
|
17
|
+
single_generate if !batch? && !single_by_choose?
|
18
|
+
end
|
19
|
+
|
20
|
+
def ask_for_key
|
21
|
+
return ask('Key:') { |q| q.echo = false } unless check?
|
22
|
+
|
23
|
+
key = 'a'
|
24
|
+
key_check = 'b'
|
25
|
+
|
26
|
+
while key != key_check
|
27
|
+
key = ask('Key:') { |q| q.echo = false }
|
28
|
+
key_check = ask('Key (again):') { |q| q.echo = false }
|
29
|
+
|
30
|
+
puts 'Keys do not match!' unless key == key_check
|
31
|
+
end
|
32
|
+
|
33
|
+
key
|
34
|
+
end
|
35
|
+
|
36
|
+
def batch_generate
|
37
|
+
text = File.open(file).read
|
38
|
+
|
39
|
+
text.gsub!(/\r\n?/, "\n")
|
40
|
+
|
41
|
+
text.each_line do |line|
|
42
|
+
hostname, account, port = line.split(';')
|
43
|
+
|
44
|
+
port.gsub!(/\n/, '')
|
45
|
+
|
46
|
+
passwords = make_passwords(hostname, account, port, key)
|
47
|
+
|
48
|
+
passwords.select { |a, _| animals.include? a }.each do |a, p|
|
49
|
+
puts "#{hostname}:#{account}:#{port} (#{a}) = #{p}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def kyle_r_path
|
55
|
+
File.join(Dir.home, '.kyle')
|
56
|
+
end
|
57
|
+
|
58
|
+
def record
|
59
|
+
line_to_add = "#{hostname};#{account};#{port}"
|
60
|
+
|
61
|
+
File.open(kyle_r_path, 'a') do |file|
|
62
|
+
file.puts line_to_add
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def saved_records
|
67
|
+
recs = []
|
68
|
+
i_a = 0
|
69
|
+
File.open(kyle_r_path).each do |line|
|
70
|
+
recs << line.rstrip!
|
71
|
+
puts "#{i_a} - #{line}"
|
72
|
+
i_a += 1
|
73
|
+
end
|
74
|
+
|
75
|
+
recs
|
76
|
+
end
|
77
|
+
|
78
|
+
def single_generate_choose
|
79
|
+
recs = saved_records
|
80
|
+
|
81
|
+
puts('')
|
82
|
+
idx = ask('Selection:')
|
83
|
+
|
84
|
+
r = recs[idx.to_i].split(';')
|
85
|
+
|
86
|
+
@hostname = r[0]
|
87
|
+
@account = r[1]
|
88
|
+
@port = r[2]
|
89
|
+
|
90
|
+
single_generate
|
91
|
+
end
|
92
|
+
|
93
|
+
def single_generate
|
94
|
+
record if record?
|
95
|
+
|
96
|
+
passwords = make_passwords(hostname, account, port, key)
|
97
|
+
|
98
|
+
if animals.length > 1
|
99
|
+
Constants::ANIMALS.each { |a| puts "#{a}\t#{passwords[a]}" }
|
100
|
+
else
|
101
|
+
puts passwords[animals[0]]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def record?
|
106
|
+
args.include? '-r'
|
107
|
+
end
|
108
|
+
|
109
|
+
def check?
|
110
|
+
(args.include? '-c') || (record?)
|
111
|
+
end
|
112
|
+
|
113
|
+
def batch?
|
114
|
+
args.include? '-b'
|
115
|
+
end
|
116
|
+
|
117
|
+
def single_by_choose?
|
118
|
+
args.include? '-a'
|
119
|
+
end
|
120
|
+
|
121
|
+
def main_args
|
122
|
+
args.reject { |arg| arg[0] == '-' }
|
123
|
+
end
|
124
|
+
|
125
|
+
def hostname
|
126
|
+
@hostname ||= main_args[0] || ask('Hostname:')
|
127
|
+
end
|
128
|
+
|
129
|
+
def account
|
130
|
+
@account ||= main_args[1] || ask('Account:')
|
131
|
+
end
|
132
|
+
|
133
|
+
def port
|
134
|
+
@port ||= main_args[2] || ask('Port:')
|
135
|
+
end
|
136
|
+
|
137
|
+
def animals
|
138
|
+
animal = batch? ? main_args[1] : main_args[3]
|
139
|
+
|
140
|
+
return Constants::ANIMALS if animal.nil?
|
141
|
+
Constants::ANIMALS.select { |a| a.downcase == animal.downcase }
|
142
|
+
end
|
143
|
+
|
144
|
+
def make_passwords(hostname, account, port, key)
|
145
|
+
puts 'Generating...'
|
146
|
+
puts ''
|
147
|
+
|
148
|
+
Kyle.new(hostname, account, port, key).passwords
|
149
|
+
end
|
150
|
+
|
151
|
+
def file
|
152
|
+
fail 'File only for batch operations' unless batch?
|
153
|
+
main_args[0]
|
154
|
+
end
|
155
|
+
|
156
|
+
def key
|
157
|
+
@key ||= ask_for_key
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe KyleCommands do
|
4
|
+
subject(:cmds) { described_class.new(args) }
|
5
|
+
|
6
|
+
context 'in batch mode' do
|
7
|
+
let(:args) { %w(-b filename.txt whale) }
|
8
|
+
|
9
|
+
it 'is in batch mode' do
|
10
|
+
expect(cmds.batch?).to eq(true)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'runs #batch_generate' do
|
14
|
+
expect(cmds).to receive(:batch_generate)
|
15
|
+
cmds.run
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'in single mode' do
|
20
|
+
let(:args) { %w(microsoft.com evilcommander 8080 panda) }
|
21
|
+
|
22
|
+
it 'is in single mode' do
|
23
|
+
expect(cmds.batch?).to eq(false)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'has details set' do
|
27
|
+
expect(cmds.hostname).to eq('microsoft.com')
|
28
|
+
expect(cmds.account).to eq('evilcommander')
|
29
|
+
expect(cmds.port).to eq('8080')
|
30
|
+
expect(cmds.animals).to eq(['Panda'])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/spec/kyle_spec.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Kyle do
|
4
|
+
let!(:passwords) { Kyle.new(hostname, account, port, key).passwords }
|
5
|
+
|
6
|
+
context "Bill Gates' passwords" do
|
7
|
+
let(:hostname) { 'microsoft.com' }
|
8
|
+
let(:account) { 'bill.gates' }
|
9
|
+
let(:port) { '666' }
|
10
|
+
let(:key) { 'iKnowWhatYouDidToIBMLastSummer' }
|
11
|
+
|
12
|
+
it 'has the correct passwords' do
|
13
|
+
expect(passwords).to eq(
|
14
|
+
'Ape' => 'WW0nQIY.0Rn6D8d2',
|
15
|
+
'Bat' => 'nXzU19faM7*gv7,I',
|
16
|
+
'Bear' => 'cgu0q7*DuT65r3rY',
|
17
|
+
'Whale' => 'ILZ,5vZLl/wRxf9y',
|
18
|
+
'Crow' => 'Od..eF2k6_l3XHxe',
|
19
|
+
'Dog' => 'sedWPnJsSb4DEJw-',
|
20
|
+
'Cat' => 'JEr_SzZBoofgI7Tb',
|
21
|
+
'Wasp' => 'xbbg@ebdz3FA.n6S',
|
22
|
+
'Fox' => '5EMS!XZP7WfPLKpO',
|
23
|
+
'Gull' => 'LcwDHUu0/ynzdSWE',
|
24
|
+
'Jackal' => '5+fDOoY.yrE+ESbj',
|
25
|
+
'Lion' => 'VWKqetfVKZtT,FI9',
|
26
|
+
'Panda' => 'EDe7XvMZ%8tt2vsT',
|
27
|
+
'Rat' => 'vks.HPeDVkklS_qb',
|
28
|
+
'Shark' => 'hcVznOxvT5YvxIlU',
|
29
|
+
'Spider' => 'iBTr-I42uz7h7XnA',
|
30
|
+
'Turtle' => 'fLV,g6%@1G7xpQil',
|
31
|
+
'Wolf' => 'toMFvN@Zd,b*KBC%',
|
32
|
+
'Zebra' => 'FYbi/6Udx_4mO3D0'
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'with crazy inputs' do
|
38
|
+
let(:hostname) { Constants::PASSWORD_CHARS.join }
|
39
|
+
let(:account) { Constants::PASSWORD_CHARS.join }
|
40
|
+
let(:port) { Constants::PASSWORD_CHARS.join }
|
41
|
+
let(:key) { Constants::PASSWORD_CHARS.join }
|
42
|
+
|
43
|
+
it 'has correct passwords' do
|
44
|
+
expect(passwords).to eq(
|
45
|
+
'Ape' => 'cuoTJm!UuIYCcQxs',
|
46
|
+
'Bat' => '3HrQ3s/j53A+Rssm',
|
47
|
+
'Bear' => 'KK+IV7QE9Imd65hi',
|
48
|
+
'Whale' => 'M@FH%uduAIvn/0Fg',
|
49
|
+
'Crow' => 'C@ffelLPbsh!ps68',
|
50
|
+
'Dog' => 'mF%j06cgTaD63v5C',
|
51
|
+
'Cat' => '5pRiaZDk%SY6quet',
|
52
|
+
'Wasp' => 'd56XIGF8PhHu!TfI',
|
53
|
+
'Fox' => '71UES8Six9G48hsc',
|
54
|
+
'Gull' => 'kmtu%gr1.k%T!tPy',
|
55
|
+
'Jackal' => 'zE8*qE+uU.PsOkYY',
|
56
|
+
'Lion' => '77Rbxcaiwp4Fwm.M',
|
57
|
+
'Panda' => 'qWT0K%tFP8w7_H0S',
|
58
|
+
'Rat' => '2uGyZI.SzAOujmkw',
|
59
|
+
'Shark' => '.QhaGPzV_jnnRj@F',
|
60
|
+
'Spider' => '4FsOh0GfaM4MVUUH',
|
61
|
+
'Turtle' => '_3r0DzAdYvEp@dlA',
|
62
|
+
'Wolf' => 'qcWgE@nt9SgUEsjP',
|
63
|
+
'Zebra' => '8F1BJ.OlV5Kj!wmM'
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kyle
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Harun Esur
|
8
|
+
- Isaac Seymour
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2014-
|
12
|
+
date: 2014-09-17 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: highline
|
@@ -30,6 +31,34 @@ dependencies:
|
|
30
31
|
- - ">="
|
31
32
|
- !ruby/object:Gem::Version
|
32
33
|
version: 1.6.20
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rubocop
|
36
|
+
requirement: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.26'
|
41
|
+
type: :development
|
42
|
+
prerelease: false
|
43
|
+
version_requirements: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.26'
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rspec
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.0.0
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.0.0
|
33
62
|
description: A password manager for paranoids.
|
34
63
|
email: harun.esur@sceptive.com
|
35
64
|
executables:
|
@@ -37,8 +66,19 @@ executables:
|
|
37
66
|
extensions: []
|
38
67
|
extra_rdoc_files: []
|
39
68
|
files:
|
40
|
-
-
|
69
|
+
- ".gitignore"
|
70
|
+
- Gemfile
|
71
|
+
- Gemfile.lock
|
72
|
+
- LICENSE
|
73
|
+
- README.md
|
41
74
|
- bin/kyle
|
75
|
+
- kyle.gemspec
|
76
|
+
- lib/constants.rb
|
77
|
+
- lib/kyle.rb
|
78
|
+
- lib/kyle_commands.rb
|
79
|
+
- spec/kyle_commands_spec.rb
|
80
|
+
- spec/kyle_spec.rb
|
81
|
+
- spec/spec_helper.rb
|
42
82
|
homepage: http://sceptive.com
|
43
83
|
licenses:
|
44
84
|
- MIT
|