secure_string 1.3.2 → 1.3.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -234,6 +234,10 @@ or directly via e-mail at:
234
234
  mailto:jeff@paploo.net
235
235
 
236
236
  = Version History
237
+ [1.3.3 - 2011-Jul-19] Added new functionality
238
+ * (FEATURE) Added PKCS5 v2 keygen from a passphrase support.
239
+ * (FEATURE) Addition of escaped hex string literal support for pasting into code.
240
+ * (FIX) Hex conversion is more consistently fast now.
237
241
  [1.3.2 - 2011-Jun-15] Changed minimum requirements from 1.8.6 to 1.8.7.
238
242
  * (CHANGE) Minimum requirements are 1.8.7 due to too many bugs in 1.8.6.
239
243
  * (FIX) Update to the documentation to reflect 1.8.7 support.
@@ -280,7 +284,7 @@ mailto:jeff@paploo.net
280
284
  * See what happens when including SecurizeString into an object that is not a String.
281
285
  What are the expected root methods? +to_s+, <tt>self.new(string)</tt> are two
282
286
  that I know of.
283
-
287
+ * Add HMAC support.
284
288
 
285
289
  = License
286
290
 
@@ -30,6 +30,12 @@ class SecureString < String
30
30
  return data_to_hex
31
31
  end
32
32
 
33
+ # Add a method to convert the internal binary dat into an escaped hex string
34
+ # which is suitable for pasting into Ruby and Javascript source files.
35
+ def to_escaped_hex
36
+ return data_to_escaped_hex
37
+ end
38
+
33
39
  # Override the default inspect to return the hexidecimal
34
40
  # representation of the data contained in this string.
35
41
  def inspect
@@ -25,7 +25,7 @@ module SecurizeString
25
25
  case data_type_hint
26
26
  when :hex
27
27
  hex_string = value.to_s.delete('^0-9a-fA-F')
28
- data_string = [hex_string].pack('H' + hex_string.bytesize.to_s)
28
+ data_string = [hex_string].pack('H*')
29
29
  when :data
30
30
  data_string = value.to_s
31
31
  when :int
@@ -46,7 +46,7 @@ module SecurizeString
46
46
 
47
47
  # Returns the hexidecimal string representation of the data.
48
48
  def data_to_hex
49
- return (self.to_s.empty? ? '' : self.to_s.unpack('H' + (self.to_s.bytesize*2).to_s)[0])
49
+ return (self.to_s.empty? ? '' : self.to_s.unpack('H*')[0])
50
50
  end
51
51
 
52
52
  # Returns the data converted from hexidecimal into an integer.
@@ -56,6 +56,26 @@ module SecurizeString
56
56
  return (self.to_s.empty? ? 0 : self.data_to_hex.hex)
57
57
  end
58
58
 
59
+ # Returns an escaped hex string representation of the data.
60
+ #
61
+ # This hex string is compatible with Ruby and Javascript.
62
+ def data_to_escaped_hex
63
+ # First we convert the string into a packed hex string.
64
+ hex_string = self.unpack('H*')[0]
65
+
66
+ # Now we grab two elements at a time, prefix it, and add it to the buffer.
67
+ ptr = 0
68
+ len = self.bytesize
69
+ outbuf = ""
70
+ while(ptr<(len*2))
71
+ outbuf << '\x' + hex_string[ptr,2]
72
+ ptr+=2
73
+ end
74
+
75
+ # Now we return the buffer
76
+ return outbuf
77
+ end
78
+
59
79
  end
60
80
 
61
81
  end
@@ -28,12 +28,41 @@ module SecurizeString
28
28
  return [cipher.random_key, cipher.random_iv].map {|s| self.new(s)}
29
29
  end
30
30
 
