Class: ESM::Connection::Encryption
- Inherits:
-
Object
- Object
- ESM::Connection::Encryption
- Defined in:
- lib/esm/connection/encryption.rb
Constant Summary collapse
- CIPHER =
"aes-256-gcm"- NONCE_SIZE =
GCM standard
12- TAG_SIZE =
GCM authentication tag size
16- INDEX_LOW_BOUNDS =
First 32 bytes. A standard request will larger than 32 bytes so this shouldn't cause issues with the nonce being stacked at the end of the bytes (because of the data packet being smaller than 32 bytes)
0- INDEX_HIGH_BOUNDS =
31
Class Method Summary collapse
Instance Method Summary collapse
-
#decrypt(input) ⇒ String
Attempts to decrypt the provided byte array.
- #encrypt(data) ⇒ Object
-
#initialize(key, nonce_indices: [], session_id: "") ⇒ Encryption
constructor
A new instance of Encryption.
Constructor Details
#initialize(key, nonce_indices: [], session_id: "") ⇒ Encryption
Returns a new instance of Encryption.
20 21 22 23 24 25 26 27 |
# File 'lib/esm/connection/encryption.rb', line 20 def initialize(key, nonce_indices: [], session_id: "") key = key.bytes[INDEX_LOW_BOUNDS..INDEX_HIGH_BOUNDS] raise ArgumentError, "Encryption key must be 32 bytes" if key.size != 32 @key = key.pack("C*") @session_id = session_id @nonce_indices = nonce_indices.presence || (0...NONCE_SIZE).to_a end |
Class Method Details
.generate_nonce_indices ⇒ Object
15 16 17 18 |
# File 'lib/esm/connection/encryption.rb', line 15 def self.generate_nonce_indices indices = (INDEX_LOW_BOUNDS...INDEX_HIGH_BOUNDS).to_a.shuffle.shuffle NONCE_SIZE.times.map { indices.pop }.sort end |
Instance Method Details
#decrypt(input) ⇒ String
Attempts to decrypt the provided byte array
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/esm/connection/encryption.rb', line 63 def decrypt(input) cipher = OpenSSL::Cipher.new(CIPHER).decrypt cipher.key = @key # Extract nonce and encrypted data nonce = [] packet = [] input.bytes.each_with_index do |byte, index| if @nonce_indices[nonce.size] == index nonce << byte next end packet << byte end # Separate auth tag from encrypted data auth_tag = packet.pop(TAG_SIZE).pack("C*") encrypted_data = packet.pack("C*") cipher.iv = nonce.pack("C*") cipher.auth_tag = auth_tag cipher.auth_data = @session_id decrypted_data = cipher.update(encrypted_data) + cipher.final raise ESM::Exception::DecryptionError if decrypted_data.blank? decrypted_data rescue ArgumentError => e case e. when "key must be 32 bytes" raise ESM::Exception::DecryptionError, "Invalid secret key length" when "iv must be #{NONCE_SIZE} bytes" raise ESM::Exception::DecryptionError, "Invalid IV length" else raise e end rescue OpenSSL::Cipher::CipherError raise ESM::Exception::DecryptionError, "Authentication failed" end |
#encrypt(data) ⇒ Object
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/esm/connection/encryption.rb', line 29 def encrypt(data) cipher = OpenSSL::Cipher.new(CIPHER).encrypt nonce = cipher.random_iv cipher.key = @key cipher.iv = nonce cipher.auth_data = @session_id encrypted_data = cipher.update(data) + cipher.final auth_tag = cipher.auth_tag # Combine encrypted data and auth tag encrypted_bytes = encrypted_data.bytes + auth_tag.bytes nonce_bytes = nonce.bytes # Insert nonce bytes at specified positions @nonce_indices.each_with_index do |nonce_index, index| encrypted_bytes.insert(nonce_index, nonce_bytes[index]) end encrypted_bytes.pack("C*") end |