symmetric-encryption 1.1.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +75 -23
- data/lib/rails/generators/symmetric_encryption/config/templates/symmetric-encryption.yml +7 -6
- data/lib/symmetric_encryption/cipher.rb +161 -126
- data/lib/symmetric_encryption/extensions/active_record/base.rb +36 -13
- data/lib/symmetric_encryption/extensions/mongoid/fields.rb +23 -12
- data/lib/symmetric_encryption/railtie.rb +4 -4
- data/lib/symmetric_encryption/reader.rb +7 -5
- data/lib/symmetric_encryption/symmetric_encryption.rb +54 -24
- data/lib/symmetric_encryption/version.rb +1 -1
- data/lib/symmetric_encryption/writer.rb +61 -15
- data/test/attr_encrypted_test.rb +30 -0
- data/test/cipher_test.rb +14 -13
- data/test/config/symmetric-encryption.yml +2 -2
- data/test/field_encrypted_test.rb +28 -0
- data/test/reader_test.rb +72 -38
- data/test/symmetric_encryption_test.rb +25 -5
- data/test/test_db.sqlite3 +0 -0
- data/test/writer_test.rb +3 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 32af889e864031974d67cc49ed09fca4fc6a9ceb
|
4
|
+
data.tar.gz: be8ef6b6193bd44dbf4b3c760b31e9223f0e4ce1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cfdba0c992f3f82765ab3592a386cdceb9eb81265d0f6fef31cd324d02d1d4d21bbd8bf51032b0e3a01f4b0afd2e885f94a6976fab427b8c617c83db554f5e71
|
7
|
+
data.tar.gz: 7dd486b715dcd74ac6ae88451a39e12c61f1b2b525e62f1e52a0e18dfe0a485b3f853ff37ac6f183a30b20383c6cc7410a8cbc6fa9e0fdbcef129b9f188a0697
|
data/README.md
CHANGED
@@ -50,22 +50,53 @@ From a security perspective it is important then to properly secure the system s
|
|
50
50
|
no hacker can switch to and run as the rails user and thereby gain access to the
|
51
51
|
encryption and decryption capabilities
|
52
52
|
|
53
|
+
## Limitations
|
54
|
+
|
55
|
+
By default symmetric encryption uses the same initialization vector (IV) and
|
56
|
+
encryption key to encrypt data using the SymmetricEncryption.encrypt call.
|
57
|
+
This technique is required in cases where the encrypted data is used as a key
|
58
|
+
to lookup for example a Social Security Number, since for the same input data it
|
59
|
+
must always return the same encrypted result. The drawback is that this
|
60
|
+
technique is not considered secure when encypting large amounts of data.
|
61
|
+
|
62
|
+
For non-key fields, such as storing encrypted raw responses,
|
63
|
+
use the :random_iv => true option where possible so that a
|
64
|
+
randomly generated IV is used and included in every encrypted string.
|
65
|
+
|
66
|
+
The Symmetric Encryption streaming interface SymmetricEncryption::Writer avoids this
|
67
|
+
problem by using a random IV and key in every file/stream by default.
|
68
|
+
The random IV and key are stored in the header of the output stream so that it
|
69
|
+
is available when reading back the encrypted file/stream.
|
70
|
+
|
71
|
+
The ActiveRecord attr_encrypted method supports the :random_iv => true option.
|
72
|
+
Similarly for Mongoid the :random_iv => true option can be added.
|
73
|
+
|
74
|
+
Note that encrypting the same input string with the same key and :random_iv => true
|
75
|
+
option will result in different encrypted output every time it is encrypted.
|
76
|
+
|
53
77
|
## Features
|
54
78
|
|
55
79
|
* Encryption of passwords in configuration files
|
56
80
|
* Encryption of ActiveRecord model attributes by prefixing attributes / column
|
57
81
|
names with encrypted_
|
82
|
+
* Encryption of Mongoid model fields by adding :encrypted => true to field
|
83
|
+
definitions
|
58
84
|
* Externalization of symmetric encryption keys so that they are not in the
|
59
85
|
source code, or the source code control system
|
60
|
-
*
|
61
|
-
* Compatible with the default Encryption algorithm in attr_encrypted
|
62
|
-
* More efficient replacement for attr_encrypted since only ActiveRecord Models
|
63
|
-
are extended with encrypted_ behavior, rather than every object in the system
|
64
|
-
* Custom validator for ActiveRecord Models
|
86
|
+
* Validator for ActiveRecord Models to ensure fields contain encrypted data
|
65
87
|
* Stream based encryption and decryption so that large files can be read or
|
66
|
-
written with encryption
|
88
|
+
written with encryption, along with a random key and IV for every file
|
67
89
|
* Stream based encryption and decryption also supports compression and decompression
|
68
90
|
on the fly
|
91
|
+
* When :compress => true option is specified Symmetric Encryption will transparently
|
92
|
+
compress the data prior to decryption. When decrypting compressed data Symmetric
|
93
|
+
Encryption will transparently decompress the data after decryption based on the
|
94
|
+
header stored in the encrypted data
|
95
|
+
* Uses built-in support in Ruby for OpenSSL and Zlib for high performance and
|
96
|
+
maximum portability without introducing any additional dependencies
|
97
|
+
* Drop in replacement for attr_encrypted. Just remove the attr_encrypted gem
|
98
|
+
* For maximum security supports fully random keys and initialization vectors
|
99
|
+
extracted from the entire encryption key space
|
69
100
|
|
70
101
|
## Examples
|
71
102
|
|
@@ -215,9 +246,9 @@ Before generating keys we can use SymmetricEncryption in a standalone test envir
|
|
215
246
|
```ruby
|
216
247
|
# Use test encryption keys
|
217
248
|
SymmetricEncryption.cipher = SymmetricEncryption::Cipher.new(
|
218
|
-
:key
|
219
|
-
:iv
|
220
|
-
:
|
249
|
+
:key => '1234567890ABCDEF1234567890ABCDEF',
|
250
|
+
:iv => '1234567890ABCDEF',
|
251
|
+
:cipher_name => 'aes-128-cbc'
|
221
252
|
)
|
222
253
|
encrypted = SymmetricEncryption.encrypt('hello world')
|
223
254
|
puts SymmetricEncryption.decrypt(encrypted)
|
@@ -408,7 +439,7 @@ Create a configuration file in config/symmetric-encryption.yml per the following
|
|
408
439
|
development: &development_defaults
|
409
440
|
key: 1234567890ABCDEF1234567890ABCDEF
|
410
441
|
iv: 1234567890ABCDEF
|
411
|
-
|
442
|
+
cipher_name: aes-128-cbc
|
412
443
|
|
413
444
|
test:
|
414
445
|
<<: *development_defaults
|
@@ -457,7 +488,7 @@ production:
|
|
457
488
|
key_filename: /etc/rails/.rails.key
|
458
489
|
iv_filename: /etc/rails/.rails.iv
|
459
490
|
|
460
|
-
# Encryption
|
491
|
+
# Encryption cipher_name
|
461
492
|
# Recommended values:
|
462
493
|
# aes-256-cbc
|
463
494
|
# 256 AES CBC Algorithm. Very strong
|
@@ -467,7 +498,7 @@ production:
|
|
467
498
|
# 128 AES CBC Algorithm. Less strong.
|
468
499
|
# Ruby 1.8.7 MRI Approximately 100,000 encryptions or decryptions per second
|
469
500
|
# JRuby 1.6.7 with Ruby 1.8.7 Approximately 22,000 encryptions or decryptions per second
|
470
|
-
|
501
|
+
cipher_name: aes-256-cbc
|
471
502
|
|
472
503
|
-
|
473
504
|
# OPTIONAL:
|
@@ -478,27 +509,48 @@ production:
|
|
478
509
|
# to be used
|
479
510
|
key_filename: /etc/rails/.rails_old.key
|
480
511
|
iv_filename: /etc/rails/.rails_old.iv
|
481
|
-
|
512
|
+
cipher_name: aes-256-cbc
|
482
513
|
```
|
483
514
|
|
484
|
-
##
|
515
|
+
## New features in V1.1 and V2
|
485
516
|
|
486
517
|
* Ability to randomly generate a new initialization vector (iv) with every
|
487
|
-
encryption and put the iv in the encrypted data as its header
|
518
|
+
encryption and put the iv in the encrypted data as its header, without having
|
519
|
+
to use SymmetricEncryption::Writer
|
488
520
|
|
489
521
|
* With file encryption randomly generate a new key and initialization vector (iv) with every
|
490
522
|
file encryption and put the key and iv in the encrypted data as its header which
|
491
523
|
is encrypted using the global key and iv
|
492
524
|
|
493
|
-
|
494
|
-
|
495
|
-
* Ability to entirely disable encryption for a specific environment.
|
496
|
-
SymmetricEncryption.encrypt() would return the supplied data without encrypting it and
|
497
|
-
SymmetricEncryption.decrypt() would return the supplied data without decrypting it
|
525
|
+
* Support for compression via SymmetricEncryption.encrypt, attr_encrypted and Mongoid
|
526
|
+
fields
|
498
527
|
|
499
|
-
*
|
500
|
-
|
501
|
-
|
528
|
+
* SymmetricEncryption.encrypt has two additional optional parameters:
|
529
|
+
```
|
530
|
+
random_iv [true|false]
|
531
|
+
Whether the encypted value should use a random IV every time the
|
532
|
+
field is encrypted.
|
533
|
+
It is recommended to set this to true where feasible. If the encrypted
|
534
|
+
value could be used as part of a SQL where clause, or as part
|
535
|
+
of any lookup, then it must be false.
|
536
|
+
Setting random_iv to true will result in a different encrypted output for
|
537
|
+
the same input string.
|
538
|
+
Note: Only set to true if the field will never be used as part of
|
539
|
+
the where clause in an SQL query.
|
540
|
+
Note: When random_iv is true it will add a 8 byte header, plus the bytes
|
541
|
+
to store the random IV in every returned encrypted string, prior to the
|
542
|
+
encoding if any.
|
543
|
+
Default: false
|
544
|
+
Highly Recommended where feasible: true
|
545
|
+
|
546
|
+
compress [true|false]
|
547
|
+
Whether to compress str before encryption
|
548
|
+
Should only be used for large strings since compression overhead and
|
549
|
+
the overhead of adding the 'magic' header may exceed any benefits of
|
550
|
+
compression
|
551
|
+
Note: Adds a 6 byte header prior to encoding, only if :random_iv is false
|
552
|
+
Default: false
|
553
|
+
```
|
502
554
|
|
503
555
|
Meta
|
504
556
|
----
|
@@ -5,10 +5,11 @@
|
|
5
5
|
# For the development and test environments the test symmetric encryption keys
|
6
6
|
# can be placed directly in the source code.
|
7
7
|
# And therefore no RSA private key is required
|
8
|
-
development:
|
9
|
-
key:
|
10
|
-
iv:
|
11
|
-
|
8
|
+
development: &development_defaults
|
9
|
+
key: 1234567890ABCDEF1234567890ABCDEF
|
10
|
+
iv: 1234567890ABCDEF
|
11
|
+
cipher_name: aes-128-cbc
|
12
|
+
encoding: :base64strict
|
12
13
|
|
13
14
|
test:
|
14
15
|
<<: *development_defaults
|
@@ -27,7 +28,7 @@ release:
|
|
27
28
|
# RSA public key derived from the private key above
|
28
29
|
key_filename: <%= File.join(key_path, "#{app_name}_release.key") %>
|
29
30
|
iv_filename: <%= File.join(key_path, "#{app_name}_release.iv") %>
|
30
|
-
|
31
|
+
cipher_name: aes-256-cbc
|
31
32
|
# Base64 encode encrypted data without newlines
|
32
33
|
encoding: :base64strict
|
33
34
|
version: 1
|
@@ -46,7 +47,7 @@ production:
|
|
46
47
|
# RSA public key derived from the private key above
|
47
48
|
key_filename: <%= File.join(key_path, "#{app_name}_production.key") %>
|
48
49
|
iv_filename: <%= File.join(key_path, "#{app_name}_production.iv") %>
|
49
|
-
|
50
|
+
cipher_name: aes-256-cbc
|
50
51
|
# Base64 encode encrypted data without newlines
|
51
52
|
encoding: :base64strict
|
52
53
|
version: 1
|
@@ -7,39 +7,31 @@ module SymmetricEncryption
|
|
7
7
|
# threads at the same time without needing an instance of Cipher per thread
|
8
8
|
class Cipher
|
9
9
|
# Cipher to use for encryption and decryption
|
10
|
-
attr_reader :
|
10
|
+
attr_reader :cipher_name, :version
|
11
11
|
attr_accessor :encoding
|
12
12
|
|
13
13
|
# Available encodings
|
14
14
|
ENCODINGS = [:none, :base64, :base64strict, :base16]
|
15
15
|
|
16
|
+
# Backward compatibility
|
17
|
+
alias_method :cipher, :cipher_name
|
18
|
+
|
16
19
|
# Generate a new Symmetric Key pair
|
17
20
|
#
|
18
21
|
# Returns a hash containing a new random symmetric_key pair
|
19
22
|
# consisting of a :key and :iv.
|
20
|
-
# The
|
21
|
-
def self.random_key_pair(
|
22
|
-
openssl_cipher = OpenSSL::Cipher.new(
|
23
|
+
# The cipher_name is also included for compatibility with the Cipher initializer
|
24
|
+
def self.random_key_pair(cipher_name = 'aes-256-cbc', generate_iv = true)
|
25
|
+
openssl_cipher = ::OpenSSL::Cipher.new(cipher_name)
|
23
26
|
openssl_cipher.encrypt
|
24
27
|
|
25
28
|
{
|
26
|
-
:key
|
27
|
-
:iv
|
28
|
-
:
|
29
|
+
:key => openssl_cipher.random_key,
|
30
|
+
:iv => generate_iv ? openssl_cipher.random_iv : nil,
|
31
|
+
:cipher_name => cipher_name
|
29
32
|
}
|
30
33
|
end
|
31
34
|
|
32
|
-
# Returns a new Cipher with a random key and iv
|
33
|
-
#
|
34
|
-
# The cipher and encoding used are from the global encryption cipher
|
35
|
-
#
|
36
|
-
def self.random_cipher(cipher=nil, encoding=nil)
|
37
|
-
global_cipher = SymmetricEncryption.cipher
|
38
|
-
options = random_key_pair(cipher || global_cipher.cipher)
|
39
|
-
options[:encoding] = encoding || global_cipher.encoding
|
40
|
-
new(options)
|
41
|
-
end
|
42
|
-
|
43
35
|
# Create a Symmetric::Key for encryption and decryption purposes
|
44
36
|
#
|
45
37
|
# Parameters:
|
@@ -50,7 +42,7 @@ module SymmetricEncryption
|
|
50
42
|
# Optional. The Initialization Vector to use with Symmetric Key
|
51
43
|
# Highly Recommended as it is the input into the CBC algorithm
|
52
44
|
#
|
53
|
-
# :
|
45
|
+
# :cipher_name [String]
|
54
46
|
# Optional. Encryption Cipher to use
|
55
47
|
# Default: aes-256-cbc
|
56
48
|
#
|
@@ -76,7 +68,7 @@ module SymmetricEncryption
|
|
76
68
|
def initialize(parms={})
|
77
69
|
raise "Missing mandatory parameter :key" unless @key = parms[:key]
|
78
70
|
@iv = parms[:iv]
|
79
|
-
@
|
71
|
+
@cipher_name = parms[:cipher_name] || parms[:cipher] || 'aes-256-cbc'
|
80
72
|
@version = parms[:version]
|
81
73
|
raise "Cipher version has a maximum of 255. #{@version} is too high" if @version.to_i > 255
|
82
74
|
@encoding = (parms[:encoding] || :base64).to_sym
|
@@ -84,28 +76,45 @@ module SymmetricEncryption
|
|
84
76
|
raise("Invalid Encoding: #{@encoding}") unless ENCODINGS.include?(@encoding)
|
85
77
|
end
|
86
78
|
|
87
|
-
#
|
88
|
-
#
|
79
|
+
# Returns encrypted and then encoded string
|
80
|
+
# Returns nil if str is nil
|
81
|
+
# Returns "" str is empty
|
89
82
|
#
|
90
|
-
#
|
91
|
-
#
|
92
|
-
#
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
83
|
+
# Parameters
|
84
|
+
#
|
85
|
+
# str [String]
|
86
|
+
# String to be encrypted. If str is not a string, #to_s will be called on it
|
87
|
+
# to convert it to a string
|
88
|
+
#
|
89
|
+
# random_iv [true|false]
|
90
|
+
# Whether the encypted value should use a random IV every time the
|
91
|
+
# field is encrypted.
|
92
|
+
# It is recommended to set this to true where feasible. If the encrypted
|
93
|
+
# value could be used as part of a SQL where clause, or as part
|
94
|
+
# of any lookup, then it must be false.
|
95
|
+
# Setting random_iv to true will result in a different encrypted output for
|
96
|
+
# the same input string.
|
97
|
+
# Note: Only set to true if the field will never be used as part of
|
98
|
+
# the where clause in an SQL query.
|
99
|
+
# Note: When random_iv is true it will add a 8 byte header, plus the bytes
|
100
|
+
# to store the random IV in every returned encrypted string, prior to the
|
101
|
+
# encoding if any.
|
102
|
+
# Default: false
|
103
|
+
# Highly Recommended where feasible: true
|
104
|
+
#
|
105
|
+
# compress [true|false]
|
106
|
+
# Whether to compress str before encryption
|
107
|
+
# Should only be used for large strings since compression overhead and
|
108
|
+
# the overhead of adding the 'magic' header may exceed any benefits of
|
109
|
+
# compression
|
110
|
+
# Note: Adds a 6 byte header prior to encoding, only if :random_iv is false
|
111
|
+
# Default: false
|
112
|
+
def encrypt(str, random_iv=false, compress=false)
|
113
|
+
return if str.nil?
|
114
|
+
str = str.to_s
|
115
|
+
return str if str.empty?
|
116
|
+
encrypted = binary_encrypt(str, random_iv, compress)
|
117
|
+
self.encode(encrypted)
|
109
118
|
end
|
110
119
|
|
111
120
|
# Decryption of supplied string
|
@@ -116,25 +125,16 @@ module SymmetricEncryption
|
|
116
125
|
# Returns nil if the supplied str is nil
|
117
126
|
# Returns "" if it is a string and it is empty
|
118
127
|
if defined?(Encoding)
|
119
|
-
def decrypt(str
|
120
|
-
decoded = self.decode(str)
|
121
|
-
return unless decoded
|
122
|
-
|
123
|
-
return decoded if decoded.empty?
|
124
|
-
crypt(:decrypt, decoded).force_encoding(SymmetricEncryption::UTF8_ENCODING)
|
125
|
-
end
|
126
|
-
|
127
|
-
# Returns a binary decrypted string
|
128
|
-
def decrypt_binary(str, decode = true)
|
129
|
-
decoded = self.decode(str) if decode
|
128
|
+
def decrypt(str)
|
129
|
+
decoded = self.decode(str)
|
130
130
|
return unless decoded
|
131
131
|
|
132
132
|
return decoded if decoded.empty?
|
133
|
-
|
133
|
+
binary_decrypt(decoded).force_encoding(SymmetricEncryption::UTF8_ENCODING)
|
134
134
|
end
|
135
135
|
else
|
136
|
-
def decrypt(str
|
137
|
-
decoded = self.decode(str)
|
136
|
+
def decrypt(str)
|
137
|
+
decoded = self.decode(str)
|
138
138
|
return unless decoded
|
139
139
|
|
140
140
|
return decoded if decoded.empty?
|
@@ -142,15 +142,15 @@ module SymmetricEncryption
|
|
142
142
|
end
|
143
143
|
end
|
144
144
|
|
145
|
-
# Return a new random key using the configured
|
145
|
+
# Return a new random key using the configured cipher_name
|
146
146
|
# Useful for generating new symmetric keys
|
147
147
|
def random_key
|
148
|
-
::OpenSSL::Cipher::Cipher.new(@
|
148
|
+
::OpenSSL::Cipher::Cipher.new(@cipher_name).random_key
|
149
149
|
end
|
150
150
|
|
151
|
-
# Returns the block size for the configured
|
151
|
+
# Returns the block size for the configured cipher_name
|
152
152
|
def block_size
|
153
|
-
::OpenSSL::Cipher::Cipher.new(@
|
153
|
+
::OpenSSL::Cipher::Cipher.new(@cipher_name).block_size
|
154
154
|
end
|
155
155
|
|
156
156
|
# Returns UTF8 encoded string after encoding the supplied Binary string
|
@@ -193,12 +193,13 @@ module SymmetricEncryption
|
|
193
193
|
end
|
194
194
|
end
|
195
195
|
|
196
|
-
# Returns an Array
|
197
|
-
#
|
198
|
-
#
|
199
|
-
#
|
200
|
-
#
|
201
|
-
#
|
196
|
+
# Returns an Array of the following values extracted from header or nil
|
197
|
+
# if any value was not specified in the header
|
198
|
+
# compressed [true|false]
|
199
|
+
# iv [String]
|
200
|
+
# key [String]
|
201
|
+
# cipher_name [String}
|
202
|
+
# decryption_cipher [SymmetricEncryption::Cipher]
|
202
203
|
#
|
203
204
|
# The supplied buffer will be updated directly and will have the header
|
204
205
|
# portion removed
|
@@ -215,19 +216,21 @@ module SymmetricEncryption
|
|
215
216
|
# If no header is present, this is the default value for the compression
|
216
217
|
def self.parse_magic_header!(buffer, default_version=nil, default_compressed=false)
|
217
218
|
buffer.force_encoding(SymmetricEncryption::BINARY_ENCODING)
|
218
|
-
return [SymmetricEncryption.cipher(default_version)
|
219
|
+
return [default_compressed, nil, nil, nil, SymmetricEncryption.cipher(default_version)] unless buffer.start_with?(MAGIC_HEADER)
|
219
220
|
|
220
221
|
# Header includes magic header and version byte
|
221
222
|
# Remove header and extract flags
|
222
|
-
|
223
|
+
_, flags = buffer.slice!(0..MAGIC_HEADER_SIZE+1).unpack(MAGIC_HEADER_UNPACK)
|
223
224
|
compressed = (flags & 0b1000_0000_0000_0000) != 0
|
224
225
|
include_iv = (flags & 0b0100_0000_0000_0000) != 0
|
225
226
|
include_key = (flags & 0b0010_0000_0000_0000) != 0
|
226
227
|
include_cipher= (flags & 0b0001_0000_0000_0000) != 0
|
228
|
+
# Version of the key to use to decrypt the key if present,
|
229
|
+
# otherwise to decrypt the data following the header
|
227
230
|
version = flags & 0b0000_0000_1111_1111
|
228
231
|
decryption_cipher = SymmetricEncryption.cipher(version)
|
229
232
|
raise "Cipher with version:#{version.inspect} not found in any of the configured SymmetricEncryption ciphers" unless decryption_cipher
|
230
|
-
iv, key,
|
233
|
+
iv, key, cipher_name = nil
|
231
234
|
|
232
235
|
if include_iv
|
233
236
|
len = buffer.slice!(0..1).unpack('v').first
|
@@ -235,22 +238,14 @@ module SymmetricEncryption
|
|
235
238
|
end
|
236
239
|
if include_key
|
237
240
|
len = buffer.slice!(0..1).unpack('v').first
|
238
|
-
key = decryption_cipher.
|
241
|
+
key = decryption_cipher.binary_decrypt(buffer.slice!(0..len-1))
|
239
242
|
end
|
240
243
|
if include_cipher
|
241
244
|
len = buffer.slice!(0..1).unpack('v').first
|
242
|
-
|
243
|
-
end
|
244
|
-
|
245
|
-
if iv || key || cipher
|
246
|
-
decryption_cipher = SymmetricEncryption::Cipher.new(
|
247
|
-
:iv => iv,
|
248
|
-
:key => key || decryption_cipher.key,
|
249
|
-
:cipher => cipher || decryption_cipher.cipher
|
250
|
-
)
|
245
|
+
cipher_name = buffer.slice!(0..len-1)
|
251
246
|
end
|
252
247
|
|
253
|
-
[
|
248
|
+
[compressed, iv, key, cipher_name, decryption_cipher]
|
254
249
|
end
|
255
250
|
|
256
251
|
# Returns a magic header for this cipher instance that can be placed at
|
@@ -259,85 +254,125 @@ module SymmetricEncryption
|
|
259
254
|
# Parameters
|
260
255
|
# compressed
|
261
256
|
# Sets the compressed indicator in the header
|
257
|
+
# Default: false
|
262
258
|
#
|
263
|
-
#
|
264
|
-
#
|
265
|
-
#
|
259
|
+
# iv
|
260
|
+
# The iv to to put in the header
|
261
|
+
# Default: nil : Exclude from header
|
266
262
|
#
|
267
|
-
#
|
268
|
-
#
|
263
|
+
# key
|
264
|
+
# The key to to put in the header
|
269
265
|
# The key is encrypted using the global encryption key
|
266
|
+
# Default: nil : Exclude key from header
|
270
267
|
#
|
271
|
-
#
|
272
|
-
# Includes the
|
273
|
-
#
|
274
|
-
#
|
275
|
-
|
276
|
-
# When supplied, the version is set to it's version so that decryption
|
277
|
-
# knows which cipher to use
|
278
|
-
# Default: Global cipher: SymmetricEncryption.cipher
|
279
|
-
def magic_header(compressed=false, include_iv=false, include_key=false, include_cipher=false, encryption_cipher=nil)
|
268
|
+
# cipher_name
|
269
|
+
# Includes the cipher_name used. For example 'aes-256-cbc'
|
270
|
+
# The cipher_name string to to put in the header
|
271
|
+
# Default: nil : Exclude cipher_name name from header
|
272
|
+
def self.magic_header(version, compressed=false, iv=nil, key=nil, cipher_name=nil)
|
280
273
|
# Ruby V2 named parameters would be perfect here
|
281
274
|
|
282
275
|
# Encryption version indicator if available
|
283
276
|
flags = version || 0 # Same as 0b0000_0000_0000_0000
|
284
277
|
|
285
|
-
# Replace version with cipher used to encrypt
|
286
|
-
if
|
287
|
-
|
288
|
-
flags = (encryption_cipher.version || 0)
|
278
|
+
# Replace version with global cipher that will be used to encrypt the random key
|
279
|
+
if iv || key
|
280
|
+
flags = (SymmetricEncryption.cipher.version || 0)
|
289
281
|
end
|
290
282
|
|
291
283
|
# If the data is to be compressed before being encrypted, set the
|
292
284
|
# compressed bit in the flags word
|
293
285
|
flags |= 0b1000_0000_0000_0000 if compressed
|
294
|
-
flags |= 0b0100_0000_0000_0000 if
|
295
|
-
flags |= 0b0010_0000_0000_0000 if
|
296
|
-
flags |= 0b0001_0000_0000_0000 if
|
286
|
+
flags |= 0b0100_0000_0000_0000 if iv
|
287
|
+
flags |= 0b0010_0000_0000_0000 if key
|
288
|
+
flags |= 0b0001_0000_0000_0000 if cipher_name
|
297
289
|
header = "#{MAGIC_HEADER}#{[flags].pack('v')}".force_encoding(SymmetricEncryption::BINARY_ENCODING)
|
298
|
-
if
|
299
|
-
header << [
|
300
|
-
header <<
|
290
|
+
if iv
|
291
|
+
header << [iv.length].pack('v')
|
292
|
+
header << iv
|
301
293
|
end
|
302
|
-
if
|
303
|
-
encrypted =
|
294
|
+
if key
|
295
|
+
encrypted = SymmetricEncryption.cipher.binary_encrypt(key, false, false)
|
304
296
|
header << [encrypted.length].pack('v').force_encoding(SymmetricEncryption::BINARY_ENCODING)
|
305
297
|
header << encrypted
|
306
298
|
end
|
307
|
-
if
|
308
|
-
header << [
|
309
|
-
header <<
|
299
|
+
if cipher_name
|
300
|
+
header << [cipher_name.length].pack('v')
|
301
|
+
header << cipher_name
|
310
302
|
end
|
311
303
|
header
|
312
304
|
end
|
313
305
|
|
314
|
-
|
315
|
-
|
316
|
-
#
|
317
|
-
|
318
|
-
|
319
|
-
|
306
|
+
# Advanced use only
|
307
|
+
#
|
308
|
+
# Returns a Binary encrypted string without applying any Base64, or other encoding
|
309
|
+
#
|
310
|
+
# Adds the 'magic' header if a random_iv is required or compression is enabled
|
311
|
+
#
|
312
|
+
# Creates a new OpenSSL::Cipher with every call so that this call
|
313
|
+
# is thread-safe
|
314
|
+
#
|
315
|
+
# See #encrypt to encrypt and encode the result as a string
|
316
|
+
def binary_encrypt(string, random_iv=false, compress=false)
|
317
|
+
openssl_cipher = ::OpenSSL::Cipher.new(self.cipher_name)
|
318
|
+
openssl_cipher.encrypt
|
320
319
|
openssl_cipher.key = @key
|
321
|
-
|
322
|
-
|
320
|
+
result = if random_iv || compress
|
321
|
+
# Random iv and compress both add the magic header
|
322
|
+
iv = random_iv ? openssl_cipher.random_iv : @iv
|
323
|
+
openssl_cipher.iv = iv if iv
|
324
|
+
self.class.magic_header(version, compress, random_iv ? iv : nil) +
|
325
|
+
openssl_cipher.update(compress ? Zlib::Deflate.deflate(string) : string)
|
326
|
+
else
|
327
|
+
openssl_cipher.iv = @iv if @iv
|
328
|
+
openssl_cipher.update(string)
|
329
|
+
end
|
330
|
+
result << openssl_cipher.final
|
323
331
|
end
|
324
332
|
|
333
|
+
# Advanced use only
|
334
|
+
#
|
335
|
+
# Returns a Binary decrypted string without decoding the string first
|
336
|
+
#
|
337
|
+
# Reads the 'magic' header if present for key, iv, cipher_name and compression
|
338
|
+
#
|
339
|
+
# encrypted_string must be in raw binary form when calling this method
|
340
|
+
#
|
325
341
|
# Creates a new OpenSSL::Cipher with every call so that this call
|
326
342
|
# is thread-safe
|
327
|
-
#
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
343
|
+
#
|
344
|
+
# See #decrypt to decrypt encoded strings
|
345
|
+
def binary_decrypt(encrypted_string)
|
346
|
+
str = encrypted_string.to_s
|
347
|
+
if str.start_with?(MAGIC_HEADER)
|
348
|
+
str = str.dup
|
349
|
+
compressed, iv, key, cipher_name = self.class.parse_magic_header!(str)
|
350
|
+
openssl_cipher = ::OpenSSL::Cipher.new(cipher_name || self.cipher_name)
|
351
|
+
openssl_cipher.decrypt
|
352
|
+
openssl_cipher.key = key || @key
|
353
|
+
iv ||= @iv
|
354
|
+
openssl_cipher.iv = iv if iv
|
355
|
+
result = openssl_cipher.update(str)
|
356
|
+
result << openssl_cipher.final
|
357
|
+
compressed ? Zlib::Inflate.inflate(result) : result
|
358
|
+
else
|
359
|
+
openssl_cipher = ::OpenSSL::Cipher.new(self.cipher_name)
|
360
|
+
openssl_cipher.decrypt
|
361
|
+
openssl_cipher.key = @key
|
362
|
+
openssl_cipher.iv = @iv if @iv
|
363
|
+
result = openssl_cipher.update(encrypted_string)
|
364
|
+
result << openssl_cipher.final
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
# Returns [String] object represented as a string
|
369
|
+
# Excluding the key and iv
|
370
|
+
def inspect
|
371
|
+
"#<#{self.class}:0x#{self.__id__.to_s(16)} @cipher_name=#{cipher_name.inspect}, @version=#{version.inspect}, @encoding=#{encoding.inspect}"
|
336
372
|
end
|
337
373
|
|
338
374
|
private
|
339
375
|
|
340
376
|
attr_reader :key, :iv
|
341
|
-
|
342
377
|
end
|
343
378
|
end
|