31
- # A convenience method for generating a random key and init vector for AES keys.
31
+ # A convenience method for generating a cipher key from a passphrase
32
+ # using PKCS5 v2 standards. The key and the salt may be any string.
33
+ #
34
+ # This also derives a predictable initialization vector from the given
35
+ # passphrase in a manor consistent with RFC2898, though it is better to
36
+ # generate a random IV with each encryption of the same data if possible.
37
+ #
38
+ # Note that the OpenSSL::Cipher#pkcs5_keyivgen method is not PKCS5 v2
39
+ # compliant, and therefore will not be implemented.
40
+ def cipher_passphrase_keygen(cipher_name, passphrase, salt, iterations=2048)
41
+ # The first pits of a PBKDF2 are the same wether I build the key and IV
42
+ # at once, but when an IV is built in the RFC2898 standards, they do it
43
+ # this way.
44
+ cipher = OpenSSL::Cipher.new(cipher_name.to_s)
45
+ cipher.encrypt
46
+ key_and_iv = OpenSSL::PKCS5.pbkdf2_hmac_sha1(passphrase.to_s, salt.to_s, iterations.to_i, cipher.key_len+cipher.iv_len)
47
+ return [key_and_iv[0,cipher.key_len], key_and_iv[cipher.key_len, cipher.iv_len]].map {|s| self.new(s)}
48
+ end
49
+
50
+ # A convenience method for generating a random key and init vector for AES
51
+ # encryption.
52
+ #
32
53
  # Defaults to a key length of 256.
33
54
  def aes_keygen(key_len=256)
34
55
  return cipher_keygen("aes-#{key_len.to_i}-cbc")
35
56
  end
36
57
 
58
+ # A convenience method for generating a key and init vector from a passphrase
59
+ # for AES encryption.
60
+ #
61
+ # Defaults to a key length of 256.
62
+ def aes_passphrase_keygen(key_len, passphrase, salt, iterations=2048)
63
+ return cipher_passphrase_keygen("aes-#{key_len.to_i}-cbc", passphrase, salt, iterations)
64
+ end
65
+
37
66
  end
38
67
 
39
68
  # Adds instance methods for OpenSSL::Cipher support, including AES encryption,
@@ -47,6 +47,14 @@ describe "SecurityString" do
47
47
  ss.data_to_i.should == 0
48
48
  end
49
49
 
50
+ it 'should be able to convert to an escaped hex string' do
51
+ @messages.each do |message|
52
+ ss = SecureString.new(message[:string])
53
+ ss.data_to_escaped_hex.delete('^0-9A-Fa-f').should == message[:hex]
54
+ ss.data_to_escaped_hex.should == message[:string].each_byte.map {|b| '\x' + ('%02x' % b)}.join
55
+ end
56
+ end
57
+
50
58
  end
51
59
 
52
60
  end
@@ -18,11 +18,54 @@ describe "SecureString" do
18
18
  iv.should be_kind_of(SecureString)
19
19
  end
20
20
 
21
+ it 'should provide a passphrase keygen helper' do
22
+ SecureString.should respond_to(:cipher_passphrase_keygen)
23
+ end
24
+
25
+ it 'should provide passphrase generated keys as a SecureString class' do
26
+ key, iv = SecureString.cipher_passphrase_keygen('DES', 'some passphrase', 'my salt')
27
+
28
+ key.should_not be_nil
29
+ iv.should_not be_nil
30
+
31
+ key.should be_kind_of(SecureString)
32
+ iv.should be_kind_of(SecureString)
33
+ end
34
+
35
+ it 'should generate keys from passphrases with 2048 rounds by default' do
36
+ default_key, default_iv = SecureString.cipher_passphrase_keygen('DES', 'some passphrase', 'my_salt')
37
+
38
+ key, iv = SecureString.cipher_passphrase_keygen('DES', 'some passphrase', 'my_salt', 2048)
39
+ key.should == default_key
40
+ iv.should == default_iv
41
+
42
+ key, iv = SecureString.cipher_passphrase_keygen('DES', 'some passphrase', 'my_salt', 1024)
43
+ key.should_not == default_key
44
+ key.should_not == default_iv
45
+ end
46
+
47
+ it 'should generate a predictable key from a given passphrase' do
48
+ SecureString.cipher_passphrase_keygen('DES', 'some passphrase', 'my_salt').should == SecureString.cipher_passphrase_keygen('DES', 'some passphrase', 'my_salt')
49
+ SecureString.cipher_passphrase_keygen('DES', 'some passphrase', 'my_salt').should_not == SecureString.cipher_passphrase_keygen('DES', 'some passphrase', 'another_salt')
50
+ SecureString.cipher_passphrase_keygen('DES', 'some passphrase', 'my_salt').should_not == SecureString.cipher_passphrase_keygen('DES', 'another passphrase', 'my_salt')
51
+ end
52
+
53
+ it 'should provide an AES passphrase keygen helper' do
54
+ SecureString.should respond_to(:aes_passphrase_keygen)
55
+ end
56
+
57
+ it 'should, for any AES key size, passthrough to the cipher passphrase keygen' do
58
+ [128,192,256].each do |bits|
59
+ SecureString.aes_passphrase_keygen(bits, 'passphrase', 'salt').should == SecureString.cipher_passphrase_keygen("aes-#{bits}-cbc", 'passphrase', 'salt')
60
+ SecureString.aes_passphrase_keygen(bits, 'passphrase', 'salt', 1024).should == SecureString.cipher_passphrase_keygen("aes-#{bits}-cbc", 'passphrase', 'salt', 1024)
61
+ end
62
+ end
63
+
21
64
  it 'should provide an AES keygen helper' do
