symmetric-encryption 1.1.1 → 2.0.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.
- 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
|