diff --git a/config/settings.rb b/config/settings.rb
index 3f3481bb65d191336715bdd7ceccb6b998b568ff..1b94df785a76c5be663c58b5e394887d0a2e6588 100644
--- a/config/settings.rb
+++ b/config/settings.rb
@@ -95,6 +95,14 @@ def attr_encrypted_db_key_base_truncated
       Gitlab::Application.secrets.db_key_base[0..31]
     end
 
+    def attr_encrypted_db_key_base_32
+      Gitlab::Utils.ensure_utf8_size(attr_encrypted_db_key_base, bytes: 32.bytes)
+    end
+
+    def attr_encrypted_db_key_base_12
+      Gitlab::Utils.ensure_utf8_size(attr_encrypted_db_key_base, bytes: 12.bytes)
+    end
+
     # This should be used for :per_attribute_salt_and_iv mode. There is no
     # need to truncate the key because the encryptor will use the salt to
     # generate a hash of the password:
diff --git a/lib/gitlab/crypto_helper.rb b/lib/gitlab/crypto_helper.rb
index e85753c3a127aaef7aeddc43b455190c1c172ee3..87a03d9c58fe7f5a10591d599a0c82be69d83f9d 100644
--- a/lib/gitlab/crypto_helper.rb
+++ b/lib/gitlab/crypto_helper.rb
@@ -6,8 +6,8 @@ module CryptoHelper
 
     AES256_GCM_OPTIONS = {
       algorithm: 'aes-256-gcm',
-      key: Settings.attr_encrypted_db_key_base_truncated,
-      iv: Settings.attr_encrypted_db_key_base_truncated[0..11]
+      key: Settings.attr_encrypted_db_key_base_32,
+      iv: Settings.attr_encrypted_db_key_base_12
     }.freeze
 
     def sha256(value)
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
index 9e59137a2c0f918d935dc98843a72bc439daba48..ad2efa6b4e1e6e781d1bd1bd91f0090c7b89d26c 100644
--- a/lib/gitlab/utils.rb
+++ b/lib/gitlab/utils.rb
@@ -16,6 +16,20 @@ def force_utf8(str)
       str.force_encoding(Encoding::UTF_8)
     end
 
+    def ensure_utf8_size(str, bytes:)
+      raise ArgumentError if str.empty? || bytes.negative?
+
+      truncated = str.each_char.each_with_object(String.new) do |char, object|
+        if object.bytesize + char.bytesize > bytes
+          break object
+        else
+          object.concat(char)
+        end
+      end
+
+      truncated + ("\0" * (bytes - truncated.bytesize))
+    end
+
     # Append path to host, making sure there's one single / in between
     def append_path(host, path)
       "#{host.to_s.sub(%r{\/+$}, '')}/#{path.to_s.sub(%r{^\/+}, '')}"
diff --git a/spec/config/settings_spec.rb b/spec/config/settings_spec.rb
index 83b2de4774187acf9dfb5c5e5c6a69b380fbb846..fdbd0cb8990fbd4db9f85d744197fab4b635baba 100644
--- a/spec/config/settings_spec.rb
+++ b/spec/config/settings_spec.rb
@@ -6,4 +6,102 @@
       expect(described_class.omniauth.enabled).to be true
     end
   end
