secure_string 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +24 -0
- data/README.rdoc +63 -0
- data/Rakefile +37 -0
- data/lib/secure_string.rb +59 -0
- data/lib/secure_string/base64_methods.rb +28 -0
- data/lib/secure_string/cipher_methods.rb +76 -0
- data/lib/secure_string/digest_methods.rb +48 -0
- data/lib/secure_string/rsa_methods.rb +65 -0
- data/spec/spec_helper.rb +4 -0
- metadata +74 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Copyright (c) 2010, Jeffrey C. Reinecke
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
* Redistributions of source code must retain the above copyright
|
7
|
+
notice, this list of conditions and the following disclaimer.
|
8
|
+
* Redistributions in binary form must reproduce the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer in the
|
10
|
+
documentation and/or other materials provided with the distribution.
|
11
|
+
* Neither the name of the copyright holders nor the
|
12
|
+
names of its contributors may be used to endorse or promote products
|
13
|
+
derived from this software without specific prior written permission.
|
14
|
+
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
16
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
17
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL JEFFREY REINECKE BE LIABLE FOR ANY
|
19
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
20
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
21
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
22
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
23
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
24
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
= Overview
|
2
|
+
|
3
|
+
SecureString is a special string subclass that provides two pieces of
|
4
|
+
functionality that can be used individually:
|
5
|
+
|
6
|
+
* Byte string support: Although a string can already contain bytes, this makes
|
7
|
+
it easier to view and work with strings holding binary data, including
|
8
|
+
conversion to/from raw hex or Base64 encoded values.
|
9
|
+
* Secure string support: Easy methods for RSA encryption, AES encoding, and
|
10
|
+
SHA/MD5 digest hashing, of the data in the strings.
|
11
|
+
|
12
|
+
= Contact
|
13
|
+
|
14
|
+
If you have any questions, comments, concerns, patches, or bugs, you can contact
|
15
|
+
me via the github repository at:
|
16
|
+
|
17
|
+
http://github.com/paploo/secure_string
|
18
|
+
|
19
|
+
or directly via e-mail at:
|
20
|
+
|
21
|
+
mailto:jeff@paploo.net
|
22
|
+
|
23
|
+
= Version History
|
24
|
+
|
25
|
+
[0.9.0 - 2010-Nov-03] Initial release.
|
26
|
+
* Feature complete, but lacks spec tests and examples.
|
27
|
+
|
28
|
+
= TODO List
|
29
|
+
|
30
|
+
* Add complete spec tests.
|
31
|
+
* Add examples.
|
32
|
+
|
33
|
+
= License
|
34
|
+
|
35
|
+
The files contained in this repository are released under the commercially and
|
36
|
+
GPL compatible "New BSD License", given below:
|
37
|
+
|
38
|
+
== License Text
|
39
|
+
|
40
|
+
Copyright (c) 2010, Jeffrey C. Reinecke
|
41
|
+
All rights reserved.
|
42
|
+
|
43
|
+
Redistribution and use in source and binary forms, with or without
|
44
|
+
modification, are permitted provided that the following conditions are met:
|
45
|
+
* Redistributions of source code must retain the above copyright
|
46
|
+
notice, this list of conditions and the following disclaimer.
|
47
|
+
* Redistributions in binary form must reproduce the above copyright
|
48
|
+
notice, this list of conditions and the following disclaimer in the
|
49
|
+
documentation and/or other materials provided with the distribution.
|
50
|
+
* Neither the name of the copyright holders nor the
|
51
|
+
names of its contributors may be used to endorse or promote products
|
52
|
+
derived from this software without specific prior written permission.
|
53
|
+
|
54
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
55
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
56
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
57
|
+
DISCLAIMED. IN NO EVENT SHALL JEFFREY REINECKE BE LIABLE FOR ANY
|
58
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
59
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
60
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
61
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
62
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
63
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require "rake/rdoctask"
|
3
|
+
|
4
|
+
# ===== RDOC BUILDING =====
|
5
|
+
# This isn't necessary if installing from a gem.
|
6
|
+
|
7
|
+
Rake::RDocTask.new do |rdoc|
|
8
|
+
rdoc.rdoc_dir = "rdoc"
|
9
|
+
rdoc.rdoc_files.add "lib/**/*.rb", "README.rdoc"
|
10
|
+
end
|
11
|
+
|
12
|
+
# ===== SPEC TESTING =====
|
13
|
+
|
14
|
+
begin
|
15
|
+
require "spec/rake/spectask"
|
16
|
+
|
17
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
18
|
+
spec.spec_opts = ['-c' '-f specdoc']
|
19
|
+
spec.spec_files = ['spec']
|
20
|
+
end
|
21
|
+
|
22
|
+
Spec::Rake::SpecTask.new(:spec_with_backtrace) do |spec|
|
23
|
+
spec.spec_opts = ['-c' '-f specdoc', '-b']
|
24
|
+
spec.spec_files = ['spec']
|
25
|
+
end
|
26
|
+
rescue LoadError
|
27
|
+
task :spec do
|
28
|
+
puts "You must have rspec installed to run this task."
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# ===== GEM BUILDING =====
|
33
|
+
|
34
|
+
desc "Build the gem file for this package"
|
35
|
+
task :build_gem do
|
36
|
+
STDOUT.puts `gem build secure_string.gemspec`
|
37
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
require_relative 'secure_string/digest_methods'
|
4
|
+
require_relative 'secure_string/base64_methods'
|
5
|
+
require_relative 'secure_string/cipher_methods'
|
6
|
+
require_relative 'secure_string/rsa_methods'
|
7
|
+
|
8
|
+
# SecureString is a String subclass whose emphasis is on byte data rather than
|
9
|
+
# human readable strings. class gives a number of conveniences, such
|
10
|
+
# as easier viewing of the byte data as hex, digest methods, and encryption
|
11
|
+
# and decryption methods.
|
12
|
+
class SecureString < String
|
13
|
+
include Base64Methods
|
14
|
+
include DigestMethods
|
15
|
+
include RSAMethods
|
16
|
+
include CipherMethods
|
17
|
+
|
18
|
+
# Creates the string from one many kinds of values:
|
19
|
+
# [:data] (default) The passed string value is directly used.
|
20
|
+
# [:hex] Initialize using a hexidecimal string.
|
21
|
+
# [:int] Initialize using the numeric value of the hexidecimal string.
|
22
|
+
# [:base64] Initialize using the given base64 encoded data.
|
23
|
+
def initialize(mode = :data, value)
|
24
|
+
case mode
|
25
|
+
when :hex
|
26
|
+
hex_string = value.to_s
|
27
|
+
data = [hex_string].pack('H' + hex_string.length.to_s)
|
28
|
+
when :data
|
29
|
+
data = value.to_s
|
30
|
+
when :int
|
31
|
+
self.send(__method__, :hex, value.to_i.to_s(16))
|
32
|
+
when :base64
|
33
|
+
data = Base64.decode64(value.to_s)
|
34
|
+
end
|
35
|
+
|
36
|
+
self.replace(data)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Override the default String inspect to return the hexidecimal
|
40
|
+
# representation of the data contained in this string.
|
41
|
+
def inspect
|
42
|
+
return "<#{to_hex}>"
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the hexidecimal string representation of the data.
|
46
|
+
def to_hex
|
47
|
+
return (self.empty? ? '' : self.unpack('H' + (self.length*2).to_s)[0])
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns the data converted from hexidecimal into an integer.
|
51
|
+
# This is usually as a BigInt.
|
52
|
+
#
|
53
|
+
# WARNING: If the data string is empty, then this returns -1, as there is no
|
54
|
+
# integer representation of the absence of data.
|
55
|
+
def to_i
|
56
|
+
return (self.empty? ? -1 : to_hex.hex)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
class SecureString < String
|
4
|
+
module Base64Methods
|
5
|
+
|
6
|
+
def self.included(mod)
|
7
|
+
mod.send(:include, InstanceMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module InstanceMethods
|
11
|
+
|
12
|
+
# Encodes to Base64. By default, the output is made URL safe, which means all
|
13
|
+
# newlines are stripped out. If you want standard formatted Base64 with
|
14
|
+
# newlines, then call this method with url_safe as false.
|
15
|
+
def to_base64(url_safe = true)
|
16
|
+
encoded_data = (url_safe ? Base64.urlsafe_encode64(self) : Base64.encode64(self))
|
17
|
+
return self.class.new( encoded_data )
|
18
|
+
end
|
19
|
+
|
20
|
+
# Decode self as a Base64 data string and return the result.
|
21
|
+
def from_base64
|
22
|
+
return self.class.new( Base64.decode64(self) )
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
class SecureString < String
|
4
|
+
module CipherMethods
|
5
|
+
|
6
|
+
def self.included(mod)
|
7
|
+
mod.send(:extend, ClassMethods)
|
8
|
+
mod.send(:include, InstanceMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
# A convenience method for generating random cipher keys and initialization
|
14
|
+
# vectors.
|
15
|
+
def cipher_keygen(cipher_name)
|
16
|
+
cipher = OpenSSL::Cipher::Cipher.new(cipher_name)
|
17
|
+
cipher.encrypt
|
18
|
+
return [cipher.random_key, cipher.random_iv].map {|s| self.new(s)}
|
19
|
+
end
|
20
|
+
|
21
|
+
# A convenience method for generating a random key and init vector for AES keys.
|
22
|
+
# Defaults to a key length of 256.
|
23
|
+
def aes_keygen(key_len=256)
|
24
|
+
return cipher_keygen("aes-#{key_len.to_i}-cbc")
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
module InstanceMethods
|
30
|
+
|
31
|
+
# Given an OpenSSL cipher name, a key, and initialization vector,
|
32
|
+
# encrypt the data.
|
33
|
+
#
|
34
|
+
# Use OpenSSL::Cipher.ciphers to get a list of available cipher names.
|
35
|
+
#
|
36
|
+
# To generate a new key and iv, do the following:
|
37
|
+
# cipher = OpenSSL::Cipher::Cipher.new(cipher_name)
|
38
|
+
# cipher.encrypt
|
39
|
+
# key = cipher.random_key
|
40
|
+
# iv = cipher.random_iv
|
41
|
+
def to_cipher(cipher_name, key, iv)
|
42
|
+
cipher = OpenSSL::Cipher.new(cipher_name)
|
43
|
+
cipher.encrypt # MUST set the mode BEFORE setting the key and iv!
|
44
|
+
cipher.key = key
|
45
|
+
cipher.iv = iv
|
46
|
+
msg = cipher.update(self)
|
47
|
+
msg << cipher.final
|
48
|
+
return self.class.new(msg)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Given an OpenSSL cipher name, a key, and an init vector,
|
52
|
+
# decrypt the data.
|
53
|
+
def from_cipher(cipher_name, key, iv)
|
54
|
+
cipher = OpenSSL::Cipher.new(cipher_name)
|
55
|
+
cipher.decrypt # MUST set the mode BEFORE setting the key and iv!
|
56
|
+
cipher.key = key
|
57
|
+
cipher.iv = iv
|
58
|
+
msg = cipher.update(self)
|
59
|
+
msg << cipher.final
|
60
|
+
return self.class.new(msg)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Given an AES key and initialization vector, AES encode the data.
|
64
|
+
def to_aes(key, iv, key_len=256)
|
65
|
+
return self.class.new( to_cipher("aes-#{key_len.to_i}-cbc", key, iv) )
|
66
|
+
end
|
67
|
+
|
68
|
+
# Given an AES key and init vector, AES decode the data.
|
69
|
+
def from_aes(key, iv, key_len=256)
|
70
|
+
return self.class.new( from_cipher("aes-#{key_len.to_i}-cbc", key, iv) )
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
class SecureString < String
|
4
|
+
module DigestMethods
|
5
|
+
|
6
|
+
def self.included(mod)
|
7
|
+
mod.send(:include, InstanceMethods)
|
8
|
+
end
|
9
|
+
|
10
|
+
module InstanceMethods
|
11
|
+
|
12
|
+
# Returns the digest of the byte string as a SecureString, using the passed OpenSSL object.
|
13
|
+
def to_digest(digest_obj)
|
14
|
+
return self.class.new( digest_obj.digest(self) )
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the MD5 of the byte string as a SecureString.
|
18
|
+
def to_md5
|
19
|
+
return to_digest( OpenSSL::Digest::MD5.new )
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the SHA2 of the byte string as a SecureString.
|
23
|
+
#
|
24
|
+
# By default, this uses the 256 bit SHA2, but the optional arugment allows
|
25
|
+
# specification of which bit length to use.
|
26
|
+
def to_sha2(length=256)
|
27
|
+
if [224,256,384,512].include?(length)
|
28
|
+
digest_klass = OpenSSL::Digest.const_get("SHA#{length}", false)
|
29
|
+
return to_digest( digest_klass )
|
30
|
+
else
|
31
|
+
raise ArgumentError, "Invalid SHA2 length: #{length}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the SHA2 256 of the data string. See +to_sha2+.
|
36
|
+
def to_sha256
|
37
|
+
return to_sha2(256)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns the SHA2 512 of the data string. See +to_sha2+.
|
41
|
+
def to_sha512
|
42
|
+
return to_sha2(512)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
class SecureString < String
|
4
|
+
module RSAMethods
|
5
|
+
|
6
|
+
def self.included(mod)
|
7
|
+
mod.send(:extend, ClassMethods)
|
8
|
+
mod.send(:include, InstanceMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
# A convenience method for generating random public/private RSA key pairs.
|
14
|
+
# Defaults to a key length of 1024.
|
15
|
+
#
|
16
|
+
# Returns the private key first, then the public key. Returns them in PEM file
|
17
|
+
# format by default, as this is most useful for portability. DER format can
|
18
|
+
# be explicitly specified with the second argument.
|
19
|
+
def rsa_keygen(key_len=1024, format = :pem)
|
20
|
+
private_key_obj = OpenSSL::PKey::RSA.new(key_len.to_i)
|
21
|
+
public_key_obj = private_key_obj.public_key
|
22
|
+
formatting_method = (format == :der ? :to_der : :to_pem)
|
23
|
+
return [private_key_obj, public_key_obj].map {|k| self.new( k.send(formatting_method) )}
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
module InstanceMethods
|
29
|
+
|
30
|
+
# Given an RSA public key, it RSA encrypts the data string.
|
31
|
+
#
|
32
|
+
# Note that the key must be 11 bytes longer than the data string or it doesn't
|
33
|
+
# work.
|
34
|
+
def to_rsa(public_key)
|
35
|
+
key = OpenSSL::PKey::RSA.new(public_key)
|
36
|
+
return self.class.new( key.public_encrypt(self) )
|
37
|
+
end
|
38
|
+
|
39
|
+
# Given an RSA private key, it decrypts the data string back into the original text.
|
40
|
+
def from_rsa(private_key)
|
41
|
+
key = OpenSSL::PKey::RSA.new(private_key)
|
42
|
+
return self.class.new( key.private_decrypt(self) )
|
43
|
+
end
|
44
|
+
|
45
|
+
# Signs the given message using hte given private key.
|
46
|
+
#
|
47
|
+
# By default, signs using SHA256, but another digest object can be given.
|
48
|
+
def sign(private_key, digest_obj=OpenSSL::Digest::SHA256.new)
|
49
|
+
key = OpenSSL::PKey::RSA.new(private_key)
|
50
|
+
return self.class.new( key.sign(digest_obj, self) )
|
51
|
+
end
|
52
|
+
|
53
|
+
# Verifies the given signature matches the messages digest, using the
|
54
|
+
# signer's public key.
|
55
|
+
#
|
56
|
+
# By default, verifies using SHA256, but another digest object can be given.
|
57
|
+
def verify?(public_key, signature, digest_obj=OpenSSL::Digest::SHA256.new)
|
58
|
+
key = OpenSSL::PKey::RSA.new(public_key)
|
59
|
+
return key.verify(digest_obj, signature.to_s, self)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: secure_string
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 9
|
8
|
+
- 0
|
9
|
+
version: 0.9.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Jeff Reinecke
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-11-03 00:00:00 -07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
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"
|
22
|
+
email: jeff@paploo.net
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README.rdoc
|
29
|
+
files:
|
30
|
+
- README.rdoc
|
31
|
+
- LICENSE.txt
|
32
|
+
- Rakefile
|
33
|
+
- lib/secure_string/base64_methods.rb
|
34
|
+
- lib/secure_string/cipher_methods.rb
|
35
|
+
- lib/secure_string/digest_methods.rb
|
36
|
+
- lib/secure_string/rsa_methods.rb
|
37
|
+
- lib/secure_string.rb
|
38
|
+
- spec/spec_helper.rb
|
39
|
+
has_rdoc: true
|
40
|
+
homepage: http://www.github.com/paploo/secure_string
|
41
|
+
licenses:
|
42
|
+
- BSD
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options: []
|
45
|
+
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
segments:
|
54
|
+
- 1
|
55
|
+
- 9
|
56
|
+
- 2
|
57
|
+
version: 1.9.2
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
requirements: []
|
67
|
+
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 1.3.7
|
70
|
+
signing_key:
|
71
|
+
specification_version: 3
|
72
|
+
summary: A String subclass for simple handling of binary data and encryption.
|
73
|
+
test_files: []
|
74
|
+
|