symmetric-encryption 0.4.0 → 0.5.0

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.
@@ -0,0 +1,4 @@
1
+ # encoding: utf-8
2
+ module SymmetricEncryption #:nodoc
3
+ VERSION = "0.5.0"
4
+ end
@@ -0,0 +1,132 @@
1
+ module SymmetricEncryption
2
+ class Writer
3
+ # Write to encrypted files and other IO streams
4
+ #
5
+ # Features:
6
+ # * Encryption on the fly whilst writing files.
7
+ # * Large file support by only buffering small amounts of data in memory
8
+ # * Underlying buffering to ensure that encrypted data fits
9
+ # into the Symmetric Encryption Cipher block size
10
+ # Only the last block in the file will be padded if it is less than the block size
11
+ #
12
+ # # Example: Encrypt and write data to a file
13
+ # SymmetricEncryption::Writer.open('test_file') do |file|
14
+ # file.write "Hello World\n"
15
+ # file.write "Keep this secret"
16
+ # end
17
+ #
18
+ # # Example: Compress, Encrypt and write data to a file
19
+ # SymmetricEncryption::Writer.open('encrypted_compressed.zip', :compress => true) do |file|
20
+ # file.write "Hello World\n"
21
+ # file.write "Compress this\n"
22
+ # file.write "Keep this safe and secure\n"
23
+ # end
24
+ #
25
+
26
+
27
+ # Open a file for writing, or use the supplied IO Stream
28
+ #
29
+ # Parameters:
30
+ # filename_or_stream:
31
+ # The filename to open if a string, otherwise the stream to use
32
+ # The file or stream will be closed on completion, use .initialize to
33
+ # avoid having the stream closed automatically
34
+ #
35
+ # options:
36
+ # :compress [true|false]
37
+ # Uses Zlib to compress the data before it is encrypted and
38
+ # written to the file
39
+ # Default: false
40
+ #
41
+ # :header [true|false]
42
+ # Whether to include the magic header that indicates the file
43
+ # is encrypted and whether its contents are compressed
44
+ #
45
+ # The header contains:
46
+ # Version of the encryption key used to encrypt the file
47
+ # Indicator if the data was compressed
48
+ # Default: false
49
+ #
50
+ # :version
51
+ # Version of the encryption key to use when encrypting
52
+ # Default: Current primary key
53
+ #
54
+ # :mode
55
+ # See File.open for open modes
56
+ # Default: 'w'
57
+ #
58
+ # Note: Compression occurs before encryption
59
+ #
60
+ def self.open(filename_or_stream, options={}, &block)
61
+ raise "options must be a hash" unless options.respond_to?(:each_pair)
62
+ mode = options.fetch(:mode, 'w')
63
+ compress = options.fetch(:compress, false)
64
+ ios = filename_or_stream.is_a?(String) ? ::File.open(filename_or_stream, mode) : filename_or_stream
65
+
66
+ begin
67
+ file = self.new(ios, options)
68
+ file = Zlib::GzipWriter.new(file) if compress
69
+ block.call(file)
70
+ ensure
71
+ file.close if file
72
+ end
73
+ end
74
+
75
+ # Encrypt data before writing to the supplied stream
76
+ def initialize(ios,options={})
77
+ @ios = ios
78
+ header = options.fetch(:header, false)
79
+ # Compress is only used at this point for setting the flag in the header
80
+ @compress = options.fetch(:compress, false)
81
+
82
+ # Use primary cipher by default, but allow a secondary cipher to be selected for encryption
83
+ @cipher = SymmetricEncryption.cipher(options[:version])
84
+ raise "Cipher with version:#{options[:version]} not found in any of the configured SymmetricEncryption ciphers" unless @cipher
85
+
86
+ @stream_cipher = @cipher.send(:openssl_cipher, :encrypt)
87
+
88
+ write_header if header
89
+ end
90
+
91
+ # Close the IO Stream
92
+ # Flushes any unwritten data
93
+ #
94
+ # Note: Once an EncryptionWriter has been closed a new instance must be
95
+ # created before writing again
96
+ #
97
+ # Note: Also closes the passed in io stream or file
98
+ # Note: This method must be called _before_ the supplied stream is closed
99
+ #
100
+ # It is recommended to call Symmetric::EncryptedStream.open
101
+ # rather than creating an instance of Symmetric::EncryptedStream directly to
102
+ # ensure that the encrypted stream is closed before the stream itself is closed
103
+ def close(close_child_stream = true)
104
+ final = @stream_cipher.final
105
+ @ios.write(final) if final.length > 0
106
+ @ios.close if close_child_stream
107
+ end
108
+
109
+ # Write to the IO Stream as encrypted data
110
+ # Returns the number of bytes written
111
+ def write(data)
112
+ partial = @stream_cipher.update(data.to_s)
113
+ @ios.write(partial) if partial.length > 0
114
+ data.length
115
+ end
116
+
117
+ private
118
+
119
+ # Write the Encryption header if this is the first write
120
+ def write_header
121
+ # Include Header and encryption version indicator
122
+ flags = @cipher.version || 0 # Same as 0b0000_0000_0000_0000
123
+
124
+ # If the data is to be compressed before being encrypted, set the
125
+ # compressed bit in the version byte
126
+ flags |= 0b1000_0000_0000_0000 if @compress
127
+
128
+ @ios.write "#{MAGIC_HEADER}#{[flags].pack('v')}"
129
+ end
130
+
131
+ end
132
+ end
@@ -1,4 +1,17 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
2
  <project-private xmlns="http://www.netbeans.org/ns/project-private/1">