+
+  describe '.attr_encrypted_db_key_base_truncated' do
+    it 'is a string with maximum 32 bytes size' do
+      expect(described_class.attr_encrypted_db_key_base_truncated.bytesize)
+        .to be <= 32
+    end
+  end
+
+  describe '.attr_encrypted_db_key_base_12' do
+    context 'when db key base secret is less than 12 bytes' do
+      before do
+        allow(described_class)
+          .to receive(:attr_encrypted_db_key_base)
+          .and_return('a' * 10)
+      end
+
+      it 'expands db key base secret to 12 bytes' do
+        expect(described_class.attr_encrypted_db_key_base_12)
+          .to eq ('a' * 10) + ("\0" * 2)
+      end
+    end
+
+    context 'when key has multiple multi-byte UTF chars exceeding 12 bytes' do
+      before do
+        allow(described_class)
+          .to receive(:attr_encrypted_db_key_base)
+          .and_return('❤' * 18)
+      end
+
+      it 'does not use more than 32 bytes' do
+        db_key_base = described_class.attr_encrypted_db_key_base_12
+
+        expect(db_key_base).to eq ('❤' * 4)
+        expect(db_key_base.bytesize).to eq 12
+      end
+    end
+  end
+
+  describe '.attr_encrypted_db_key_base_32' do
+    context 'when db key base secret is less than 32 bytes' do
+      before do
+        allow(described_class)
+          .to receive(:attr_encrypted_db_key_base)
+          .and_return('a' * 10)
+      end
+
+      it 'expands db key base secret to 32 bytes' do
+        expanded_key_base = ('a' * 10) + ("\0" * 22)
+
+        expect(expanded_key_base.bytesize).to eq 32
+        expect(described_class.attr_encrypted_db_key_base_32)
+          .to eq expanded_key_base
+      end
+    end
+
+    context 'when db key base secret is 32 bytes' do
+      before do
+        allow(described_class)
+          .to receive(:attr_encrypted_db_key_base)
+          .and_return('a' * 32)
+      end
+
+      it 'returns original value' do
+        expect(described_class.attr_encrypted_db_key_base_32)
+          .to eq 'a' * 32
+      end
+    end
+
+    context 'when db key base contains multi-byte UTF character' do
+      before do
+        allow(described_class)
+          .to receive(:attr_encrypted_db_key_base)
+          .and_return('❤' * 6)
+      end
+
+      it 'does not use more than 32 bytes' do
+        db_key_base = described_class.attr_encrypted_db_key_base_32
+
+        expect(db_key_base).to eq '❤❤❤❤❤❤' + ("\0" * 14)
+        expect(db_key_base.bytesize).to eq 32
+      end
+    end
+
+    context 'when db key base multi-byte UTF chars exceeding 32 bytes' do
+      before do
+        allow(described_class)
+          .to receive(:attr_encrypted_db_key_base)
+          .and_return('❤' * 18)
+      end
+
+      it 'does not use more than 32 bytes' do
+        db_key_base = described_class.attr_encrypted_db_key_base_32
+
+        expect(db_key_base).to eq ('❤' * 10) + ("\0" * 2)
+        expect(db_key_base.bytesize).to eq 32
+      end
+    end
+  end
 end
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index ad2c9d7f2afb841424c6fa346e465f6d31e31207..9927ad41108025753a470ebd68674d3bb0c25c38 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -127,4 +127,42 @@
       end
     end
   end
+
+  describe '.ensure_utf8_size' do
+    context 'string is has less bytes than expected' do
+      it 'backfills string with null characters' do
+        transformed = described_class.ensure_utf8_size('a' * 10, bytes: 32)
+
+        expect(transformed.bytesize).to eq 32
+        expect(transformed).to eq ('a' * 10) + ("\0" * 22)
+      end
+    end
+
+    context 'string size is exactly the one that is expected' do
+      it 'returns original value' do
+        transformed = described_class.ensure_utf8_size('a' * 32, bytes: 32)
+
+        expect(transformed).to eq 'a' * 32
+        expect(transformed.bytesize).to eq 32
+      end
+    end
+
+    context 'when string contains a few multi-byte UTF characters' do
+      it 'backfills string with null characters' do
+        transformed = described_class.ensure_utf8_size('❤' * 6, bytes: 32)
+
+        expect(transformed).to eq '❤❤❤❤❤❤' + ("\0" * 14)
+        expect(transformed.bytesize).to eq 32
+      end
+    end
+
+    context 'when string has multiple multi-byte UTF chars exceeding 32 bytes' do
+      it 'truncates string to 32 characters and backfills it if needed' do
+        transformed = described_class.ensure_utf8_size('❤' * 18, bytes: 32)
+
+        expect(transformed).to eq ('❤' * 10) + ("\0" * 2)
+        expect(transformed.bytesize).to eq 32
+      end
+    end
+  end
 end