strongboxio 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/strongboxio.rb +207 -0
- data/test/2fe22f4d-bd41-476a-8159-c6e0b6487f7d.sbox +24 -0
- data/test/test_helper.rb +3 -0
- data/test/test_strongboxio.rb +28 -0
- metadata +53 -0
data/lib/strongboxio.rb
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'openssl' # for decryption
|
2
|
+
require 'zlib' # for decompression
|
3
|
+
require 'base64' # for decoding
|
4
|
+
require 'rubygems' if defined?RUBY_VERSION && RUBY_VERSION =~ /^1.8/ # for requiring gem dependency in Ruby 1.8
|
5
|
+
require 'nokogiri' # for xml parsing
|
6
|
+
|
7
|
+
class Strongboxio
|
8
|
+
|
9
|
+
VERSION = '0.1.0'
|
10
|
+
|
11
|
+
STRONGBOX_VERSION = 3
|
12
|
+
VERSION_LENGTH = 1
|
13
|
+
SALT_LENGTH = 64
|
14
|
+
IV_LENGTH = 16
|
15
|
+
KEY_LENGTH = 32
|
16
|
+
RANDOM_VALUE_LENGTH = 64
|
17
|
+
CIPHER = 'AES-256-CBC'
|
18
|
+
PAYLOAD_SCHEMA_VERSION = '2.4'
|
19
|
+
UNIX_EPOCH_IN_100NS_INTERVALS = 621355968000000000 # .NET time format: number of 100-nanosecond intervals since .NET epoch: January 1, 0001 at 00:00:00.000 (midnight)
|
20
|
+
|
21
|
+
def self.resembles_base64?(string)
|
22
|
+
string.length % 4 == 0 && string =~ /^[A-Za-z0-9+\/=]+\Z/
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.decrypt(sbox_filename, password)
|
26
|
+
# open the xml file
|
27
|
+
f = File.open(sbox_filename)
|
28
|
+
data = Nokogiri::XML(f)
|
29
|
+
f.close
|
30
|
+
|
31
|
+
# extract the Data node
|
32
|
+
data = data.xpath('//Data').text
|
33
|
+
base64_error_msg = 'expected Base64 encoded byte string from the StrongBox.Payload.Data element of the xml of a Strongbox file'
|
34
|
+
raise "#{base64_error_msg}, but got nothing" if data.length == 0
|
35
|
+
raise "#{base64_error_msg}, but it does not resemble Base64" unless self.resembles_base64?(data)
|
36
|
+
|
37
|
+
data = Base64.decode64(data)
|
38
|
+
|
39
|
+
#version = data.getbyte(0) # ruby 1.9
|
40
|
+
version = data.bytes.to_a[0] # ruby 1.8 friendly
|
41
|
+
raise "expected version number #{STRONGBOX_VERSION}, but got #{version}" unless version == STRONGBOX_VERSION
|
42
|
+
|
43
|
+
salt = data.slice(1, SALT_LENGTH)
|
44
|
+
raise "expected salt length #{SALT_LENGTH}, but got #{salt.length}" unless salt.length == SALT_LENGTH
|
45
|
+
|
46
|
+
iv = data.bytes.to_a.slice((1+64), IV_LENGTH).pack('C*')
|
47
|
+
raise "expected iv length #{IV_LENGTH}, but got #{iv.length}" unless iv.length == IV_LENGTH
|
48
|
+
|
49
|
+
key = Digest::SHA256.digest(salt + password)
|
50
|
+
raise "expected key length #{KEY_LENGTH}, but got #{key.length}" unless key.length == KEY_LENGTH
|
51
|
+
|
52
|
+
# prepare for decryption
|
53
|
+
d = OpenSSL::Cipher.new(CIPHER)
|
54
|
+
d.decrypt
|
55
|
+
d.key = key
|
56
|
+
d.iv = iv
|
57
|
+
|
58
|
+
# decrypt the portion beyond the header
|
59
|
+
begin
|
60
|
+
data = '' << d.update(data.slice((VERSION_LENGTH+SALT_LENGTH+IV_LENGTH)..-1)) << d.final
|
61
|
+
rescue => e
|
62
|
+
raise "Error decrypting. You probably entered the password incorrectly. Specific error: #{e}"
|
63
|
+
end
|
64
|
+
|
65
|
+
# decompress the portion beyond the random value
|
66
|
+
#z = Zlib::Inflate.new
|
67
|
+
#z = Zlib::Inflate.new(-Zlib::BEST_COMPRESSION) # works for Strongbox
|
68
|
+
z = Zlib::Inflate.new(-Zlib::MAX_WBITS) # works for Strongbox!
|
69
|
+
#z = Zlib::Inflate.new(Zlib::MAX_WBITS) # works for roundtrip
|
70
|
+
data = z.inflate(data.slice(RANDOM_VALUE_LENGTH..-1))
|
71
|
+
z.finish
|
72
|
+
z.close
|
73
|
+
|
74
|
+
data
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.render(decrypted_sbox, continue_despite_unexpected_payload_schema_version=false)
|
78
|
+
data = Nokogiri::XML(decrypted_sbox)
|
79
|
+
|
80
|
+
payload_schema_version = data.xpath('//Payload').xpath('SchemaVersion').text
|
81
|
+
unless payload_schema_version == PAYLOAD_SCHEMA_VERSION
|
82
|
+
raise "expected schema version #{PAYLOAD_SCHEMA_VERSION}, but got #{payload_schema_version}" unless continue_despite_unexpected_payload_schema_version
|
83
|
+
end
|
84
|
+
|
85
|
+
mt = data.xpath('//Payload').xpath('PayloadInfo').xpath('MT').text
|
86
|
+
puts mt
|
87
|
+
|
88
|
+
data.xpath('//PayloadData').each { |payload_data|
|
89
|
+
payload_data.xpath('//SBE').each { |entity|
|
90
|
+
puts
|
91
|
+
name = entity.xpath('N').text
|
92
|
+
puts "#{name}" if name.length > 0
|
93
|
+
description = entity.xpath('D').text
|
94
|
+
puts "#{description}" if description.length > 0
|
95
|
+
tags = entity.xpath('T').text
|
96
|
+
puts "#{tags}" if tags.length > 0
|
97
|
+
ce = entity.xpath('CE')
|
98
|
+
ce.xpath('TFE').each { |tfe|
|
99
|
+
name = tfe.xpath('N').text
|
100
|
+
puts "#{name}:" if name.length > 0
|
101
|
+
content = tfe.xpath('C').text
|
102
|
+
puts "#{content}" if content.length > 0
|
103
|
+
}
|
104
|
+
}
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
def assemble(decrypted_sbox, continue_despite_unexpected_payload_schema_version=false)
|
109
|
+
data = Nokogiri::XML(decrypted_sbox)
|
110
|
+
|
111
|
+
payload_schema_version = data.xpath('//Payload').xpath('SchemaVersion').text
|
112
|
+
unless payload_schema_version == PAYLOAD_SCHEMA_VERSION
|
113
|
+
raise "expected schema version #{PAYLOAD_SCHEMA_VERSION}, but got #{payload_schema_version}" unless continue_despite_unexpected_payload_schema_version
|
114
|
+
end
|
115
|
+
|
116
|
+
sbox = {}
|
117
|
+
|
118
|
+
mt = data.xpath('//Payload').xpath('PayloadInfo').xpath('MT').text
|
119
|
+
sbox['MT'] = mt
|
120
|
+
|
121
|
+
data.xpath('//PayloadData').each { |payload_data|
|
122
|
+
|
123
|
+
sbox['PayloadData'] = []
|
124
|
+
|
125
|
+
payload_data.xpath('//SBE').each_with_index { |strongbox_entity, sbe_index|
|
126
|
+
|
127
|
+
sbox['PayloadData'][sbe_index] = {}
|
128
|
+
|
129
|
+
sbe_mt = strongbox_entity.attr('MT') # ModifiedTimestamp.Ticks
|
130
|
+
sbox['PayloadData'][sbe_index]['MT'] = sbe_mt if sbe_mt.length > 0
|
131
|
+
|
132
|
+
sbe_ct = strongbox_entity.attr('CT') # CreatedTimestamp.Ticks
|
133
|
+
sbox['PayloadData'][sbe_index]['CT'] = sbe_ct if sbe_ct.length > 0
|
134
|
+
|
135
|
+
sbe_ac = strongbox_entity.attr('AC') # accessCount
|
136
|
+
sbox['PayloadData'][sbe_index]['AC'] = sbe_ac if sbe_ac.length > 0
|
137
|
+
|
138
|
+
sbe_name = strongbox_entity.xpath('N').text
|
139
|
+
sbox['PayloadData'][sbe_index]['N'] = sbe_name if sbe_name.length > 0
|
140
|
+
|
141
|
+
sbe_description = strongbox_entity.xpath('D').text
|
142
|
+
sbox['PayloadData'][sbe_index]['D'] = sbe_description if sbe_description.length > 0
|
143
|
+
|
144
|
+
sbe_tags = strongbox_entity.xpath('T').text
|
145
|
+
sbox['PayloadData'][sbe_index]['T'] = sbe_tags if sbe_tags.length > 0
|
146
|
+
|
147
|
+
child_entity = strongbox_entity.xpath('CE')
|
148
|
+
if child_entity.length > 0
|
149
|
+
sbox['PayloadData'][sbe_index]['CE'] = []
|
150
|
+
|
151
|
+
child_entity.xpath('TFE').each_with_index { |text_field_entity, ce_index|
|
152
|
+
|
153
|
+
sbox['PayloadData'][sbe_index]['CE'][ce_index] = {}
|
154
|
+
|
155
|
+
tfe_name = text_field_entity.xpath('N').text
|
156
|
+
sbox['PayloadData'][sbe_index]['CE'][ce_index]['N'] = tfe_name if tfe_name.length > 0
|
157
|
+
|
158
|
+
tfe_content = text_field_entity.xpath('C').text
|
159
|
+
sbox['PayloadData'][sbe_index]['CE'][ce_index]['C'] = tfe_content if tfe_content.length > 0
|
160
|
+
}
|
161
|
+
end
|
162
|
+
}
|
163
|
+
}
|
164
|
+
|
165
|
+
sbox
|
166
|
+
end
|
167
|
+
|
168
|
+
def render(verbose=false)
|
169
|
+
puts sbox['MT']
|
170
|
+
|
171
|
+
sbox['PayloadData'].each { |payload_data|
|
172
|
+
puts
|
173
|
+
puts payload_data['N'] unless payload_data['N'].nil?
|
174
|
+
puts payload_data['D'] unless payload_data['D'].nil?
|
175
|
+
puts payload_data['T'] unless payload_data['T'].nil?
|
176
|
+
payload_data['CE'].each { |strongbox_entity|
|
177
|
+
puts strongbox_entity['N'] + ': ' unless strongbox_entity['N'].nil?
|
178
|
+
puts strongbox_entity['C'] unless strongbox_entity['C'].nil?
|
179
|
+
}
|
180
|
+
puts "Access Count: #{payload_data['AC']}" if !payload_data['AC'].nil? && verbose
|
181
|
+
puts "#{convert_time_from_dot_net_epoch(payload_data['MT'].to_i)} (modify time)" if !payload_data['MT'].nil? && verbose
|
182
|
+
puts "#{convert_time_from_dot_net_epoch(payload_data['CT'].to_i)} (create time)" if !payload_data['CT'].nil? && verbose
|
183
|
+
}
|
184
|
+
end
|
185
|
+
|
186
|
+
#attr_accessor :decrypted_sbox
|
187
|
+
attr_accessor :sbox
|
188
|
+
|
189
|
+
# create an instance of Strongbox
|
190
|
+
def initialize(decrypted_sbox, continue_despite_unexpected_payload_schema_version=false)
|
191
|
+
super()
|
192
|
+
#self.decrypted_sbox = decrypted_sbox
|
193
|
+
self.sbox = assemble(decrypted_sbox, continue_despite_unexpected_payload_schema_version)
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
|
198
|
+
def convert_time_from_dot_net_epoch(t)
|
199
|
+
Time.at((t-UNIX_EPOCH_IN_100NS_INTERVALS)*1e-7).utc.getlocal
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
#class String
|
204
|
+
# def resembles_base64?
|
205
|
+
# self.length % 4 == 0 && self =~ /^[A-Za-z0-9+\/=]+\Z/
|
206
|
+
# end
|
207
|
+
#end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
2
|
+
<StrongBox>
|
3
|
+
<SchemaVersion>2.1</SchemaVersion>
|
4
|
+
<Metadata>
|
5
|
+
<SchemaVersion>2.2</SchemaVersion>
|
6
|
+
<SBOXID>2fe22f4d-bd41-476a-8159-c6e0b6487f7d</SBOXID>
|
7
|
+
<SID>1aefeb7f-87fb-4c12-9587-78f2354c3a70</SID>
|
8
|
+
<CreateDate>129985312023925270</CreateDate>
|
9
|
+
<ModifiedDate>129985323759179228</ModifiedDate>
|
10
|
+
<Name>strongbox gem test</Name>
|
11
|
+
<Description>Strongbox Ruby gem test box</Description>
|
12
|
+
<PassphaseHint />
|
13
|
+
<LocalModifiedDate>129985312023925280</LocalModifiedDate>
|
14
|
+
<PayloadIdentity>sQgzfRSQEkCJo25tVi47Q</PayloadIdentity>
|
15
|
+
<PayloadSize>1217</PayloadSize>
|
16
|
+
<PayloadType />
|
17
|
+
<PayloadItemCount>4</PayloadItemCount>
|
18
|
+
<PayloadBaseRevisionTag>129985312024788056-129985312024788056</PayloadBaseRevisionTag>
|
19
|
+
</Metadata>
|
20
|
+
<Payload>
|
21
|
+
<SchemaVersion>2.1</SchemaVersion>
|
22
|
+
<Data>AxHLk57HTQaFzAHatF+lFg++t44Dfwr78cKGHzQB1+eWbKL9HVTZK9T/omI7P9az8gvJA//5CkYGz6HcQS7Sg/U5PTNqU4u2EZhkdaRUK6KooHux/u9HBF27RXy/8shiJbS6FN7EPew/8zzuZRe00WzOcoKELmHvbeTgZG0Te2Mtox+PfZMYEvRPjbA2eI70KwzlzSuoHbmyECKo36x9RTAHTeQTEXMfkcMU6D0ONsbMmNoJkC3TqIX5bcYLQ1FxQpwvB/AbtfyCLPk9Zxhmm0ihEsBfcPqxU9kaVkCBOfOyxFyvueEdFgEs1PxYJ571huJpyprCcQKcarr3yvMIzkn3rJKO6s9F0gR8PqdrBBO3PxKeZO6sJnpEq0WIuSYaY2wFGXr0UvK/Hid3hUHwVEjfbiVVJmXFmRHmEnJD7Q2EctX5Ju20ESYSXy3f3r/W3RTaR8ZwLVPiyroV2EjOZrhI0pMhXLZaOhszVA4alC7djphKRZkh1zg39pnMaPNYACW0rlAnFz5zk74Uez64xiWI352i89jfjj5R16blBlxq8FjJJVSlUxJJNJwnlbG744AXxd1ygI56koztmbwY1VbAOmr4FiX9JtVLq6a6gVKv3sy3eKlxKXd41jhOTNIKnu0Nh36TxXs64Q2BdozKnQvCN9BYelawWPQOp3viQ95LlruiQQEnzzmcGuZw33ny1QfxVFuz/Gd+i50eg++urf/woprI74OtXANWWhN6chSP29Gz8y76/zYZoYAerZxAVPuh+elW9Xw3fjCwHk33/tkOvVcKQb/LCcFZdaawh6gdYfRJTRetQYKIWdKn7QuYhZLKYXMcJ1BBrDnJx0QKCN8kwNggzRlfYtgzz07t1BQXL+5F9m18KQfXbGR4HpI3INwWMFA61mFKRMih0cORjbVwfISJIZfRp/oe1Ut8r4+cZbTwdmPEnUrMiOSQLk0gUk7DknnCfEPKLjOFz+DAwhqSfosNKUkV1AB+GDzI2JfR5P3SMLh77/CLN1Adx/tXH46A2K6D5JXPVGUR9mhMD9hCH6v34dRNkfBSD0EN2VHMAkVAWrDm4UiKf3uO6k51Q7IZ0vcp+QSZdcV98rhAVgpx79S3MPd8+riamxuZFHe/5Ao1p4JW1FajPxbZnh4LhQNdN8vTSuywOaefN7Mph4X3WEJ5cOYcJm5MdYQDx9t1DSdY1l+tuDHW+8LWpqicVN0JWNADPFCyQwoAQMzX1ax3zq8zXofI+MaJOxaxiEu/pJDPce07fmf/W6Xxcw8Uugm4bZE7pxcTRrrhXZ4WYBmHyxRfNtGZyw8K385S8wRW93FPUnwHRDUyDoRN+w9FwAxWPmywM2Z8RyfyN6frQ0j4roB9YD2Yt1YuY6/yoUDiDxsLdaBYEfre+A26VlkC3zUy/M64yPe6J/CO1hBDVkwxJ8hmkiw2QtHROIz80hDvprecVWjzwLNT+ddT+oXf5nEFREZP8IMxzDNdrRcydQjpJHViWvn4fLxhF72KcTLKM0yfokpbsGnC3X63dcHFHG0KTiTcfnBNqqdi9WyhD12Jm62bAfgJVV+GFJHXSHdTGCa1REmqfuKeSIVBCxaCPSNG1M/WbEdAfJNjGj980ZQ=</Data>
|
23
|
+
</Payload>
|
24
|
+
</StrongBox>
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'test_helper'
|
3
|
+
|
4
|
+
class StrongboxioTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
FILENAME = 'test/2fe22f4d-bd41-476a-8159-c6e0b6487f7d.sbox'
|
7
|
+
PASSWORD = 'pWY4ic9q'
|
8
|
+
|
9
|
+
def test_decrypt_from_file
|
10
|
+
expect='<Payload><SchemaVersion>2.4</SchemaVersion><PayloadInfo><SchemaVersion>1.0</SchemaVersion><IP>PyOQP3GmNEA3T5BjBOiPg,DU4Qm4btjyejD1TYGoUjQ,nC9jzQ4Tv3RsrfxHzghe4A,YlVFCZO1FqBT91H0M3XmQA,WIVON6o61vebxLKJhXKQ,sQgzfRSQEkCJo25tVi47Q</IP><MT>2012-11-27T23:29:23.18873Z</MT><SD>xJ2VL0VourNV3a7cOJkinAMkVVEJJahV17MlbE5x8w</SD></PayloadInfo><PayloadData><T /><CE><SBE ID="7ijIF8xabBZjKsEKpUFnag" II="4" MT="634896555753103810" CT="634896555753102130" AC="0"><N>VanCity bank info</N><D>credit card, debit card, online banking</D><T>Credit, Debit, Card, Online, Banking</T><CE><TFE ID="K87JOu1lge4X7SSKE9elDQ" II="4" MT="634896555753103820"><N>credit card number</N><C>1234 5678 9012 3456</C></TFE><TFE ID="PTsGYmJb7f1B8nIquZfAMQ" II="4" MT="634896555753104130"><N>credit card expiration</N><C>11/2012</C></TFE><TFE ID="sfRgujycbbKIad58FkC1vg" II="4" MT="634896555753104380"><N>credit card verification value</N><C>123</C></TFE><TFE ID="bK7Vba0t2oupLiRGjEkt5Q" II="4" MT="634896555753104690"><N>debit card PIN</N><C>1234</C></TFE><TFE ID="2j9hKQJqCvbrVpmfRqJVA" II="4" MT="634896555753104970"><N>online banking password</N><C>qwerty</C></TFE></CE></SBE><SBE ID="L35bmTNw34lQnA70hmEVmg" II="3" MT="634896550747551430" CT="634896550747549900" AC="0"><N>fake Gmail account</N><D>used for Stackoverflow</D><T>Fake Gmail Stackoverflow</T><CE><TFE ID="Zmjyh5cMoj5s1AuH18EJA" II="3" MT="634896550747551440"><N>username</N><C>ima_fake@gmail.com</C></TFE><TFE ID="bOudR7nlwP40bo6V23jw" II="3" MT="634896550747551750"><N>password</N><C>password1</C></TFE><TFE ID="klbjFvsyyDEs956gkduKg" II="3" MT="634896550747552080"><N>gender</N><C>other</C></TFE><TFE ID="ckdoh9oM478UliFoeg" II="3" MT="634896550747552370"><N>backup</N><C>bart_simpson@gmail.com</C></TFE></CE></SBE><SBE ID="SwH6NoXPhg7qXLOUlDQyQ" II="2" MT="634896545666557670" CT="634896545666556630" AC="0"><N>just a name</N><D></D><T /><CE><TFE ID="u9LEZ2H2jQlzqc7a5vD2iA" II="2" MT="634896545666557680"><N /><C /></TFE></CE></SBE><SBE ID="ZoPVBFNFdOlRLT0FLO9BVQ" II="5" MT="634896557631887530" CT="634896545082402010" AC="0"><N>test item name</N><D>test item description</D><T>Test, Item, Tag1, Tag2, Tag3</T><CE><TFE ID="NyRRL7tmBMr0VrDoyzfwg" II="1" MT="634896545082426060"><N>test field name</N><C>test secure text</C></TFE></CE></SBE></CE></PayloadData></Payload>'
|
11
|
+
|
12
|
+
assert_equal expect,
|
13
|
+
Strongboxio.decrypt(FILENAME, PASSWORD)
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_strongboxio_instantiation
|
17
|
+
# #<Strongboxio:0x1011b2e28 @sbox={"PayloadData"=>[{"CE"=>[{"C"=>"1234 5678 9012 3456", "N"=>"credit card number"}, {"C"=>"11/2012", "N"=>"credit card expiration"}, {"C"=>"123", "N"=>"credit card verification value"}, {"C"=>"1234", "N"=>"debit card PIN"}, {"C"=>"qwerty", "N"=>"online banking password"}], "AC"=>"0", "N"=>"VanCity bank info", "D"=>"credit card, debit card, online banking", "CT"=>"634896555753102130", "MT"=>"634896555753103810", "T"=>"Credit, Debit, Card, Online, Banking"}, {"CE"=>[{"C"=>"ima_fake@gmail.com", "N"=>"username"}, {"C"=>"password1", "N"=>"password"}, {"C"=>"other", "N"=>"gender"}, {"C"=>"bart_simpson@gmail.com", "N"=>"backup"}], "AC"=>"0", "N"=>"fake Gmail account", "D"=>"used for Stackoverflow", "CT"=>"634896550747549900", "MT"=>"634896550747551430", "T"=>"Fake Gmail Stackoverflow"}, {"CE"=>[{}], "AC"=>"0", "N"=>"just a name", "CT"=>"634896545666556630", "MT"=>"634896545666557670"}, {"CE"=>[{"C"=>"test secure text", "N"=>"test field name"}], "AC"=>"0", "N"=>"test item name", "D"=>"test item description", "CT"=>"634896545082402010", "MT"=>"634896557631887530", "T"=>"Test, Item, Tag1, Tag2, Tag3"}], "MT"=>"2012-11-27T23:29:23.18873Z"}>
|
18
|
+
|
19
|
+
d = Strongboxio.decrypt(FILENAME, PASSWORD)
|
20
|
+
sbox = Strongboxio.new(d)
|
21
|
+
assert_equal 'Strongboxio',
|
22
|
+
sbox.class.to_s
|
23
|
+
assert_equal '2012-11-27T23:29:23.18873Z',
|
24
|
+
sbox.sbox['MT']
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: strongboxio
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Alex Batko
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-11-29 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: Decrypt and read www.Strongbox.io files.
|
15
|
+
email:
|
16
|
+
- alexbatko@gmail.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- lib/strongboxio.rb
|
22
|
+
- test/2fe22f4d-bd41-476a-8159-c6e0b6487f7d.sbox
|
23
|
+
- test/test_helper.rb
|
24
|
+
- test/test_strongboxio.rb
|
25
|
+
homepage: https://github.com/abatko/strongboxio
|
26
|
+
licenses:
|
27
|
+
- MIT
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubyforge_project:
|
46
|
+
rubygems_version: 1.8.10
|
47
|
+
signing_key:
|
48
|
+
specification_version: 3
|
49
|
+
summary: Decrypt and read www.Strongbox.io files
|
50
|
+
test_files:
|
51
|
+
- test/2fe22f4d-bd41-476a-8159-c6e0b6487f7d.sbox
|
52
|
+
- test/test_helper.rb
|
53
|
+
- test/test_strongboxio.rb
|