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.
@@ -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
- Install from gem, and add the following to your script:
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(OpenSSL::Digest::SHA512).to_hex
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.0 - 2030-Nov-04] Extracted methods into a module that can be easily included
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
- * Add a +to_ss+ or +to_secure+ method to String for easy conversion.
197
- * to_digest should be able to take a string that is the algorithm name.
198
- * Explore how encodings affect the data. What about when finding length? What
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
@@ -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 the base methods necessary to make String or a String subclass handle
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 to String or a String subclass, via
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 to String or a String subclass, via
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 to_hex
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
- # WARNING: If the data string is empty, then this returns -1, as there is no
60
- # integer representation of the absence of data.
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.to_i}-cbc", key, iv) )
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.to_i}-cbc", key, iv) )
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, using the passed OpenSSL object.
17
- def to_digest(digest_obj)
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( OpenSSL::Digest::MD5.new )
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( OpenSSL::Digest::SHA1.new )
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
- digest_klass = OpenSSL::Digest.const_get("SHA#{length}", false)
38
- return to_digest( digest_klass )
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(public_key)
46
- key = OpenSSL::PKey::RSA.new(public_key)
47
- return self.class.new( key.public_encrypt(self) )
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(private_key)
52
- key = OpenSSL::PKey::RSA.new(private_key)
53
- return self.class.new( key.private_decrypt(self) )
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, signs using SHA256, but another digest object can be given.
59
- def sign(private_key, digest_obj=OpenSSL::Digest::SHA256.new)
60
- digest_obj = (digest_obj.kind_of?(Class) ? digest_obj.new : digest_obj)
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 object can be given.
69
- def verify?(public_key, signature, digest_obj=OpenSSL::Digest::SHA256.new)
70
- digest_obj = (digest_obj.kind_of?(Class) ? digest_obj.new : digest_obj)
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
@@ -41,6 +41,7 @@ describe "SecureString" do
41
41
  ss.from_base64.should == message[:string]
42
42
  end
43
43
  end
44
+
44
45
  end
45
46
 
46
47
  end
@@ -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.to_hex.should == message[:hex]
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.to_i.should == message[:int]
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.to_hex)
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
@@ -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(OpenSSL::Digest::MD5).should be_kind_of(SecureString)
22
- @message.to_digest(OpenSSL::Digest::SHA512).should be_kind_of(SecureString)
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(OpenSSL::Digest::MD5)
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(OpenSSL::Digest::SHA512)
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
@@ -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
- @pvt_key = SecureString.new(:hex, "2d2d2d2d2d424547494e205253412050524956415445204b45592d2d2d2d2d0a4d47454341514143455143364c704764426d334a78495048513945345537443141674d424141454345474c62517a6e724a6652525762433365474b3561656b430a4351446d633569312f5669666277494a414d37536b4534554b7750624167682b7831316430564274395149494a4648372f346f784a364d4343474e646e3933330a4a43774d0a2d2d2d2d2d454e44205253412050524956415445204b45592d2d2d2d2d0a")
71
- @pub_key = SecureString.new(:hex, "2d2d2d2d2d424547494e20525341205055424c4943204b45592d2d2d2d2d0a4d426743455143364c704764426d334a78495048513945345537443141674d424141453d0a2d2d2d2d2d454e4420525341205055424c4943204b45592d2d2d2d2d0a")
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 decrypy a message' do
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 sign and verify a message using a private key' do
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
- @encrypted_message = @message.to_rsa(@bob_pub_key)
108
- @signature = @encrypted_message.sign(@alice_pvt_key)
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 = @encrypted_message.verify?(@alice_pub_key, @signature)
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 = @encrypted_message.verify?(@bob_pub_key, @signature)
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
- @decrypted_message = @encrypted_message.from_rsa(@bob_pvt_key)
120
- @decrypted_message.should == @message
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, OpenSSL::Digest::SHA256.new)
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 digest' do
153
+ it 'should allow signing with other digests' do
129
154
  encrypted_message = @message.to_rsa(@bob_pub_key)
130
- comparison_digest_klass = OpenSSL::Digest::SHA256
131
- [OpenSSL::Digest::SHA512, OpenSSL::Digest::MD5, OpenSSL::Digest::SHA1].each do |digest_klass|
132
- next if digest_klass == comparison_digest_klass
133
- signature = encrypted_message.sign(@alice_pvt_key, digest_klass.new)
134
- signature.should_not == encrypted_message.sign(@alice_pvt_key, comparison_digest_klass.new)
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 class' do
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.new)
142
- signature2 = encrypted_message.sign(@alice_pvt_key, digest_klass)
143
- signature1.should == signature2
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)
@@ -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
- - 0
9
- version: 1.1.0
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-04 00:00:00 -07:00
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
- - 2
65
- version: 1.9.2
67
+ - 0
68
+ version: 1.9.0
66
69
  required_rubygems_version: !ruby/object:Gem::Requirement
67
70
  none: false
68
71
  requirements: