secure_string 1.1.0 → 1.1.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/README.rdoc +72 -11
- data/lib/secure_string.rb +17 -0
- data/lib/securize_string/binary_string_data_methods.rb +7 -18
- data/lib/securize_string/cipher_methods.rb +4 -4
- data/lib/securize_string/digest_finder.rb +56 -0
- data/lib/securize_string/digest_methods.rb +21 -7
- data/lib/securize_string/rsa_methods.rb +34 -12
- data/spec/base64_methods_spec.rb +1 -0
- data/spec/binary_string_data_methods_spec.rb +11 -3
- data/spec/digest_finder_spec.rb +48 -0
- data/spec/digest_methods_spec.rb +4 -6
- data/spec/example_spec.rb +75 -0
- data/spec/rsa_methods_spec.rb +48 -23
- data/spec/secure_string_spec.rb +70 -0
- metadata +8 -5
data/README.rdoc
CHANGED
@@ -19,13 +19,20 @@ String version of the value.
|
|
19
19
|
WARNING: it is important to note that the String method +length+ is not a good
|
20
20
|
measure of a byte string's length, as depending on the encoding, it may count
|
21
21
|
multibyte characters as a single element. To ensure that you get the byte
|
22
|
-
length, use the standard string method +bytesize+.
|
22
|
+
length, use the standard string method +bytesize+. See the section on
|
23
|
+
String Encodings for more detail.
|
23
24
|
|
24
25
|
= Installation & Configuration
|
25
26
|
|
26
27
|
== Installation
|
27
28
|
|
28
|
-
|
29
|
+
SecureString is currently only supported in Ruby 1.9.x.
|
30
|
+
|
31
|
+
To install, first install the gem:
|
32
|
+
|
33
|
+
gem install secure_string
|
34
|
+
|
35
|
+
Then require the gem like so:
|
29
36
|
|
30
37
|
require 'secure_string'
|
31
38
|
|
@@ -108,13 +115,14 @@ cryptographic hash sums for the data in the string. Note that since SecureStrin
|
|
108
115
|
handles binary data well, the string value returns is NOT the hex string; to get
|
109
116
|
the hex digest, simply call to_hex:
|
110
117
|
|
118
|
+
ss = SecureString.new("Hello World!")
|
111
119
|
ss.to_md5.to_hex
|
112
120
|
--> "ed076287532e86365e841e92bfc50d8c"
|
113
121
|
ss.to_sha1.to_hex
|
114
122
|
--> "2ef7bde608ce5404e97d5f042f95f89f1c232871"
|
115
123
|
ss.to_sha256.to_hex
|
116
124
|
--> "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069"
|
117
|
-
ss.to_digest(
|
125
|
+
ss.to_digest('SHA-512').to_hex
|
118
126
|
--> "861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8"
|
119
127
|
|
120
128
|
== RSA Methods Overview
|
@@ -167,6 +175,53 @@ cipher:
|
|
167
175
|
# Now decrypt the message:
|
168
176
|
decoded_text = cipher_text.from_aes(key, iv)
|
169
177
|
|
178
|
+
= String Encodings
|
179
|
+
|
180
|
+
== Overview
|
181
|
+
|
182
|
+
Starting in Ruby 1.9.x, String instances manage their own encodings. For example,
|
183
|
+
a string with Unicode characters are usually encoded as UTF-8, while a lot of
|
184
|
+
source is still written in US-ASCII.
|
185
|
+
|
186
|
+
Binary data is independent of the encoding that encodes it, however using the
|
187
|
+
binary data when it is assigned a multi-byte character encoding strategy can
|
188
|
+
lead to a few surprises.
|
189
|
+
|
190
|
+
For example, for a Unicode string, the +length+ method returns the number of
|
191
|
+
characters in the string, while +bytesize+ returns the number of bytes encoded
|
192
|
+
in the string:
|
193
|
+
|
194
|
+
s = "Resum\u00E9"
|
195
|
+
s.encoding --> UTF-8
|
196
|
+
s.length --> 6
|
197
|
+
s.bytesize --> 7
|
198
|
+
|
199
|
+
== SecureString Encoding
|
200
|
+
|
201
|
+
SecureString's basic design philosophy is to--as much as possible--only extend the
|
202
|
+
behavior of String, not replace it. <b>Therefore, SecureString does NOT affect the
|
203
|
+
encoding of the string it is using.</b>
|
204
|
+
|
205
|
+
This is normally not a problem--as long as you use +bytesize+ instead of +length+
|
206
|
+
to get the byte count of the data in the string.
|
207
|
+
|
208
|
+
In rare cases, you may want to change a String to the ASCII-8BIT binary
|
209
|
+
encoding without changing its data. To accomplish this, one should call
|
210
|
+
+force_encoding+ like so:
|
211
|
+
|
212
|
+
s = "Resum\u00E9"
|
213
|
+
s.force_encoding('BINARY')
|
214
|
+
s.encoding --> ASCII-8BIT
|
215
|
+
s.length --> 7
|
216
|
+
s.bytesize --> 7
|
217
|
+
|
218
|
+
Note that when you do this, equality tests can be broken like so:
|
219
|
+
|
220
|
+
s = "Resum\u00E9"
|
221
|
+
b = s.dup.force_encoding('BINARY')
|
222
|
+
s == b --> false
|
223
|
+
s.bytes.to_a == b.bytes.to_a --> true
|
224
|
+
|
170
225
|
= Contact
|
171
226
|
|
172
227
|
If you have any questions, comments, concerns, patches, or bugs, you can contact
|
@@ -179,7 +234,17 @@ or directly via e-mail at:
|
|
179
234
|
mailto:jeff@paploo.net
|
180
235
|
|
181
236
|
= Version History
|
182
|
-
[1.1.
|
237
|
+
[1.1.1 - 2010-Nov-05] Backed down requirements to ruby 1.9.x; Bugfixes and minor changes.
|
238
|
+
* Tested in 1.9.1 and 1.9.2.
|
239
|
+
* Added some documentation and tests on String encodings.
|
240
|
+
* Added more spec tests.
|
241
|
+
* (FEATURE) Digest methods may be supplied via string now.
|
242
|
+
* (FEATURE) RSA encryption works with both public and private keys.
|
243
|
+
* (FEATURE) RSA keys encoded as SecureString can be asked if public or private.
|
244
|
+
* (CHANGE) BinaryStringDataMethods now is non-clobbering and independent.
|
245
|
+
* (CHANGE) An empty string's integer value is now zero.
|
246
|
+
* (FIX) Non-hex characters are ignored when accepting hex data input.
|
247
|
+
[1.1.0 - 2010-Nov-04] Extracted methods into a module that can be easily included
|
183
248
|
on any String class.
|
184
249
|
[1.0.0 - 2010-Nov-04] Added Tests, Examples, and Bugfixes
|
185
250
|
* Added a full suite of spec tests.
|
@@ -193,13 +258,9 @@ mailto:jeff@paploo.net
|
|
193
258
|
|
194
259
|
= TODO List
|
195
260
|
|
196
|
-
*
|
197
|
-
|
198
|
-
|
199
|
-
methods should be recommended to users for finding byte-length vs. char length.
|
200
|
-
* RSA encoding/decoding should auto-detect if the given key is public or private,
|
201
|
-
and use the correct encoding routine? What about key confusion?
|
202
|
-
* RSA signature digests: accept Digest hashes, not just OpenSSL::Digest hashes.
|
261
|
+
* See what happens when including SecurizeString into an object that is not a String.
|
262
|
+
What are the expected root methods? +to_s+, <tt>self.new(string)</tt> are two
|
263
|
+
that I know of.
|
203
264
|
|
204
265
|
|
205
266
|
= License
|
data/lib/secure_string.rb
CHANGED
@@ -17,4 +17,21 @@ class SecureString < String
|
|
17
17
|
self.replace( data_string )
|
18
18
|
end
|
19
19
|
|
20
|
+
# Override the default to_i method to return the integer value of the data
|
21
|
+
# contained in the string, rather than the parsed value of the characters.
|
22
|
+
def to_i
|
23
|
+
return data_to_i
|
24
|
+
end
|
25
|
+
|
26
|
+
# Add a method to convert the internal binary data into a hex string.
|
27
|
+
def to_hex
|
28
|
+
return data_to_hex
|
29
|
+
end
|
30
|
+
|
31
|
+
# Override the default inspect to return the hexidecimal
|
32
|
+
# representation of the data contained in this string.
|
33
|
+
def inspect
|
34
|
+
return "<#{to_hex}>"
|
35
|
+
end
|
36
|
+
|
20
37
|
end
|
@@ -1,8 +1,7 @@
|
|
1
1
|
require 'base64'
|
2
2
|
|
3
3
|
module SecurizeString
|
4
|
-
# Adds
|
5
|
-
# binary data better.
|
4
|
+
# Adds base methods that help in interpreting the binary data represented by the String value of an object.
|
6
5
|
# See BinaryStringDataMethods::ClassMethods and BinaryStringDataMethods::InstanceMethods for more deatils.
|
7
6
|
module BinaryStringDataMethods
|
8
7
|
|
@@ -11,8 +10,7 @@ module SecurizeString
|
|
11
10
|
mod.send(:include, InstanceMethods)
|
12
11
|
end
|
13
12
|
|
14
|
-
# Adds basic binary data class methods
|
15
|
-
# an include of SecurizeString::BinaryStringDataMethods
|
13
|
+
# Adds basic binary data class methods via an include of SecurizeString::BinaryStringDataMethods.
|
16
14
|
module ClassMethods
|
17
15
|
|
18
16
|
# Creates a data string from one many kinds of values:
|
@@ -23,7 +21,7 @@ module SecurizeString
|
|
23
21
|
def parse_data(mode = :data, value)
|
24
22
|
case mode
|
25
23
|
when :hex
|
26
|
-
hex_string = value.to_s
|
24
|
+
hex_string = value.to_s.delete('^0-9a-fA-F')
|
27
25
|
data_string = [hex_string].pack('H' + hex_string.bytesize.to_s)
|
28
26
|
when :data
|
29
27
|
data_string = value.to_s
|
@@ -38,28 +36,19 @@ module SecurizeString
|
|
38
36
|
|
39
37
|
end
|
40
38
|
|
41
|
-
# Adds basic binary data instance methods
|
42
|
-
# an include of SecurizeString::BinaryStringDataMethods.
|
39
|
+
# Adds basic binary data instance methods via an include of SecurizeString::BinaryStringDataMethods.
|
43
40
|
module InstanceMethods
|
44
41
|
|
45
|
-
# Override the default inspect to return the hexidecimal
|
46
|
-
# representation of the data contained in this string.
|
47
|
-
def inspect
|
48
|
-
return "<#{to_hex}>"
|
49
|
-
end
|
50
|
-
|
51
42
|
# Returns the hexidecimal string representation of the data.
|
52
|
-
def
|
43
|
+
def data_to_hex
|
53
44
|
return (self.to_s.empty? ? '' : self.to_s.unpack('H' + (self.to_s.bytesize*2).to_s)[0])
|
54
45
|
end
|
55
46
|
|
56
47
|
# Returns the data converted from hexidecimal into an integer.
|
57
48
|
# This is usually as a BigInt.
|
58
49
|
#
|
59
|
-
|
60
|
-
|
61
|
-
def to_i
|
62
|
-
return (self.to_s.empty? ? -1 : to_hex.hex)
|
50
|
+
def data_to_i
|
51
|
+
return (self.to_s.empty? ? 0 : self.data_to_hex.hex)
|
63
52
|
end
|
64
53
|
|
65
54
|
end
|
@@ -55,7 +55,7 @@ module SecurizeString
|
|
55
55
|
cipher.encrypt # MUST set the mode BEFORE setting the key and iv!
|
56
56
|
cipher.key = key
|
57
57
|
cipher.iv = iv
|
58
|
-
msg = cipher.update(self)
|
58
|
+
msg = cipher.update(self.to_s)
|
59
59
|
msg << cipher.final
|
60
60
|
return self.class.new(msg)
|
61
61
|
end
|
@@ -67,7 +67,7 @@ module SecurizeString
|
|
67
67
|
cipher.decrypt # MUST set the mode BEFORE setting the key and iv!
|
68
68
|
cipher.key = key
|
69
69
|
cipher.iv = iv
|
70
|
-
msg = cipher.update(self)
|
70
|
+
msg = cipher.update(self.to_s)
|
71
71
|
msg << cipher.final
|
72
72
|
return self.class.new(msg)
|
73
73
|
end
|
@@ -78,13 +78,13 @@ module SecurizeString
|
|
78
78
|
# combination on two different messages as this weakens the security.
|
79
79
|
def to_aes(key, iv)
|
80
80
|
key_len = (key.bytesize * 8)
|
81
|
-
return self.class.new( to_cipher("aes-#{key_len
|
81
|
+
return self.class.new( to_cipher("aes-#{key_len}-cbc", key, iv) )
|
82
82
|
end
|
83
83
|
|
84
84
|
# Given an AES key and init vector, AES-CBC decode the data.
|
85
85
|
def from_aes(key, iv)
|
86
86
|
key_len = (key.bytesize * 8)
|
87
|
-
return self.class.new( from_cipher("aes-#{key_len
|
87
|
+
return self.class.new( from_cipher("aes-#{key_len}-cbc", key, iv) )
|
88
88
|
end
|
89
89
|
|
90
90
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'digest'
|
2
|
+
require 'openssl'
|
3
|
+
|
4
|
+
module SecurizeString
|
5
|
+
# A Helper class to look-up digests easily.
|
6
|
+
class DigestFinder
|
7
|
+
DIGESTS = {
|
8
|
+
'DSS' => OpenSSL::Digest::DSS ,
|
9
|
+
'DSS1' => OpenSSL::Digest::DSS1 ,
|
10
|
+
'MD2' => OpenSSL::Digest::MD2 ,
|
11
|
+
'MD4' => OpenSSL::Digest::MD4 ,
|
12
|
+
'MD5' => OpenSSL::Digest::MD5 ,
|
13
|
+
'SHA' => OpenSSL::Digest::SHA ,
|
14
|
+
'SHA-0' => OpenSSL::Digest::SHA ,
|
15
|
+
'SHA0' => OpenSSL::Digest::SHA ,
|
16
|
+
'SHA-1' => OpenSSL::Digest::SHA1 ,
|
17
|
+
'SHA1' => OpenSSL::Digest::SHA1 ,
|
18
|
+
'SHA-224' => OpenSSL::Digest::SHA224 ,
|
19
|
+
'SHA-256' => OpenSSL::Digest::SHA256 ,
|
20
|
+
'SHA-384' => OpenSSL::Digest::SHA384 ,
|
21
|
+
'SHA-512' => OpenSSL::Digest::SHA512 ,
|
22
|
+
'SHA224' => OpenSSL::Digest::SHA224 ,
|
23
|
+
'SHA256' => OpenSSL::Digest::SHA256 ,
|
24
|
+
'SHA384' => OpenSSL::Digest::SHA384 ,
|
25
|
+
'SHA512' => OpenSSL::Digest::SHA512 ,
|
26
|
+
'RIPEMD-160' => OpenSSL::Digest::RIPEMD160,
|
27
|
+
'RIPEMD160' => OpenSSL::Digest::RIPEMD160
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
# Returns the complete list of digest strings recognized by find.
|
31
|
+
def self.digests
|
32
|
+
@supported_digests = DIGESTS.keys.inject([]) {|a,v| a | [v.upcase, v.downcase]}.sort if @supported_digests.nil?
|
33
|
+
return @supported_digests
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the OpenSSL::Digest class that goes with a given argument.
|
37
|
+
def self.find(obj)
|
38
|
+
if( obj.kind_of?(OpenSSL::Digest) )
|
39
|
+
klass = obj.class
|
40
|
+
elsif( obj.kind_of?(Class) && (obj < OpenSSL::Digest) )
|
41
|
+
klass = obj
|
42
|
+
elsif( obj.kind_of?(Class) && (obj < Digest::Base) )
|
43
|
+
klass = DIGESTS[obj.name.split('::').last.upcase]
|
44
|
+
elsif( obj.kind_of?(Digest::Base) )
|
45
|
+
klass = DIGESTS[obj.class.name.split('::').last.upcase]
|
46
|
+
else
|
47
|
+
klass = DIGESTS[obj.to_s.upcase]
|
48
|
+
end
|
49
|
+
|
50
|
+
raise ArgumentError, "Cannot convert to OpenSSL::Digest: #{obj.inspect}" if klass.nil?
|
51
|
+
|
52
|
+
return klass
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -1,31 +1,45 @@
|
|
1
1
|
require 'openssl'
|
2
|
+
require_relative 'digest_finder'
|
2
3
|
|
3
4
|
module SecurizeString
|
4
5
|
# Adds methods for OpenSSL::Digest support.
|
5
6
|
# See DigestMethods::ClassMethods and DigestMethods::InstanceMethods for more details.
|
6
7
|
module DigestMethods
|
7
|
-
|
8
8
|
def self.included(mod)
|
9
9
|
mod.send(:include, InstanceMethods)
|
10
10
|
end
|
11
11
|
|
12
|
+
# Adds instance methods for OpenSSL::Digest support via inclusion of
|
13
|
+
# SecurizeString::DigestMethods to a class.
|
14
|
+
module ClassMethods
|
15
|
+
|
16
|
+
# Returns a list of supported digests. These can be passed directly into
|
17
|
+
# the cipher methods.
|
18
|
+
def supported_digests
|
19
|
+
return DigestFinder.digests
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
12
24
|
# Adds instance methods for OpenSSL::Digest support via inclusion of
|
13
25
|
# SecurizeString::DigestMethods to a class.
|
14
26
|
module InstanceMethods
|
15
27
|
|
16
|
-
# Returns the digest of the byte string as a SecureString
|
17
|
-
|
28
|
+
# Returns the digest of the byte string as a SecureString using the passed
|
29
|
+
# digest from the list of digests in +supported_digests+.
|
30
|
+
def to_digest(digest)
|
31
|
+
digest_obj = DigestFinder.find(digest).new
|
18
32
|
return self.class.new( digest_obj.digest(self) )
|
19
33
|
end
|
20
34
|
|
21
35
|
# Returns the MD5 of the byte string as a SecureString.
|
22
36
|
def to_md5
|
23
|
-
return to_digest(
|
37
|
+
return to_digest('MD5')
|
24
38
|
end
|
25
39
|
|
26
40
|
# Returns the SHA1 of the byte string as SecureString
|
27
41
|
def to_sha1
|
28
|
-
return to_digest(
|
42
|
+
return to_digest('SHA-1')
|
29
43
|
end
|
30
44
|
|
31
45
|
# Returns the SHA2 of the byte string as a SecureString.
|
@@ -34,8 +48,8 @@ module SecurizeString
|
|
34
48
|
# specification of which bit length to use.
|
35
49
|
def to_sha2(length=256)
|
36
50
|
if [224,256,384,512].include?(length)
|
37
|
-
|
38
|
-
return to_digest(
|
51
|
+
digest = "SHA-#{length}"
|
52
|
+
return to_digest( digest )
|
39
53
|
else
|
40
54
|
raise ArgumentError, "Invalid SHA2 length: #{length}"
|
41
55
|
end
|
@@ -42,22 +42,25 @@ module SecurizeString
|
|
42
42
|
#
|
43
43
|
# Note that the key must be 11 bytes longer than the data string or it doesn't
|
44
44
|
# work.
|
45
|
-
def to_rsa(
|
46
|
-
key = OpenSSL::PKey::RSA.new(
|
47
|
-
|
45
|
+
def to_rsa(key)
|
46
|
+
key = OpenSSL::PKey::RSA.new(key)
|
47
|
+
cipher_text = key.private? ? key.private_encrypt(self.to_s) : key.public_encrypt(self.to_s)
|
48
|
+
return self.class.new(cipher_text)
|
48
49
|
end
|
49
50
|
|
50
51
|
# Given an RSA private key, it decrypts the data string back into the original text.
|
51
|
-
def from_rsa(
|
52
|
-
key = OpenSSL::PKey::RSA.new(
|
53
|
-
|
52
|
+
def from_rsa(key)
|
53
|
+
key = OpenSSL::PKey::RSA.new(key)
|
54
|
+
plain_text = key.private? ? key.private_decrypt(self.to_s) : key.public_decrypt(self.to_s)
|
55
|
+
return self.class.new(plain_text)
|
54
56
|
end
|
55
57
|
|
56
58
|
# Signs the given message using hte given private key.
|
57
59
|
#
|
58
|
-
# By default,
|
59
|
-
|
60
|
-
|
60
|
+
# By default, verifies using SHA256, but another digest method can be given
|
61
|
+
# using the list of DigestFinder supported digests.
|
62
|
+
def sign(private_key, digest_method='SHA-256')
|
63
|
+
digest_obj = DigestFinder.find(digest_method).new
|
61
64
|
key = OpenSSL::PKey::RSA.new(private_key)
|
62
65
|
return self.class.new( key.sign(digest_obj, self) )
|
63
66
|
end
|
@@ -65,13 +68,32 @@ module SecurizeString
|
|
65
68
|
# Verifies the given signature matches the messages digest, using the
|
66
69
|
# signer's public key.
|
67
70
|
#
|
68
|
-
# By default, verifies using SHA256, but another digest
|
69
|
-
|
70
|
-
|
71
|
+
# By default, verifies using SHA256, but another digest method can be given
|
72
|
+
# using the list of DigestFinder supported digests.
|
73
|
+
def verify?(public_key, signature, digest_method='SHA-256')
|
74
|
+
digest_obj = DigestFinder.find(digest_method).new
|
71
75
|
key = OpenSSL::PKey::RSA.new(public_key)
|
72
76
|
return key.verify(digest_obj, signature.to_s, self)
|
73
77
|
end
|
74
78
|
|
79
|
+
# Interpret the conetents of the string as an RSA key, and determine if it is public.
|
80
|
+
#
|
81
|
+
# Even though private keys contain all the information necessary to reconstitute
|
82
|
+
# a public key, this method returns false. This is in contrast to the
|
83
|
+
# behavior of OpenSSL::PKey::RSA, which return true for both public and
|
84
|
+
# private checks with a private key (since it reconstituted the public
|
85
|
+
# key and it is available for use).
|
86
|
+
def public_rsa_key?
|
87
|
+
# There is an interesting bug I came across, where +public?+ can be true on a private key!
|
88
|
+
return !private_rsa_key?
|
89
|
+
end
|
90
|
+
|
91
|
+
# Interpret the conents of the string as an RSA key, and determine if it is private.
|
92
|
+
def private_rsa_key?
|
93
|
+
key = OpenSSL::PKey::RSA.new(self.to_s)
|
94
|
+
return key.private?
|
95
|
+
end
|
96
|
+
|
75
97
|
end
|
76
98
|
|
77
99
|
end
|
data/spec/base64_methods_spec.rb
CHANGED
@@ -11,14 +11,14 @@ describe "SecurityString" do
|
|
11
11
|
it 'should be able to convert to a hex string' do
|
12
12
|
@messages.each do |message|
|
13
13
|
ss = SecureString.new(message[:string])
|
14
|
-
ss.
|
14
|
+
ss.data_to_hex.should == message[:hex]
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
18
|
it 'should be able to convert to an int value' do
|
19
19
|
@messages.each do |message|
|
20
20
|
ss = SecureString.new(message[:string])
|
21
|
-
ss.
|
21
|
+
ss.data_to_i.should == message[:int]
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
@@ -34,11 +34,19 @@ describe "SecurityString" do
|
|
34
34
|
@messages.each do |message|
|
35
35
|
s = String.new(message[:string])
|
36
36
|
ss = SecureString.new(message[:string])
|
37
|
-
ss.inspect.should include(ss.
|
37
|
+
ss.inspect.should include(ss.data_to_hex)
|
38
38
|
ss.inspect.should_not include(s.to_s)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
+
it 'should return appropriate values on an empty string' do
|
43
|
+
ss = SecureString.new('')
|
44
|
+
|
45
|
+
ss.data_to_hex.should == ''
|
46
|
+
ss.data_to_i.should be_kind_of(Integer)
|
47
|
+
ss.data_to_i.should == 0
|
48
|
+
end
|
49
|
+
|
42
50
|
end
|
43
51
|
|
44
52
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
describe "Digest Finder" do
|
4
|
+
|
5
|
+
it 'should give a list of digests' do
|
6
|
+
digests = SecurizeString::DigestFinder.digests
|
7
|
+
digests.should be_kind_of(Array)
|
8
|
+
digests.should_not be_empty
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should list both upcase and downcase versions of each digest, just like OpenSSL::Cipher::ciphers' do
|
12
|
+
digests = SecurizeString::DigestFinder.digests
|
13
|
+
lower = digests.select {|d| d == d.downcase}
|
14
|
+
upper = digests.select {|d| d == d.upcase}
|
15
|
+
|
16
|
+
lower.length == upper.length
|
17
|
+
(lower.length * 2) == digests.length
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should find an OpenSSL::Digest instance for a string' do
|
21
|
+
SecurizeString::DigestFinder.find('SHA-256').should == OpenSSL::Digest::SHA256
|
22
|
+
SecurizeString::DigestFinder.find('sha-256').should == OpenSSL::Digest::SHA256
|
23
|
+
SecurizeString::DigestFinder.find('sha256').should == OpenSSL::Digest::SHA256
|
24
|
+
SecurizeString::DigestFinder.find('SHA256').should == OpenSSL::Digest::SHA256
|
25
|
+
|
26
|
+
lambda{ SecurizeString::DigestFinder.find('foobar') }.should raise_error(ArgumentError)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should pass through a valid OpenSSL::Digest instance' do
|
30
|
+
SecurizeString::DigestFinder.find(OpenSSL::Digest::SHA256).should == OpenSSL::Digest::SHA256
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should instantiate an OpenSSL::Digest class' do
|
34
|
+
digest_obj = OpenSSL::Digest::SHA256.new
|
35
|
+
SecurizeString::DigestFinder.find(digest_obj).should == OpenSSL::Digest::SHA256
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should instantiate a Digest class' do
|
39
|
+
SecurizeString::DigestFinder.find(Digest::SHA256).should == OpenSSL::Digest::SHA256
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'sould convert a Digest instance' do
|
43
|
+
digest_obj = Digest::SHA256.new
|
44
|
+
|
45
|
+
SecurizeString::DigestFinder.find(digest_obj).should == OpenSSL::Digest::SHA256
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
data/spec/digest_methods_spec.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# Make sure to test that the cipher methods are from openssl and not jsut digest?
|
2
|
-
|
3
1
|
require File.join(File.dirname(__FILE__), 'spec_helper')
|
4
2
|
|
5
3
|
describe "SecureString" do
|
@@ -18,16 +16,16 @@ describe "SecureString" do
|
|
18
16
|
end
|
19
17
|
|
20
18
|
it 'should encode as a SecureString' do
|
21
|
-
@message.to_digest(
|
22
|
-
@message.to_digest(
|
19
|
+
@message.to_digest('MD5').should be_kind_of(SecureString)
|
20
|
+
@message.to_digest('SHA-512').should be_kind_of(SecureString)
|
23
21
|
end
|
24
22
|
|
25
23
|
it 'should contain the raw value, not the hex value' do
|
26
|
-
md5 = @message.to_digest(
|
24
|
+
md5 = @message.to_digest('MD5')
|
27
25
|
md5.should_not == @message_md5_hex
|
28
26
|
md5.to_hex.should == @message_md5_hex
|
29
27
|
|
30
|
-
sha512 = @message.to_digest(
|
28
|
+
sha512 = @message.to_digest('SHA-512')
|
31
29
|
sha512.should_not == @message_sha512_hex
|
32
30
|
sha512.to_hex.should == @message_sha512_hex
|
33
31
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
describe "Examples" do
|
4
|
+
|
5
|
+
it 'should perform the basic usage example' do
|
6
|
+
|
7
|
+
ss = SecureString.new("Hello World!")
|
8
|
+
ss.to_s.should == "Hello World!"
|
9
|
+
ss.inspect.should == "<48656c6c6f20576f726c6421>"
|
10
|
+
|
11
|
+
ss.to_hex.should == "48656c6c6f20576f726c6421"
|
12
|
+
ss.to_i.should == 22405534230753928650781647905
|
13
|
+
ss.to_base64.should == "SGVsbG8gV29ybGQh"
|
14
|
+
|
15
|
+
ss1 = SecureString.new(:data, "Hello World!")
|
16
|
+
ss2 = SecureString.new(:hex, "48656c6c6f20576f726c6421")
|
17
|
+
ss3 = SecureString.new(:int, 22405534230753928650781647905)
|
18
|
+
ss4 = SecureString.new(:base64, "SGVsbG8gV29ybGQh")
|
19
|
+
|
20
|
+
ss1.should == ss
|
21
|
+
ss2.should == ss
|
22
|
+
ss3.should == ss
|
23
|
+
ss4.should == ss
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should perform the base64 example' do
|
27
|
+
SecureString.new("Hello World!").to_base64.should == "SGVsbG8gV29ybGQh"
|
28
|
+
|
29
|
+
(SecureString.new(:base64, "SGVsbG8gV29ybGQh") == "Hello World!" ).should be_true
|
30
|
+
(SecureString.new("SGVsbG8gV29ybGQh") == "Hello World!" ).should be_false
|
31
|
+
(SecureString.new("SGVsbG8gV29ybGQh").from_base64 == "Hello World!").should be_true
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should perform digest example' do
|
35
|
+
ss = SecureString.new("Hello World!")
|
36
|
+
ss.to_md5.to_hex.should == "ed076287532e86365e841e92bfc50d8c"
|
37
|
+
ss.to_sha1.to_hex.should == "2ef7bde608ce5404e97d5f042f95f89f1c232871"
|
38
|
+
ss.to_sha256.to_hex.should == "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069"
|
39
|
+
ss.to_digest(OpenSSL::Digest::SHA512).to_hex.should == "861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8"
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should perform the cipher example' do
|
43
|
+
# Generate a random key and initialization vector.
|
44
|
+
key, iv = SecureString.aes_keygen
|
45
|
+
|
46
|
+
# Now encrypt a message:
|
47
|
+
message = SecureString.new("Hello World!")
|
48
|
+
cipher_text = message.to_aes(key, iv)
|
49
|
+
|
50
|
+
# Now decrypt the message:
|
51
|
+
decoded_text = cipher_text.from_aes(key, iv)
|
52
|
+
|
53
|
+
decoded_text.should == message
|
54
|
+
cipher_text.should_not == message
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should perform the char encoding example' do
|
58
|
+
s = "Resum\u00E9"
|
59
|
+
s.encoding.should == Encoding.find("UTF-8")
|
60
|
+
s.length.should == 6
|
61
|
+
s.bytesize.should == 7
|
62
|
+
|
63
|
+
s = "Resum\u00E9"
|
64
|
+
s.force_encoding('BINARY')
|
65
|
+
s.encoding.should == Encoding.find("ASCII-8BIT")
|
66
|
+
s.length.should == 7
|
67
|
+
s.bytesize.should == 7
|
68
|
+
|
69
|
+
s = "Resum\u00E9"
|
70
|
+
b = s.dup.force_encoding('BINARY')
|
71
|
+
(s == b).should be_false
|
72
|
+
(s.bytes.to_a == b.bytes.to_a).should be_true
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
data/spec/rsa_methods_spec.rb
CHANGED
@@ -61,14 +61,29 @@ describe "SecureString" do
|
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
+
it 'should be able to determine if the key is public or private' do
|
65
|
+
pvt_key, pub_key = SecureString.rsa_keygen
|
66
|
+
|
67
|
+
pvt_key.public_rsa_key?.should be_false
|
68
|
+
pvt_key.private_rsa_key?.should be_true
|
69
|
+
|
70
|
+
pub_key.public_rsa_key?.should be_true
|
71
|
+
pub_key.private_rsa_key?.should be_false
|
72
|
+
end
|
73
|
+
|
64
74
|
end
|
65
75
|
|
66
76
|
describe "Encryption" do
|
67
77
|
|
68
78
|
before(:all) do
|
69
79
|
@key_length = 128
|
70
|
-
|
71
|
-
|
80
|
+
|
81
|
+
#@pvt_key = SecureString.new(:hex, "2d2d2d2d2d424547494e205253412050524956415445204b45592d2d2d2d2d0a4d47454341514143455143364c704764426d334a78495048513945345537443141674d424141454345474c62517a6e724a6652525762433365474b3561656b430a4351446d633569312f5669666277494a414d37536b4534554b7750624167682b7831316430564274395149494a4648372f346f784a364d4343474e646e3933330a4a43774d0a2d2d2d2d2d454e44205253412050524956415445204b45592d2d2d2d2d0a")
|
82
|
+
@pvt_key = SecureString.new("-----BEGIN RSA PRIVATE KEY-----\nMGECAQACEQC6LpGdBm3JxIPHQ9E4U7D1AgMBAAECEGLbQznrJfRRWbC3eGK5aekC\nCQDmc5i1/VifbwIJAM7SkE4UKwPbAgh+x11d0VBt9QIIJFH7/4oxJ6MCCGNdn933\nJCwM\n-----END RSA PRIVATE KEY-----")
|
83
|
+
|
84
|
+
#@pub_key = SecureString.new(:hex, "2d2d2d2d2d424547494e20525341205055424c4943204b45592d2d2d2d2d0a4d426743455143364c704764426d334a78495048513945345537443141674d424141453d0a2d2d2d2d2d454e4420525341205055424c4943204b45592d2d2d2d2d0a")
|
85
|
+
@pub_key = SecureString.new("-----BEGIN RSA PUBLIC KEY-----\nMBgCEQC6LpGdBm3JxIPHQ9E4U7D1AgMBAAE=\n-----END RSA PUBLIC KEY-----")
|
86
|
+
|
72
87
|
@message = SecureString.new("Hello")
|
73
88
|
end
|
74
89
|
|
@@ -81,7 +96,7 @@ describe "SecureString" do
|
|
81
96
|
|
82
97
|
# We cannot independently test encryption because it changes everytime
|
83
98
|
# thanks to padding generation.
|
84
|
-
it 'should encrypt and
|
99
|
+
it 'should encrypt and decrypt a message' do
|
85
100
|
encrypted_message = @message.to_rsa(@pub_key)
|
86
101
|
(encrypted_message.bytesize * 8).should == @key_length
|
87
102
|
|
@@ -89,6 +104,14 @@ describe "SecureString" do
|
|
89
104
|
decrypted_message.should == @message
|
90
105
|
end
|
91
106
|
|
107
|
+
it 'should encrypt and decrypt a message swapping pub and pvt keys' do
|
108
|
+
encrypted_message = @message.to_rsa(@pvt_key)
|
109
|
+
(encrypted_message.bytesize * 8).should == @key_length
|
110
|
+
|
111
|
+
decrypted_message = encrypted_message.from_rsa(@pub_key)
|
112
|
+
decrypted_message.should == @message
|
113
|
+
end
|
114
|
+
|
92
115
|
end
|
93
116
|
|
94
117
|
|
@@ -102,50 +125,53 @@ describe "SecureString" do
|
|
102
125
|
@message = SecureString.new("Hello")
|
103
126
|
end
|
104
127
|
|
105
|
-
it 'should
|
128
|
+
it 'should do a standard encrypt/sign then verify/decrypt cycle' do
|
106
129
|
# Alice encrypts a message for Bob and signs is.
|
107
|
-
|
108
|
-
|
130
|
+
encrypted_message = @message.to_rsa(@bob_pub_key)
|
131
|
+
signature = encrypted_message.sign(@alice_pvt_key)
|
132
|
+
encrypted_message.should_not be_empty
|
133
|
+
signature.should_not be_empty
|
109
134
|
|
110
135
|
# Verify it came from Alice.
|
111
|
-
is_verified =
|
136
|
+
is_verified = encrypted_message.verify?(@alice_pub_key, signature)
|
112
137
|
is_verified.should be_true
|
113
138
|
|
114
139
|
# Verify it did not come from Bob.
|
115
|
-
is_verified =
|
140
|
+
is_verified = encrypted_message.verify?(@bob_pub_key, signature)
|
116
141
|
is_verified.should be_false
|
117
142
|
|
118
143
|
# Bob should now decrypt it
|
119
|
-
|
120
|
-
|
144
|
+
decrypted_message = encrypted_message.from_rsa(@bob_pvt_key)
|
145
|
+
decrypted_message.should == @message
|
121
146
|
end
|
122
147
|
|
123
148
|
it 'should default to signing with SHA-256' do
|
124
149
|
encrypted_message = @message.to_rsa(@bob_pub_key)
|
125
|
-
encrypted_message.sign(@alice_pvt_key).should == encrypted_message.sign(@alice_pvt_key,
|
150
|
+
encrypted_message.sign(@alice_pvt_key).should == encrypted_message.sign(@alice_pvt_key, 'SHA-256')
|
126
151
|
end
|
127
152
|
|
128
|
-
it 'should allow signing with other
|
153
|
+
it 'should allow signing with other digests' do
|
129
154
|
encrypted_message = @message.to_rsa(@bob_pub_key)
|
130
|
-
|
131
|
-
[
|
132
|
-
next if
|
133
|
-
signature = encrypted_message.sign(@alice_pvt_key,
|
134
|
-
signature.should_not == encrypted_message.sign(@alice_pvt_key,
|
155
|
+
comparison_digest_method = 'SHA-256'
|
156
|
+
['SHA-512', 'MD5', 'SHA-1'].each do |digest_method|
|
157
|
+
next if digest_method == comparison_digest_method
|
158
|
+
signature = encrypted_message.sign(@alice_pvt_key, digest_method)
|
159
|
+
signature.should_not == encrypted_message.sign(@alice_pvt_key, comparison_digest_method)
|
135
160
|
end
|
136
161
|
end
|
137
162
|
|
138
|
-
it 'should allow passing the digest method as an instance or
|
163
|
+
it 'should allow passing the digest method as an instance, class, or string' do
|
139
164
|
encrypted_message = @message.to_rsa(@bob_pub_key)
|
140
165
|
[OpenSSL::Digest::SHA512, OpenSSL::Digest::SHA256, OpenSSL::Digest::MD5, OpenSSL::Digest::SHA1].each do |digest_klass|
|
141
|
-
signature1 = encrypted_message.sign(@alice_pvt_key, digest_klass
|
142
|
-
signature2 = encrypted_message.sign(@alice_pvt_key, digest_klass)
|
143
|
-
|
166
|
+
signature1 = encrypted_message.sign(@alice_pvt_key, digest_klass)
|
167
|
+
signature2 = encrypted_message.sign(@alice_pvt_key, digest_klass.new)
|
168
|
+
signature3 = encrypted_message.sign(@alice_pvt_key, digest_klass.name.split('::').last)
|
169
|
+
signature2.should == signature1
|
170
|
+
signature3.should == signature1
|
144
171
|
end
|
145
172
|
end
|
146
173
|
|
147
174
|
it 'should work with Digest scoped digest classes' do
|
148
|
-
pending "TODO: postponing feature"
|
149
175
|
encrypted_message = @message.to_rsa(@bob_pub_key)
|
150
176
|
signature1 = encrypted_message.sign(@alice_pvt_key, Digest::SHA256)
|
151
177
|
signature2 = encrypted_message.sign(@alice_pvt_key, OpenSSL::Digest::SHA256)
|
@@ -153,7 +179,6 @@ describe "SecureString" do
|
|
153
179
|
end
|
154
180
|
|
155
181
|
it 'should work with Digest scoped digest instances' do
|
156
|
-
pending "TODO: postponing feature"
|
157
182
|
encrypted_message = @message.to_rsa(@bob_pub_key)
|
158
183
|
signature1 = encrypted_message.sign(@alice_pvt_key, Digest::SHA256.new)
|
159
184
|
signature2 = encrypted_message.sign(@alice_pvt_key, OpenSSL::Digest::SHA256.new)
|
data/spec/secure_string_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
#encoding: UTF-8
|
2
|
+
|
1
3
|
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
4
|
|
3
5
|
describe "SecureString" do
|
@@ -56,4 +58,72 @@ describe "SecureString" do
|
|
56
58
|
newline_count.select {|nl_count| nl_count > 1}.should_not be_empty
|
57
59
|
end
|
58
60
|
|
61
|
+
it 'should implement to_hex' do
|
62
|
+
SecureString.instance_methods.should include(:to_hex)
|
63
|
+
|
64
|
+
@messages.each do |message|
|
65
|
+
ss = SecureString.new(message[:string])
|
66
|
+
ss.to_hex.should == ss.data_to_hex
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should implement to_i properly' do
|
71
|
+
SecureString.instance_methods.should include(:to_i)
|
72
|
+
|
73
|
+
@messages.each do |message|
|
74
|
+
ss = SecureString.new(message[:string])
|
75
|
+
ss.to_i.should == ss.data_to_i
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should parse hex data with spaces' do
|
80
|
+
|
81
|
+
data = <<-DATA
|
82
|
+
a766a602 b65cffe7 73bcf258 26b322b3 d01b1a97 2684ef53 3e3b4b7f 53fe3762
|
83
|
+
24c08e47 e959b2bc 3b519880 b9286568 247d110f 70f5c5e2 b4590ca3 f55f52fe
|
84
|
+
effd4c8f e68de835 329e603c c51e7f02 545410d1 671d108d f5a4000d cf20a439
|
85
|
+
4949d72c d14fbb03 45cf3a29 5dcda89f 998f8755 2c9a58b1 bdc38483 5e477185
|
86
|
+
f96e68be bb0025d2 d2b69edf 21724198 f688b41d eb9b4913 fbe696b5 457ab399
|
87
|
+
21e1d759 1f89de84 57e8613c 6c9e3b24 2879d4d8 783b2d9c a9935ea5 26a729c0
|
88
|
+
6edfc501 37e69330 be976012 cc5dfe1c 14c4c68b d1db3ecb 24438a59 a09b5db4
|
89
|
+
35563e0d 8bdf572f 77b53065 cef31f32 dc9dbaa0 4146261e 9994bd5c d0758e3d"
|
90
|
+
DATA
|
91
|
+
|
92
|
+
ss = SecureString.new(:hex, data)
|
93
|
+
|
94
|
+
# This was taken from a publically published SHA-0 data collision document,
|
95
|
+
# so the best way to know that the data is good is to SHA-0 it and see if
|
96
|
+
# we get back the value! (http://fr.wikipedia.org/wiki/SHA-0)
|
97
|
+
OpenSSL::Digest::SHA.hexdigest(ss).should == "c9f160777d4086fe8095fba58b7e20c228a4006b"
|
98
|
+
end
|
99
|
+
|
100
|
+
describe 'Encodings' do
|
101
|
+
|
102
|
+
before(:each) do
|
103
|
+
@unicode_string = "A resumé for the moose; Eine Zusammenfassung für die Elche; Резюме для лосей; アメリカヘラジカのための概要; Μια περίληψη για τις άλκες; 麋的一份簡歷; Un résumé pour les orignaux."
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should NOT change the encoding of a string' do
|
107
|
+
@unicode_string.encoding.should == Encoding.find("UTF-8")
|
108
|
+
ss = SecureString.new(@unicode_string)
|
109
|
+
ss.encoding.should == @unicode_string.encoding
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'should NOT change the length to the byte count for UTF-8 encoded strings.' do
|
113
|
+
@unicode_string.encoding.should == Encoding.find("UTF-8")
|
114
|
+
ss = SecureString.new(@unicode_string)
|
115
|
+
ss.length.should < ss.bytesize
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'should allow forced transcoding to binary' do
|
119
|
+
ss = SecureString.new(@unicode_string)
|
120
|
+
|
121
|
+
ss.encoding.should == Encoding.find("UTF-8")
|
122
|
+
ss.force_encoding("Binary")
|
123
|
+
ss.encoding.should == Encoding.find("ASCII-8BIT")
|
124
|
+
@unicode_string.encoding.should == Encoding.find("UTF-8")
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
59
129
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 1
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 1.1.
|
8
|
+
- 1
|
9
|
+
version: 1.1.1
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Jeff Reinecke
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-11-
|
17
|
+
date: 2010-11-05 00:00:00 -07:00
|
18
18
|
default_executable:
|
19
19
|
dependencies: []
|
20
20
|
|
@@ -34,13 +34,16 @@ files:
|
|
34
34
|
- lib/securize_string/base64_methods.rb
|
35
35
|
- lib/securize_string/binary_string_data_methods.rb
|
36
36
|
- lib/securize_string/cipher_methods.rb
|
37
|
+
- lib/securize_string/digest_finder.rb
|
37
38
|
- lib/securize_string/digest_methods.rb
|
38
39
|
- lib/securize_string/rsa_methods.rb
|
39
40
|
- lib/securize_string.rb
|
40
41
|
- spec/base64_methods_spec.rb
|
41
42
|
- spec/binary_string_data_methods_spec.rb
|
42
43
|
- spec/cipher_methods_spec.rb
|
44
|
+
- spec/digest_finder_spec.rb
|
43
45
|
- spec/digest_methods_spec.rb
|
46
|
+
- spec/example_spec.rb
|
44
47
|
- spec/rsa_methods_spec.rb
|
45
48
|
- spec/secure_string_spec.rb
|
46
49
|
- spec/spec_helper.rb
|
@@ -61,8 +64,8 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
61
64
|
segments:
|
62
65
|
- 1
|
63
66
|
- 9
|
64
|
-
-
|
65
|
-
version: 1.9.
|
67
|
+
- 0
|
68
|
+
version: 1.9.0
|
66
69
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
70
|
none: false
|
68
71
|
requirements:
|