22
65
  SecureString.should respond_to(:aes_keygen)
23
66
  end
24
67
 
25
- it 'should provide generated AES keysas a SecureString class' do
68
+ it 'should provide generated AES keys as a SecureString class' do
26
69
  key, iv = SecureString.aes_keygen
27
70
 
28
71
  key.should_not be_nil
@@ -84,8 +84,20 @@ describe "SecureString" do
84
84
  end
85
85
  end
86
86
 
87
- it 'should parse hex data with spaces' do
87
+ it 'should implement to_escaped_hex' do
88
+ if( RUBY_VERSION >= '1.9.0' )
89
+ SecureString.instance_methods.should include(:to_escaped_hex)
90
+ else
91
+ SecureString.instance_methods.should include('to_escaped_hex')
92
+ end
88
93
 
94
+ @messages.each do |message|
95
+ ss = SecureString.new(message[:string])
96
+ ss.to_escaped_hex.should == ss.data_to_escaped_hex
97
+ end
98
+ end
99
+
100
+ it 'should parse hex data with spaces' do
89
101
  data = <<-DATA
90
102
  a766a602 b65cffe7 73bcf258 26b322b3 d01b1a97 2684ef53 3e3b4b7f 53fe3762
91
103
  24c08e47 e959b2bc 3b519880 b9286568 247d110f 70f5c5e2 b4590ca3 f55f52fe
@@ -105,6 +117,18 @@ describe "SecureString" do
105
117
  OpenSSL::Digest::SHA.hexdigest(ss).should == "c9f160777d4086fe8095fba58b7e20c228a4006b"
106
118
  end
107
119
 
120
+ it 'should parse hex data with escape characters' do
121
+ # Create the string with escape characters in there.
122
+ data = '\x48\x65\x6c\x6c\x6f'
123
+
124
+ # Make sure Ruby didn't convert them to the actual string.
125
+ data.should_not == 'Hello'
126
+
127
+ # Now see if SecureString can convert it as hex data.
128
+ ss = SecureString.new(data, :type => :hex)
129
+ ss.should == 'Hello'
130
+ end
131
+
108
132
  if( RUBY_VERSION >= '1.9.0' )
109
133
  describe 'Encodings' do
110
134
 
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: secure_string
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 1.3.2
5
+ version: 1.3.3
6
6
  platform: ruby
7
7
  authors:
8
8
  - Jeff Reinecke
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-06-16 00:00:00 Z
13
+ date: 2011-07-19 00:00:00 Z
14
14
  dependencies: []
15
15
 
16
16
  description: " A String subclass to simplify handling of:\n 1. Binary data, including HEX encoding and Bin64 encoding.\n 2. Encryption such as RSA, AES, and digest methods such as SHA and MD5.\n"