Class: Lanet::Encryptor

Inherits:
Object
  • Object
show all
Defined in:
lib/lanet/encryptor.rb

Overview

Encryptor class for message encryption/decryption and signing

Defined Under Namespace

Modules: MessageType Classes: Error

Constant Summary collapse

ENCRYPTED_PREFIX =

Message type prefixes

"E"
PLAINTEXT_PREFIX =
"P"
SIGNED_ENCRYPTED_PREFIX =
"SE"
SIGNED_PLAINTEXT_PREFIX =
"SP"
SIGNATURE_DELIMITER =

Delimiters and sizes

"||SIG||"

Class Method Summary collapse

Class Method Details

.decrypt_data(content, key) ⇒ String

Decrypt encrypted content

Parameters:

  • content (String)

    base64-encoded encrypted data with IV

  • key (String)

    decryption key

Returns:

  • (String)

    decrypted data



206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/lanet/encryptor.rb', line 206

def self.decrypt_data(content, key)
  decoded = Base64.strict_decode64(content)
  iv = decoded[0...Config::IV_SIZE]
  ciphertext = decoded[Config::IV_SIZE..]

  decipher = OpenSSL::Cipher.new(Config::CIPHER_ALGORITHM)
  decipher.decrypt
  decipher.key = derive_key(key)
  decipher.iv = iv

  decipher.update(ciphertext) + decipher.final
rescue StandardError => e
  raise Error, "Decryption failed: #{e.message}"
end

.derive_key(key) ⇒ String

Derive a key from the provided password

Parameters:

  • key (String)

    the password/key to derive from

Returns:

  • (String)

    derived key of appropriate size



189
190
191
192
193
# File 'lib/lanet/encryptor.rb', line 189

def self.derive_key(key)
  validate_key_length(key)
  digest = OpenSSL::Digest.new("SHA256")
  OpenSSL::PKCS5.pbkdf2_hmac(key, "salt", 1000, Config::KEY_SIZE, digest)
end

.encrypt_data(data, key) ⇒ String

Encrypt data with the given key

Parameters:

  • data (String)

    data to encrypt

  • key (String)

    encryption key

Returns:

  • (String)

    base64-encoded encrypted data with IV



84
85
86
87
88
89
90
91
92
93
# File 'lib/lanet/encryptor.rb', line 84

def self.encrypt_data(data, key)
  cipher = OpenSSL::Cipher.new(Config::CIPHER_ALGORITHM)
  cipher.encrypt
  cipher.key = derive_key(key)
  iv = cipher.random_iv
  encrypted = cipher.update(data) + cipher.final
  Base64.strict_encode64(iv + encrypted)
rescue StandardError => e
  raise Error, "Encryption failed: #{e.message}"
end

.parse_message_type(data) ⇒ Array<Symbol, String>

Parse the message type from the prefix

Parameters:

  • data (String)

    the raw message data

Returns:

  • (Array<Symbol, String>)

    message type and content



122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/lanet/encryptor.rb', line 122

def self.parse_message_type(data)
  # Check for two-character prefixes first
  if data.length > 1 && data[0..1] == SIGNED_ENCRYPTED_PREFIX
    [MessageType::SIGNED_ENCRYPTED, data[2..]]
  elsif data.length > 1 && data[0..1] == SIGNED_PLAINTEXT_PREFIX
    [MessageType::SIGNED_PLAINTEXT, data[2..]]
  elsif data[0] == ENCRYPTED_PREFIX
    [MessageType::ENCRYPTED, data[1..]]
  elsif data[0] == PLAINTEXT_PREFIX
    [MessageType::PLAINTEXT, data[1..]]
  else
    [nil, data]
  end
end

.prepare_encrypted(message, key) ⇒ Object

Prepare an encrypted but unsigned message



60
61
62
63
# File 'lib/lanet/encryptor.rb', line 60

def self.prepare_encrypted(message, key)
  encrypted_data = encrypt_data(message, key)
  "#{ENCRYPTED_PREFIX}#{encrypted_data}"
end

.prepare_message(message, encryption_key, private_key = nil) ⇒ String

Prepares a message with encryption and/or signing

Parameters:

  • message (String)

    the message to prepare

  • encryption_key (String, nil)

    encryption key or nil for plaintext

  • private_key (String, nil) (defaults to: nil)

    PEM-encoded private key for signing or nil for unsigned

Returns:

  • (String)

    prepared message with appropriate prefix



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/lanet/encryptor.rb', line 37

def self.prepare_message(message, encryption_key, private_key = nil)
  message_str = message.to_s
  has_encryption = !encryption_key.nil? && !encryption_key.empty?
  has_signature = !private_key.nil? && !private_key.empty?

  case [has_signature, has_encryption]
  when [false, false]
    prepare_plaintext(message_str)
  when [false, true]
    prepare_encrypted(message_str, encryption_key)
  when [true, false]
    prepare_signed_plaintext(message_str, private_key)
  when [true, true]
    prepare_signed_encrypted(message_str, encryption_key, private_key)
  end