3
- <editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/1"/>
3
+ <editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/1">
4
+ <file>
5
+ <url>lib/symmetric_encryption/encryption.rb</url>
6
+ <line>60</line>
7
+ </file>
8
+ <file>
9
+ <url>lib/symmetric/encryption.rb</url>
10
+ <line>62</line>
11
+ </file>
12
+ <file>
13
+ <url>lib/symmetric_encryption/symmetric_encryption.rb</url>
14
+ <line>60</line>
15
+ </file>
16
+ </editor-bookmarks>
4
17
  </project-private>
Binary file
Binary file
@@ -27,12 +27,12 @@ class User < ActiveRecord::Base
27
27
  attr_encrypted :bank_account_number
28
28
  attr_encrypted :social_security_number
29
29
 
30
- validates :encrypted_bank_account_number, :symmetric_encrypted => true
31
- validates :encrypted_social_security_number, :symmetric_encrypted => true
30
+ validates :encrypted_bank_account_number, :symmetric_encryption => true
31
+ validates :encrypted_social_security_number, :symmetric_encryption => true
32
32
  end
33
33
 
34
34
  # Load Symmetric Encryption keys
35
- Symmetric::Encryption.load!(File.join(File.dirname(__FILE__), 'config', 'symmetric-encryption.yml'), 'test')
35
+ SymmetricEncryption.load!(File.join(File.dirname(__FILE__), 'config', 'symmetric-encryption.yml'), 'test')
36
36
 
37
37
  # Initialize the database connection
38
38
  config_file = File.join(File.dirname(__FILE__), 'config', 'database.yml')
@@ -44,10 +44,10 @@ raise("Environment 'test' not defined in test/config/database.yml") unless cfg
44
44
  User.establish_connection(cfg)
45
45
 
46
46
  #
47
- # Unit Test for attr_encrypted and validation aspects of Symmetric::Encryption
47
+ # Unit Test for attr_encrypted and validation aspects of SymmetricEncryption
48
48
  #
49
49
  class AttrEncryptedTest < Test::Unit::TestCase
50
- context 'the Symmetric::Encryption Library' do
50
+ context 'the SymmetricEncryption Library' do
51
51
 
52
52
  setup do
53
53
  @bank_account_number = "1234567890"
@@ -159,8 +159,8 @@ class AttrEncryptedTest < Test::Unit::TestCase
159
159
  assert_equal true, @user.valid?
160
160
  @user.encrypted_bank_account_number = '123'
