pluginaweek-encrypted_strings 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +59 -0
- data/LICENSE +20 -0
- data/README.rdoc +88 -0
- data/Rakefile +96 -0
- data/init.rb +1 -0
- data/lib/encrypted_strings.rb +7 -0
- data/lib/encrypted_strings/asymmetric_cipher.rb +185 -0
- data/lib/encrypted_strings/cipher.rb +17 -0
- data/lib/encrypted_strings/extensions/string.rb +205 -0
- data/lib/encrypted_strings/sha_cipher.rb +67 -0
- data/lib/encrypted_strings/symmetric_cipher.rb +101 -0
- data/test/asymmetric_cipher_test.rb +183 -0
- data/test/cipher_test.rb +15 -0
- data/test/keys/encrypted_private +12 -0
- data/test/keys/private +9 -0
- data/test/keys/public +4 -0
- data/test/sha_cipher_test.rb +82 -0
- data/test/string_test.rb +222 -0
- data/test/symmetric_cipher_test.rb +99 -0
- data/test/test_helper.rb +4 -0
- metadata +79 -0
data/CHANGELOG.rdoc
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
== master
|
2
|
+
|
3
|
+
== 0.3.2 / 2009-01-11
|
4
|
+
|
5
|
+
* Use Array#pack/String#unpack instead of Base64 to be compatible with Ruby 1.9+
|
6
|
+
|
7
|
+
== 0.3.1 / 2008-12-4
|
8
|
+
|
9
|
+
* Fix symmetric ciphers not working on Ruby 1.8.6 and below
|
10
|
+
|
11
|
+
== 0.3.0 / 2008-12-14
|
12
|
+
|
13
|
+
* Remove the PluginAWeek namespace
|
14
|
+
|
15
|
+
== 0.2.1 / 2008-12-04
|
16
|
+
|
17
|
+
* Fix class-level defaults not working when inherited
|
18
|
+
|
19
|
+
== 0.2.0 / 2008-12-01
|
20
|
+
|
21
|
+
* Remove AsymmetricEncryptor.default_algorithm, instead relying on SymmetricEncryptor.default_algorithm
|
22
|
+
* Rename NoKeyError to NoPasswordError
|
23
|
+
* Rename Encryptors to Ciphers
|
24
|
+
* Remove deprecated SymmetricEncryptor#key option
|
25
|
+
* Require that symmetric encryption be PKCS #5 compliant
|
26
|
+
|
27
|
+
== 0.1.1 / 2008-12-01
|
28
|
+
|
29
|
+
* Fix non-compliant PKCS #5 algorithm being used for symmetric encryption. Use :pkcs5_compliant => true for better security.
|
30
|
+
* Rename SymmetricEncryptor#key to #password
|
31
|
+
* Fix deprecation messages for Cipher#encrypt/decrypt
|
32
|
+
|
33
|
+
== 0.1.0 / 2008-07-06
|
34
|
+
|
35
|
+
* Remove dependency on active_support
|
36
|
+
|
37
|
+
== 0.0.5 / 2008-07-05
|
38
|
+
|
39
|
+
* Add automatic stringification of salts for SHA encryption
|
40
|
+
* Fix not resetting the encryptor after calling decrypt!
|
41
|
+
|
42
|
+
== 0.0.4 / 2008-05-05
|
43
|
+
|
44
|
+
* Updated documentation
|
45
|
+
|
46
|
+
== 0.0.3 / 2007-09-18
|
47
|
+
|
48
|
+
* Remove gem dependency on activesupport
|
49
|
+
|
50
|
+
== 0.0.2 / 2007-08-23
|
51
|
+
|
52
|
+
* Fix not allowing the decryption mode to be overriden if the string already has an encryptor
|
53
|
+
* Convert dos newlines to unix newlines
|
54
|
+
|
55
|
+
== 0.0.1 / 2007-08-05
|
56
|
+
|
57
|
+
* Official public release
|
58
|
+
* Add api documentation
|
59
|
+
* Refactor unit test names
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2005 Rick Olson, 2006-2009 Aaron Pfeifer
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
= encrypted_strings
|
2
|
+
|
3
|
+
+encrypted_strings+ provides dead-simple string encryption/decryption syntax.
|
4
|
+
|
5
|
+
== Resources
|
6
|
+
|
7
|
+
API
|
8
|
+
|
9
|
+
* http://api.pluginaweek.org/encrypted_strings
|
10
|
+
|
11
|
+
Bugs
|
12
|
+
|
13
|
+
* http://pluginaweek.lighthouseapp.com/projects/13270-encrypted_strings
|
14
|
+
|
15
|
+
Development
|
16
|
+
|
17
|
+
* http://github.com/pluginaweek/encrypted_strings
|
18
|
+
|
19
|
+
Source
|
20
|
+
|
21
|
+
* git://github.com/pluginaweek/encrypted_strings.git
|
22
|
+
|
23
|
+
== Description
|
24
|
+
|
25
|
+
Encrypting and decrypting data is not exactly the most straightforward and DRY
|
26
|
+
way. encrypted_strings improves the syntax and reduces the complexity, adding
|
27
|
+
straightforward support for encrypting values using SHA-1, Symmetric, and
|
28
|
+
Asymmetric ciphers.
|
29
|
+
|
30
|
+
== Usage
|
31
|
+
|
32
|
+
=== SHA Encryption
|
33
|
+
|
34
|
+
>> password = 'shhhh'
|
35
|
+
=> "shhhh"
|
36
|
+
>> encrypted_password = password.encrypt
|
37
|
+
=> "66c85d26dadde7e1db27e15a0776c921e27143bd"
|
38
|
+
>> encrypted_password.class
|
39
|
+
=> String
|
40
|
+
>> encrypted_password.cipher
|
41
|
+
=> #<EncryptedStrings::ShaCipher:0x2b9238889460 @salt="salt">
|
42
|
+
>> encrypted_password == 'shhhh'
|
43
|
+
=> true
|
44
|
+
>> encrypted_password.decrypt
|
45
|
+
NotImplementedError: Decryption is not supported using a(n) EncryptedStrings::ShaCipher
|
46
|
+
from ./script/../config/../config/../vendor/plugins/encrypted_strings/lib/encrypted_strings/cipher.rb:13:in `decrypt'
|
47
|
+
from ./script/../config/../config/../vendor/plugins/encrypted_strings/lib/encrypted_strings/extensions/string.rb:52:in `decrypt'
|
48
|
+
from (irb):40
|
49
|
+
|
50
|
+
When encrypt is called, it creates a +cipher+ instance which is used for
|
51
|
+
future encryption and decryption of the string. The default cipher uses
|
52
|
+
SHA-1 encryption. For ciphers that do not support decryption, equality with
|
53
|
+
other strings is tested by encrypting the other string and checking whether the
|
54
|
+
resulting encrypted value is the same.
|
55
|
+
|
56
|
+
=== Symmetric Encryption
|
57
|
+
|
58
|
+
>> password = 'shhhh'
|
59
|
+
=> "shhhh"
|
60
|
+
>> crypted_password = password.encrypt(:symmetric, :password => 'secret_key')
|
61
|
+
=> "qSg8vOo6QfU=\n"
|
62
|
+
>> crypted_password.class
|
63
|
+
=> String
|
64
|
+
>> crypted_password == 'shhhh'
|
65
|
+
=> true
|
66
|
+
>> password = crypted_password.decrypt
|
67
|
+
=> "shhhh"
|
68
|
+
|
69
|
+
=== Asymmetric encryption
|
70
|
+
|
71
|
+
>> password = 'shhhh'
|
72
|
+
=> "shhhh"
|
73
|
+
>> crypted_password = password.encrypt(:asymmetric, :public_key_file => './public.key', :private_key_file => './private.key')
|
74
|
+
=> "NEwVzcikYUKfS8HTc9L9eg/dMxBCLZ/nFr7J1aQYjkl3I2MPUD0lmjr/saC6\nTJEPwOl60Ki24H8TUwnGtZy14A==\n"
|
75
|
+
>> crypted_password.class
|
76
|
+
=> String
|
77
|
+
>> crypted_password == 'shhhh'
|
78
|
+
=> true
|
79
|
+
>> password = crypted_password.decrypt
|
80
|
+
=> "shhhh"
|
81
|
+
|
82
|
+
== Dependencies
|
83
|
+
|
84
|
+
None.
|
85
|
+
|
86
|
+
== References
|
87
|
+
|
88
|
+
* Rick Olson - sentry[http://github.com/technoweenie/sentry]
|
data/Rakefile
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'rake/contrib/sshpublisher'
|
5
|
+
|
6
|
+
spec = Gem::Specification.new do |s|
|
7
|
+
s.name = 'encrypted_strings'
|
8
|
+
s.version = '0.3.2'
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.summary = 'Dead-simple string encryption/decryption syntax'
|
11
|
+
s.description = s.summary
|
12
|
+
|
13
|
+
s.files = FileList['{lib,test}/**/*'] + %w(CHANGELOG.rdoc init.rb LICENSE Rakefile README.rdoc)
|
14
|
+
s.require_path = 'lib'
|
15
|
+
s.has_rdoc = true
|
16
|
+
s.test_files = Dir['test/**/*_test.rb']
|
17
|
+
|
18
|
+
s.author = 'Aaron Pfeifer'
|
19
|
+
s.email = 'aaron@pluginaweek.org'
|
20
|
+
s.homepage = 'http://www.pluginaweek.org'
|
21
|
+
s.rubyforge_project = 'pluginaweek'
|
22
|
+
end
|
23
|
+
|
24
|
+
desc 'Default: run all tests.'
|
25
|
+
task :default => :test
|
26
|
+
|
27
|
+
desc "Test the #{spec.name} plugin."
|
28
|
+
Rake::TestTask.new(:test) do |t|
|
29
|
+
t.libs << 'lib'
|
30
|
+
t.test_files = spec.test_files
|
31
|
+
t.verbose = true
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
require 'rcov/rcovtask'
|
36
|
+
namespace :test do
|
37
|
+
desc "Test the #{spec.name} plugin with Rcov."
|
38
|
+
Rcov::RcovTask.new(:rcov) do |t|
|
39
|
+
t.libs << 'lib'
|
40
|
+
t.test_files = spec.test_files
|
41
|
+
t.rcov_opts << '--exclude="^(?!lib/)"'
|
42
|
+
t.verbose = true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
rescue LoadError
|
46
|
+
end
|
47
|
+
|
48
|
+
desc "Generate documentation for the #{spec.name} plugin."
|
49
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
50
|
+
rdoc.rdoc_dir = 'rdoc'
|
51
|
+
rdoc.title = spec.name
|
52
|
+
rdoc.template = '../rdoc_template.rb'
|
53
|
+
rdoc.options << '--line-numbers'
|
54
|
+
rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG.rdoc', 'LICENSE', 'lib/**/*.rb')
|
55
|
+
end
|
56
|
+
|
57
|
+
desc 'Generate a gemspec file.'
|
58
|
+
task :gemspec do
|
59
|
+
File.open("#{spec.name}.gemspec", 'w') do |f|
|
60
|
+
f.write spec.to_ruby
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
Rake::GemPackageTask.new(spec) do |p|
|
65
|
+
p.gem_spec = spec
|
66
|
+
p.need_tar = true
|
67
|
+
p.need_zip = true
|
68
|
+
end
|
69
|
+
|
70
|
+
desc 'Publish the beta gem.'
|
71
|
+
task :pgem => [:package] do
|
72
|
+
Rake::SshFilePublisher.new('aaron@pluginaweek.org', '/home/aaron/gems.pluginaweek.org/public/gems', 'pkg', "#{spec.name}-#{spec.version}.gem").upload
|
73
|
+
end
|
74
|
+
|
75
|
+
desc 'Publish the API documentation.'
|
76
|
+
task :pdoc => [:rdoc] do
|
77
|
+
Rake::SshDirPublisher.new('aaron@pluginaweek.org', "/home/aaron/api.pluginaweek.org/public/#{spec.name}", 'rdoc').upload
|
78
|
+
end
|
79
|
+
|
80
|
+
desc 'Publish the API docs and gem'
|
81
|
+
task :publish => [:pgem, :pdoc, :release]
|
82
|
+
|
83
|
+
desc 'Publish the release files to RubyForge.'
|
84
|
+
task :release => [:gem, :package] do
|
85
|
+
require 'rubyforge'
|
86
|
+
|
87
|
+
ruby_forge = RubyForge.new.configure
|
88
|
+
ruby_forge.login
|
89
|
+
|
90
|
+
%w(gem tgz zip).each do |ext|
|
91
|
+
file = "pkg/#{spec.name}-#{spec.version}.#{ext}"
|
92
|
+
puts "Releasing #{File.basename(file)}..."
|
93
|
+
|
94
|
+
ruby_forge.add_release(spec.rubyforge_project, spec.name, spec.version, file)
|
95
|
+
end
|
96
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'encrypted_strings'
|
@@ -0,0 +1,185 @@
|
|
1
|
+
module EncryptedStrings
|
2
|
+
# Indicates no public key was found
|
3
|
+
class NoPublicKeyError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
# Indicates no private key was found
|
7
|
+
class NoPrivateKeyError < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
# Encryption in which the keys used to encrypt/decrypt come in pairs. Also
|
11
|
+
# known as public key encryption. Anything that's encrypted using the
|
12
|
+
# public key can only be decrypted with the same algorithm and a matching
|
13
|
+
# private key. Any message that is encrypted with the private key can only
|
14
|
+
# be decrypted with the matching public key.
|
15
|
+
#
|
16
|
+
# Source: http://support.microsoft.com/kb/246071
|
17
|
+
#
|
18
|
+
# == Encrypting
|
19
|
+
#
|
20
|
+
# To encrypt a string using an asymmetric cipher, the location of the
|
21
|
+
# public key file must be specified. You can define the default for this
|
22
|
+
# value like so:
|
23
|
+
#
|
24
|
+
# EncryptedStrings::AsymmetricCipher.default_public_key_file = './public.key'
|
25
|
+
#
|
26
|
+
# If these configuration options are not passed in to #encrypt, then the
|
27
|
+
# default values will be used. You can override the default values like so:
|
28
|
+
#
|
29
|
+
# password = 'shhhh'
|
30
|
+
# password.encrypt(:asymmetric, :public_key_file => './encrypted_public.key') # => "INy95irZ8AlHmvc6ZAF/ARsTpbqPIB/4bEAKKOebjsayB7NYWtIzpswvzxqf\nNJ5yyuvxfMODrcg7RimEMFkFlg==\n"
|
31
|
+
#
|
32
|
+
# An exception will be raised if either the public key file could not be
|
33
|
+
# found or the key could not decrypt the public key file.
|
34
|
+
#
|
35
|
+
# == Decrypting
|
36
|
+
#
|
37
|
+
# To decrypt a string using an asymmetric cipher, the location of the
|
38
|
+
# private key file must be specified. If this file is itself encrypted, you
|
39
|
+
# must also specify the algorithm and password used to seed the symmetric
|
40
|
+
# algorithm that will decrypt the plublic key file. You can define defaults
|
41
|
+
# for these values like so:
|
42
|
+
#
|
43
|
+
# EncryptedStrings::AsymmetricCipher.default_private_key_file = './private.key'
|
44
|
+
# EncryptedStrings::SymmetricCipher.default_algorithm = 'DES-EDE3-CBC'
|
45
|
+
# EncryptedStrings::SymmetricCipher.default_password = 'secret'
|
46
|
+
#
|
47
|
+
# If these configuration options are not passed in to #decrypt, then the
|
48
|
+
# default values will be used. You can override the default values like so:
|
49
|
+
#
|
50
|
+
# password = "INy95irZ8AlHmvc6ZAF/ARsTpbqPIB/4bEAKKOebjsayB7NYWtIzpswvzxqf\nNJ5yyuvxfMODrcg7RimEMFkFlg==\n"
|
51
|
+
# password.decrypt(:asymmetric, :public_key_file => './encrypted_public.key', :password => 'secret') # => "shhhh"
|
52
|
+
#
|
53
|
+
# An exception will be raised if either the private key file could not be
|
54
|
+
# found or the password could not decrypt the private key file.
|
55
|
+
class AsymmetricCipher < Cipher
|
56
|
+
class << self
|
57
|
+
# The default private key to use during encryption. Default is nil.
|
58
|
+
attr_accessor :default_private_key_file
|
59
|
+
|
60
|
+
# The default public key to use during encryption. Default is nil.
|
61
|
+
attr_accessor :default_public_key_file
|
62
|
+
end
|
63
|
+
|
64
|
+
# Private key used for decrypting data
|
65
|
+
attr_reader :private_key_file
|
66
|
+
|
67
|
+
# Public key used for encrypting data
|
68
|
+
attr_reader :public_key_file
|
69
|
+
|
70
|
+
# The algorithm to use if the key files are encrypted themselves
|
71
|
+
attr_accessor :algorithm
|
72
|
+
|
73
|
+
# The password used during symmetric decryption of the key files
|
74
|
+
attr_accessor :password
|
75
|
+
|
76
|
+
# Creates a new cipher that uses an asymmetric encryption strategy.
|
77
|
+
#
|
78
|
+
# Configuration options:
|
79
|
+
# * <tt>:private_key_file</tt> - Encrypted private key file
|
80
|
+
# * <tt>:public_key_file</tt> - Public key file
|
81
|
+
# * <tt>:password</tt> - The password to use in the symmetric cipher
|
82
|
+
# * <tt>:algorithm</tt> - Algorithm to use symmetrically encrypted strings
|
83
|
+
def initialize(options = {})
|
84
|
+
invalid_options = options.keys - [:private_key_file, :public_key_file, :algorithm, :password]
|
85
|
+
raise ArgumentError, "Unknown key(s): #{invalid_options.join(", ")}" unless invalid_options.empty?
|
86
|
+
|
87
|
+
options = {
|
88
|
+
:private_key_file => AsymmetricCipher.default_private_key_file,
|
89
|
+
:public_key_file => AsymmetricCipher.default_public_key_file
|
90
|
+
}.merge(options)
|
91
|
+
|
92
|
+
@public_key = @private_key = nil
|
93
|
+
|
94
|
+
self.private_key_file = options[:private_key_file]
|
95
|
+
self.public_key_file = options[:public_key_file]
|
96
|
+
raise ArgumentError, 'At least one key file must be specified (:private_key_file or :public_key_file)' unless private_key_file || public_key_file
|
97
|
+
|
98
|
+
self.algorithm = options[:algorithm]
|
99
|
+
self.password = options[:password]
|
100
|
+
|
101
|
+
super()
|
102
|
+
end
|
103
|
+
|
104
|
+
# Encrypts the given data. If no public key file has been specified, then
|
105
|
+
# a NoPublicKeyError will be raised.
|
106
|
+
def encrypt(data)
|
107
|
+
raise NoPublicKeyError, "Public key file: #{public_key_file}" unless public?
|
108
|
+
|
109
|
+
encrypted_data = public_rsa.public_encrypt(data)
|
110
|
+
[encrypted_data].pack('m')
|
111
|
+
end
|
112
|
+
|
113
|
+
# Decrypts the given data. If no private key file has been specified, then
|
114
|
+
# a NoPrivateKeyError will be raised.
|
115
|
+
def decrypt(data)
|
116
|
+
raise NoPrivateKeyError, "Private key file: #{private_key_file}" unless private?
|
117
|
+
|
118
|
+
decrypted_data = data.unpack('m')[0]
|
119
|
+
private_rsa.private_decrypt(decrypted_data)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Sets the location of the private key and loads it
|
123
|
+
def private_key_file=(file)
|
124
|
+
@private_key_file = file and load_private_key
|
125
|
+
end
|
126
|
+
|
127
|
+
# Sets the location of the public key and loads it
|
128
|
+
def public_key_file=(file)
|
129
|
+
@public_key_file = file and load_public_key
|
130
|
+
end
|
131
|
+
|
132
|
+
# Does this cipher have a public key available?
|
133
|
+
def public?
|
134
|
+
return true if @public_key
|
135
|
+
|
136
|
+
load_public_key
|
137
|
+
!@public_key.nil?
|
138
|
+
end
|
139
|
+
|
140
|
+
# Does this cipher have a private key available?
|
141
|
+
def private?
|
142
|
+
return true if @private_key
|
143
|
+
|
144
|
+
load_private_key
|
145
|
+
!@private_key.nil?
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
# Loads the private key from the configured file
|
150
|
+
def load_private_key
|
151
|
+
@private_rsa = nil
|
152
|
+
|
153
|
+
if private_key_file && File.file?(private_key_file)
|
154
|
+
@private_key = File.read(private_key_file)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Loads the public key from the configured file
|
159
|
+
def load_public_key
|
160
|
+
@public_rsa = nil
|
161
|
+
|
162
|
+
if public_key_file && File.file?(public_key_file)
|
163
|
+
@public_key = File.read(public_key_file)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Retrieves the private RSA from the private key
|
168
|
+
def private_rsa
|
169
|
+
if password
|
170
|
+
options = {:password => password}
|
171
|
+
options[:algorithm] = algorithm if algorithm
|
172
|
+
|
173
|
+
private_key = @private_key.decrypt(:symmetric, options)
|
174
|
+
OpenSSL::PKey::RSA.new(private_key)
|
175
|
+
else
|
176
|
+
@private_rsa ||= OpenSSL::PKey::RSA.new(@private_key)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Retrieves the public RSA
|
181
|
+
def public_rsa
|
182
|
+
@public_rsa ||= OpenSSL::PKey::RSA.new(@public_key)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|