end

.prepare_plaintext(message) ⇒ Object

Prepare a plaintext message



55
56
57
# File 'lib/lanet/encryptor.rb', line 55

def self.prepare_plaintext(message)
  "#{PLAINTEXT_PREFIX}#{message}"
end

.prepare_signed_encrypted(message, encryption_key, private_key) ⇒ Object

Prepare a signed and encrypted message



73
74
75
76
77
78
# File 'lib/lanet/encryptor.rb', line 73

def self.prepare_signed_encrypted(message, encryption_key, private_key)
  signature = Signer.sign(message, private_key)
  message_with_signature = "#{message}#{SIGNATURE_DELIMITER}#{signature}"
  encrypted_data = encrypt_data(message_with_signature, encryption_key)
  "#{SIGNED_ENCRYPTED_PREFIX}#{encrypted_data}"
end

.prepare_signed_plaintext(message, private_key) ⇒ Object

Prepare a signed but unencrypted message



66
67
68
69
70
# File 'lib/lanet/encryptor.rb', line 66

def self.prepare_signed_plaintext(message, private_key)
  signature = Signer.sign(message, private_key)
  message_with_signature = "#{message}#{SIGNATURE_DELIMITER}#{signature}"
  "#{SIGNED_PLAINTEXT_PREFIX}#{message_with_signature}"
end

.process_encrypted_message(content, encryption_key) ⇒ Object

Process an encrypted message



138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/lanet/encryptor.rb', line 138

def self.process_encrypted_message(content, encryption_key)
  if encryption_key.nil? || encryption_key.strip.empty?
    { content: "[Encrypted message received, but no key provided]", verified: false }
  else
    begin
      decrypted = decrypt_data(content, encryption_key)
      { content: decrypted, verified: false }
    rescue Error => e
      { content: "Decryption failed: #{e.message}", verified: false }
    end
  end
end

.process_message(data, encryption_key = nil, public_key = nil) ⇒ Hash

Processes a message, decrypting and verifying if necessary

Parameters:

  • data (String)

    the data to process

  • encryption_key (String, nil) (defaults to: nil)

    decryption key or nil

  • public_key (String, nil) (defaults to: nil)

    PEM-encoded public key for verification or nil

Returns:

  • (Hash)

    processed message with content and verification status



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/lanet/encryptor.rb', line 100

def self.process_message(data, encryption_key = nil, public_key = nil)
  return { content: "[Empty message]", verified: false } if data.nil? || data.empty?

  message_type, content = parse_message_type(data)

  case message_type
  when MessageType::ENCRYPTED
    process_encrypted_message(content, encryption_key)
  when MessageType::PLAINTEXT
    { content: content, verified: false }
  when MessageType::SIGNED_ENCRYPTED
    process_signed_encrypted_message(content, encryption_key, public_key)
  when MessageType::SIGNED_PLAINTEXT
    process_signed_content(content, public_key)
  else
    { content: "[Invalid message format]", verified: false }
  end
end

.process_signed_content(content, public_key) ⇒ Object

Process content that contains a signature



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/lanet/encryptor.rb', line 166

def self.process_signed_content(content, public_key)
  if content.include?(SIGNATURE_DELIMITER)
    message, signature = content.split(SIGNATURE_DELIMITER, 2)

    if public_key.nil? || public_key.strip.empty?
      { content: message, verified: false, verification_status: "No public key provided for verification" }
    else
      begin
        verified = Signer.verify(message, signature, public_key)
        { content: message, verified: verified,
          verification_status: verified ? "Verified" : "Signature verification failed" }
      rescue StandardError => e
        { content: message, verified: false, verification_status: "Verification error: #{e.message}" }
      end
    end
  else
    { content: content, verified: false, verification_status: "No signature found" }
  end
end

.process_signed_encrypted_message(content, encryption_key, public_key) ⇒ Object

Process a signed and encrypted message



152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/lanet/encryptor.rb', line 152

def self.process_signed_encrypted_message(content, encryption_key, public_key)
  if encryption_key.nil? || encryption_key.strip.empty?
    { content: "[Signed encrypted message received, but no encryption key provided]", verified: false }
  else
    begin
      decrypted = decrypt_data(content, encryption_key)
      process_signed_content(decrypted, public_key)
    rescue Error => e
      { content: "Processing signed encrypted message failed: #{e.message}", verified: false }
    end
  end
end

.validate_key_length(key) ⇒ Object

Validate key length

Raises:



196
197
198
199
200
# File 'lib/lanet/encryptor.rb', line 196

def self.validate_key_length(key)
  return unless key && key.length > Config::MAX_KEY_LENGTH

  raise Error, "Encryption key is too long (maximum #{Config::MAX_KEY_LENGTH} characters)"
end