161
161
  assert_equal false, @user.valid?
162
- assert_equal ["must be a value encrypted using Symmetric::Encryption.encrypt"], @user.errors[:encrypted_bank_account_number]
163
- @user.encrypted_bank_account_number = Symmetric::Encryption.encrypt('123')
162
+ assert_equal ["must be a value encrypted using SymmetricEncryption.encrypt"], @user.errors[:encrypted_bank_account_number]
163
+ @user.encrypted_bank_account_number = SymmetricEncryption.encrypt('123')
164
164
  assert_equal true, @user.valid?
165
165
  @user.bank_account_number = '123'
166
166
  assert_equal true, @user.valid?
data/test/cipher_test.rb CHANGED
@@ -4,15 +4,15 @@ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
4
4
  require 'rubygems'
5
5
  require 'test/unit'
6
6
  require 'shoulda'
7
- require 'symmetric/cipher'
7
+ require 'symmetric_encryption/cipher'
8
8
 
9
- # Unit Test for Symmetric::Cipher
9
+ # Unit Test for SymmetricEncryption::Cipher
10
10
  #
11
11
  class CipherTest < Test::Unit::TestCase
12
12
  context 'standalone' do
13
13
 
14
14
  should "allow setting the cipher" do
15
- cipher = Symmetric::Cipher.new(
15
+ cipher = SymmetricEncryption::Cipher.new(
16
16
  :cipher => 'aes-128-cbc',
17
17
  :key => '1234567890ABCDEF1234567890ABCDEF',
18
18
  :iv => '1234567890ABCDEF'
@@ -21,14 +21,14 @@ class CipherTest < Test::Unit::TestCase
21
21
  end
22
22
 
23
23
  should "not require an iv" do
24
- cipher = Symmetric::Cipher.new(
24
+ cipher = SymmetricEncryption::Cipher.new(
25
25
  :key => '1234567890ABCDEF1234567890ABCDEF'
26
26
  )
27
- assert_equal "wjzpl29q+tsxyLBWAQsn5g==\n", cipher.encrypt('Hello World')
27
+ assert_equal "\302<\351\227oj\372\3331\310\260V\001\v'\346", cipher.encrypt('Hello World')
28
28
  end
29
29
 
30
30
  should "throw an exception on bad data" do
31
- cipher = Symmetric::Cipher.new(
31
+ cipher = SymmetricEncryption::Cipher.new(
32
32
  :cipher => 'aes-128-cbc',
33
33
  :key => '1234567890ABCDEF1234567890ABCDEF',
34
34
  :iv => '1234567890ABCDEF'
@@ -42,12 +42,12 @@ class CipherTest < Test::Unit::TestCase
42
42
 
43
43
  context 'with configuration' do
44
44
  setup do
45
- @cipher = Symmetric::Cipher.new(
45
+ @cipher = SymmetricEncryption::Cipher.new(
46
46
  :key => '1234567890ABCDEF1234567890ABCDEF',
47
47
  :iv => '1234567890ABCDEF'
48
48
  )
49
49
  @social_security_number = "987654321"
50
- @social_security_number_encrypted = "Qd0qzN6oVuATJQBTf8X6tg==\n"
50
+ @social_security_number_encrypted = "A\335*\314\336\250V\340\023%\000S\177\305\372\266"
51
51
  @sample_data = [
52
52
  { :text => '555052345', :encrypted => ''}
53
53
  ]
@@ -65,10 +65,5 @@ class CipherTest < Test::Unit::TestCase
65
65
  assert_equal @social_security_number, @cipher.decrypt(@social_security_number_encrypted)
66
66
  end
67
67
 
68
- should "determine if string is encrypted" do
69
- assert_equal true, @cipher.encrypted?(@social_security_number_encrypted)
70
- assert_equal false, @cipher.encrypted?(@social_security_number)
71
- end
72
-
73
68
  end
74
69
  end
@@ -30,13 +30,13 @@ class MongoidUser
30
30
  end
31
31
 
32
32
  # Load Symmetric Encryption keys
33
- Symmetric::Encryption.load!(File.join(File.dirname(__FILE__), 'config', 'symmetric-encryption.yml'), 'test')
33
+ SymmetricEncryption.load!(File.join(File.dirname(__FILE__), 'config', 'symmetric-encryption.yml'), 'test')
34
34
 
35
35
  #
36
- # Unit Tests for field encrypted and validation aspects of Symmetric::Encryption
36
+ # Unit Tests for field encrypted and validation aspects of SymmetricEncryption
37
37
  #
38
38
  class FieldEncryptedTest < Test::Unit::TestCase
39
- context 'the Symmetric::Encryption Library' do
39
+ context 'the SymmetricEncryption Library' do
40
40
  setup do
41
41
  @bank_account_number = "1234567890"
42
42
  @bank_account_number_encrypted = "L94ArJeFlJrZp6SYsvoOGA==\n"
@@ -0,0 +1,76 @@
1
+ # Allow examples to be run in-place without requiring a gem install
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'rubygems'
5
+ require 'test/unit'
6
+ require 'shoulda'
7
+ require 'symmetric-encryption'
8
+
9
+ # Load Symmetric Encryption keys
10
+ SymmetricEncryption.load!(File.join(File.dirname(__FILE__), 'config', 'symmetric-encryption.yml'), 'test')
11
+
12
+ # Unit Test for SymmetricEncrypted::ReaderStream
13
+ #
14
+ class ReaderTest < Test::Unit::TestCase
15
+ context 'Reader' do
16
+ setup do
17
+ @data = [
18
+ "Hello World\n",
19
+ "Keep this secret\n",
20
+ "And keep going even further and further..."
21
+ ]
22
+ @data_str = @data.inject('') {|sum,str| sum << str}
23
+ @data_len = @data_str.length
24
+ @data_encrypted = SymmetricEncryption.cipher.encrypt(@data_str)
25
+ @filename = '._test'
26
+ end
27
+
28
+ should "decrypt from string stream as a single read" do
29
+ stream = StringIO.new(@data_encrypted)
30
+ decrypted = SymmetricEncryption::Reader.open(stream) {|file| file.read}
31
+ assert_equal @data_str, decrypted
32
+ end
33
+
34
+ should "decrypt from string stream as a single read, after a partial read" do
35
+ stream = StringIO.new(@data_encrypted)
36
+ decrypted = SymmetricEncryption::Reader.open(stream) do |file|
37
+ file.read(10)
38
+ file.read
39
+ end
40
+ assert_equal @data_str[10..-1], decrypted
41
+ end
42
+
43
+ should "decrypt lines from string stream" do
44
+ stream = StringIO.new(@data_encrypted)
45
+ i = 0
46
+ decrypted = SymmetricEncryption::Reader.open(stream) do |file|
47
+ file.each_line do |line|
48
+ assert_equal @data[i], line
49
+ i += 1
50
+ end
51
+ end
52
+ end
53
+
54
+ should "decrypt fixed lengths from string stream" do
55
+ stream = StringIO.new(@data_encrypted)
56
+ i = 0
57
+ SymmetricEncryption::Reader.open(stream) do |file|
58
+ index = 0
59
+ [0,10,5,5000].each do |size|
60
+ buf = file.read(size)
61
+ if size == 0
62
+ assert_equal '', buf
63
+ else
64
+ assert_equal @data_str[index..index+size-1], buf
65
+ end
66
+ index += size
67
+ end
68
+ end
69
+ end
70
+
71
+ should "decrypt from file" do
72
+
73
+ end
74
+ end
75
+
76
+ end
@@ -0,0 +1,53 @@
1
+ # Allow examples to be run in-place without requiring a gem install
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'rubygems'
5
+ require 'test/unit'
6
+ require 'shoulda'
7
+ require 'symmetric-encryption'
8
+
9
+ # Load Symmetric Encryption keys
10
+ SymmetricEncryption.load!(File.join(File.dirname(__FILE__), 'config', 'symmetric-encryption.yml'), 'test')
11
+
12
+ # Unit Test for SymmetricEncryption
13
+ #
14
+ class SymmetricEncryptionTest < Test::Unit::TestCase
15
+ context 'initialized' do
16
+
17
+ context 'SymmetricEncryption configuration' do
18
+ setup do
19
+ @config = SymmetricEncryption.send(:read_config, File.join(File.dirname(__FILE__), 'config', 'symmetric-encryption.yml'), 'test')
20
+ end
21
+
22
+ should "match config file" do
23
+ assert_equal @config[:ciphers][0][:cipher], SymmetricEncryption.cipher.cipher
24
+ end
25
+ end
26
+
27
+ context 'SymmetricEncryption tests' do
28
+ setup do
29
+ @social_security_number = "987654321"
30
+ @social_security_number_encrypted = "S+8X1NRrqdfEIQyFHVPuVA==\n"
31
+ @social_security_number_encrypted_with_secondary_1 = "D1UCu38pqJ3jc0GvwJHiow==\n"
32
+ end
33
+
34
+ should "encrypt simple string" do
35
+ assert_equal @social_security_number_encrypted, SymmetricEncryption.encrypt(@social_security_number)
36
+ end
37
+
38
+ should "decrypt string" do
39
+ assert_equal @social_security_number, SymmetricEncryption.decrypt(@social_security_number_encrypted)
40
+ end
41
+
42
+ should "determine if string is encrypted" do
43
+ assert_equal true, SymmetricEncryption.encrypted?(@social_security_number_encrypted)
44
+ assert_equal false, SymmetricEncryption.encrypted?(@social_security_number)
45
+ end
46
+
47
+ should "decrypt with secondary key when first one fails" do
48
+ assert_equal @social_security_number, SymmetricEncryption.decrypt(@social_security_number_encrypted)
49
+ end
50
+ end
51
+ end
52
+
53
+ end
@@ -0,0 +1,56 @@
1
+ # Allow examples to be run in-place without requiring a gem install
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'rubygems'
5
+ require 'test/unit'
6
+ require 'shoulda'
7
+ require 'symmetric-encryption'
8
+
9
+ # Load Symmetric Encryption keys
10
+ SymmetricEncryption.load!(File.join(File.dirname(__FILE__), 'config', 'symmetric-encryption.yml'), 'test')
11
+
12
+ # Unit Test for Symmetric::EncryptedStream
13
+ #
14
+ class EncryptionWriterTest < Test::Unit::TestCase
15
+ context 'EncryptionWriter' do
16
+ setup do
17
+ @data = [
18
+ "Hello World\n",
19
+ "Keep this secret\n",
20
+ "And keep going even further and further..."
21
+ ]
22
+ @data_str = @data.inject('') {|sum,str| sum << str}
23
+ @data_len = @data_str.length
24
+ @data_encrypted = SymmetricEncryption.cipher.encrypt(@data_str)
25
+ @filename = '._test'
26
+ end
27
+
28
+ should "encrypt to string stream" do
29
+ stream = StringIO.new
30
+ file = SymmetricEncryption::Writer.new(stream)
31
+ written_len = @data.inject(0) {|sum,str| sum + file.write(str)}
32
+ file.close
33
+
34
+ assert_equal @data_len, written_len
35
+ assert_equal @data_encrypted, stream.string
36
+ end
37
+
38
+ should "encrypt to string stream using .open" do
39
+ written_len = 0
40
+ stream = StringIO.new
41
+ SymmetricEncryption::Writer.open(stream) do |file|
42
+ written_len = @data.inject(0) {|sum,str| sum + file.write(str)}
43
+ end
44
+ assert_equal @data_len, written_len
45
+ end
46
+
47
+ should "encrypt to file using .open" do
48
+ written_len = nil
49
+ SymmetricEncryption::Writer.open(@filename) do |file|
50
+ written_len = @data.inject(0) {|sum,str| sum + file.write(str)}
51
+ end
52
+ assert_equal @data_len, written_len
53
+ assert_equal @data_encrypted, File.read(@filename)
54
+ end
55
+ end
56
+ end