Skip to content

Commit

Permalink
Improve thread safety of Symmetric cipher
Browse files Browse the repository at this point in the history
Previously using Symmetric encryption
from multiple threads could result in errors
from OpenSSL. This commit adds a mutex
to synchronize access to the OpenSSL
instance for encryption and decryption.
  • Loading branch information
micahlee committed Feb 10, 2023
1 parent 027de63 commit c9408a0
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 20 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# v3.0.1

* The symmetric cipher class now encrypts and decrypts in a thread-safe manner.
[cyberark/slosilo#31](https://github.com/cyberark/slosilo/pull/31)

# v3.0.0

* Transition to Ruby 3. Consuming projects based on Ruby 2 shall use slosilo V2.X.X.
Expand Down
43 changes: 26 additions & 17 deletions lib/slosilo/symmetric.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class Symmetric

def initialize
@cipher = OpenSSL::Cipher.new 'aes-256-gcm' # NB: has to be lower case for whatever reason.
@cipher_mutex = Mutex.new
end

# This lets us do a final sanity check in migrations from older encryption versions
Expand All @@ -13,34 +14,42 @@ def cipher_name
end

def encrypt plaintext, opts = {}
@cipher.reset
@cipher.encrypt
@cipher.key = (opts[:key] or raise("missing :key option"))
@cipher.iv = iv = random_iv
@cipher.auth_data = opts[:aad] || "" # Nothing good happens if you set this to nil, or don't set it at all
ctext = @cipher.update(plaintext) + @cipher.final
tag = @cipher.auth_tag(TAG_LENGTH)
"#{VERSION_MAGIC}#{tag}#{iv}#{ctext}"
# All of these operations in OpenSSL must occur atomically, so we
# synchronize their access to make this step thread-safe.
@cipher_mutex.synchronize do
@cipher.reset
@cipher.encrypt
@cipher.key = (opts[:key] or raise("missing :key option"))
@cipher.iv = iv = random_iv
@cipher.auth_data = opts[:aad] || "" # Nothing good happens if you set this to nil, or don't set it at all
ctext = @cipher.update(plaintext) + @cipher.final
tag = @cipher.auth_tag(TAG_LENGTH)
"#{VERSION_MAGIC}#{tag}#{iv}#{ctext}"
end
end

def decrypt ciphertext, opts = {}
version, tag, iv, ctext = unpack ciphertext

raise "Invalid version magic: expected #{VERSION_MAGIC} but was #{version}" unless version == VERSION_MAGIC

@cipher.reset
@cipher.decrypt
@cipher.key = opts[:key]
@cipher.iv = iv
@cipher.auth_tag = tag
@cipher.auth_data = opts[:aad] || ""
@cipher.update(ctext) + @cipher.final
# All of these operations in OpenSSL must occur atomically, so we
# synchronize their access to make this step thread-safe.
@cipher_mutex.synchronize do
@cipher.reset
@cipher.decrypt
@cipher.key = opts[:key]
@cipher.iv = iv
@cipher.auth_tag = tag
@cipher.auth_data = opts[:aad] || ""
@cipher.update(ctext) + @cipher.final
end
end

def random_iv
@cipher.random_iv
end

def random_key
@cipher.random_key
end
Expand Down
2 changes: 1 addition & 1 deletion lib/slosilo/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module Slosilo
VERSION = "3.0.0"
VERSION = "3.0.1"
end
25 changes: 23 additions & 2 deletions spec/symmetric_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,29 @@
expect(subject.encrypt(plaintext, key: key, aad: auth_data)).to eq(ciphertext)
end
end

describe '#decrypt' do

it "doesn't fail when called by multiple threads" do
threads = []

begin
# Verify we can successfuly decrypt using many threads without OpenSSL
# errors.
1000.times do
threads << Thread.new do
100.times do
expect(
subject.decrypt(ciphertext, key: key, aad: auth_data)
).to eq(plaintext)
end
end
end
ensure
threads.each(&:join)
end
end

it "decrypts with AES-256-GCM" do
expect(subject.decrypt(ciphertext, key: key, aad: auth_data)).to eq(plaintext)
end
Expand Down Expand Up @@ -56,7 +77,7 @@
end
end
end

describe '#random_iv' do
it "generates a random iv" do
expect_any_instance_of(OpenSSL::Cipher).to receive(:random_iv).and_return :iv
Expand Down

0 comments on commit c9408a0

Please sign in to comment.