Module: AesBridge
- Defined in:
- lib/aes_bridge.rb,
lib/aes_bridge/cbc.rb,
lib/aes_bridge/gcm.rb,
lib/aes_bridge/common.rb,
lib/aes_bridge/legacy.rb,
lib/aes_bridge/version.rb
Defined Under Namespace
Classes: Error, RandomGenerator
Constant Summary collapse
- BLOCK_SIZE =
16- KEY_LEN =
32- IV_LEN =
16- VERSION =
"2.0.0"
Class Method Summary collapse
-
.decrypt(data, passphrase) ⇒ String
Decrypts a base64-encoded string encrypted with AES-GCM and verifies its integrity using an authentication tag.
-
.decrypt_cbc(data, passphrase) ⇒ String
Decrypts a base64-encoded string encrypted with AES-CBC-256 and verifies its integrity using HMAC-SHA-256.
-
.decrypt_cbc_bin(data, passphrase) ⇒ String
Decrypts the given ciphertext using AES-CBC-256 and HMAC-SHA-256 for integrity verification.
-
.decrypt_gcm(data, passphrase) ⇒ String
Decrypts a base64-encoded string encrypted with AES-GCM and verifies its integrity using an authentication tag.
-
.decrypt_gcm_bin(data, passphrase) ⇒ String
Decrypts a binary string encrypted with AES-GCM.
-
.decrypt_legacy(enc, passphrase) ⇒ String
Decrypts the given ciphertext using the legacy AES Everywhere format with AES-256-CBC.
- .derive_key_gcm(passphrase, salt) ⇒ Object
-
.derive_key_iv_legacy(passphrase, salt) ⇒ Array<String>
Derives an AES key and initialization vector (IV) from a passphrase and salt using an iterative hashing process.
-
.derive_keys_cbc(passphrase, salt) ⇒ Array<String>
Derives AES and HMAC keys from a passphrase and salt using PBKDF2-HMAC-SHA256.
-
.encrypt(plaintext, passphrase) ⇒ String
Encrypts a string using AES-GCM.
-
.encrypt_cbc(data, passphrase) ⇒ String
Encrypts the given plaintext using AES-CBC-256 with a randomly generated IV, and HMAC-SHA-256 for integrity verification.
-
.encrypt_cbc_bin(plaintext, passphrase) ⇒ String
Encrypts the given plaintext using AES-CBC-256 with a randomly generated IV, and HMAC-SHA-256 for integrity verification.
-
.encrypt_gcm(data, passphrase) ⇒ String
Encrypts a string using AES-GCM.
-
.encrypt_gcm_bin(plaintext, passphrase) ⇒ String
Encrypts the given plaintext using AES-GCM.
-
.encrypt_legacy(raw, passphrase) ⇒ String
Encrypts the given plaintext using the legacy AES Everywhere format with AES-256-CBC.
-
.generate_random(size) ⇒ String
Generates a cryptographically secure random string of a given size.
-
.to_bytes(data) ⇒ String
Converts data to bytes.
Class Method Details
.decrypt(data, passphrase) ⇒ String
Decrypts a base64-encoded string encrypted with AES-GCM and verifies its integrity using an authentication tag.
26 27 28 |
# File 'lib/aes_bridge.rb', line 26 def self.decrypt(data, passphrase) self.decrypt_gcm(data, passphrase) end |
.decrypt_cbc(data, passphrase) ⇒ String
Decrypts a base64-encoded string encrypted with AES-CBC-256 and verifies its integrity using HMAC-SHA-256.
104 105 106 |
# File 'lib/aes_bridge/cbc.rb', line 104 def self.decrypt_cbc(data, passphrase) decrypt_cbc_bin(Base64.decode64(data), passphrase) end |
.decrypt_cbc_bin(data, passphrase) ⇒ String
Decrypts the given ciphertext using AES-CBC-256 and HMAC-SHA-256 for integrity verification.
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'lib/aes_bridge/cbc.rb', line 62 def self.decrypt_cbc_bin(data, passphrase) data = self.to_bytes(data) passphrase = self.to_bytes(passphrase) salt = data[0, 16] iv = data[16, 16] tag = data[-32, 32] ciphertext = data[32...-32] aes_key, hmac_key = self.derive_keys_cbc(passphrase, salt) expected_tag = OpenSSL::HMAC.digest('sha256', hmac_key, iv + ciphertext) raise 'HMAC verification failed' unless expected_tag == tag cipher = OpenSSL::Cipher.new('aes-256-cbc') cipher.decrypt cipher.key = aes_key cipher.iv = iv # fix empty plaintext decryption if ciphertext.bytesize == 0 return '' end cipher.update(ciphertext) + cipher.final end |
.decrypt_gcm(data, passphrase) ⇒ String
Decrypts a base64-encoded string encrypted with AES-GCM and verifies its integrity using an authentication tag.
89 90 91 |
# File 'lib/aes_bridge/gcm.rb', line 89 def self.decrypt_gcm(data, passphrase) decrypt_gcm_bin(Base64.decode64(to_bytes(data)), passphrase) end |
.decrypt_gcm_bin(data, passphrase) ⇒ String
Decrypts a binary string encrypted with AES-GCM.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/aes_bridge/gcm.rb', line 52 def self.decrypt_gcm_bin(data, passphrase) data = to_bytes(data) passphrase = to_bytes(passphrase) salt = data[0,16] nonce = data[16,12] tag = data[-16,16] ciphertext = data[28...-16] cipher = OpenSSL::Cipher.new('aes-256-gcm') cipher.decrypt cipher.key = derive_key_gcm(passphrase, salt) cipher.iv = nonce cipher.auth_tag = tag # fix empty plaintext decryption if ciphertext.bytesize == 0 return '' end cipher.update(ciphertext) + cipher.final end |
.decrypt_legacy(enc, passphrase) ⇒ String
Decrypts the given ciphertext using the legacy AES Everywhere format with AES-256-CBC. The ciphertext must have a “Salted__” prefix.
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/aes_bridge/legacy.rb', line 43 def self.decrypt_legacy(enc, passphrase) data = Base64.decode64(enc) raise 'Invalid OpenSSL header' unless data.start_with?('Salted__') salt = data[8, 8] key, iv = derive_key_iv_legacy(passphrase, salt) cipher = OpenSSL::Cipher.new('AES-256-CBC') cipher.decrypt cipher.key = key cipher.iv = iv ciphertext = data[16..] # fix empty plaintext decryption if ciphertext.bytesize == 0 return '' end cipher.update(ciphertext) + cipher.final end |
.derive_key_gcm(passphrase, salt) ⇒ Object
9 10 11 12 13 14 15 16 17 |
# File 'lib/aes_bridge/gcm.rb', line 9 def self.derive_key_gcm(passphrase, salt) OpenSSL::KDF.pbkdf2_hmac( passphrase, salt: salt, iterations: 100_000, length: 32, hash: 'sha256' ) end |
.derive_key_iv_legacy(passphrase, salt) ⇒ Array<String>
Derives an AES key and initialization vector (IV) from a passphrase and salt using an iterative hashing process.
69 70 71 72 73 74 75 76 77 |
# File 'lib/aes_bridge/legacy.rb', line 69 def self.derive_key_iv_legacy(passphrase, salt) d = +'' prev = +'' while d.bytesize < KEY_LEN + IV_LEN prev = OpenSSL::Digest::MD5.digest(prev + passphrase.b + salt.b) d << prev end [d[0, KEY_LEN], d[KEY_LEN, IV_LEN]] end |
.derive_keys_cbc(passphrase, salt) ⇒ Array<String>
Derives AES and HMAC keys from a passphrase and salt using PBKDF2-HMAC-SHA256.
15 16 17 18 19 20 21 22 23 24 |
# File 'lib/aes_bridge/cbc.rb', line 15 def self.derive_keys_cbc(passphrase, salt) key_material = OpenSSL::KDF.pbkdf2_hmac( passphrase, salt: salt, iterations: 100_000, length: 64, hash: 'sha256' ) [key_material[0, 32], key_material[32, 32]] end |
.encrypt(plaintext, passphrase) ⇒ String
Encrypts a string using AES-GCM.
16 17 18 |
# File 'lib/aes_bridge.rb', line 16 def self.encrypt(plaintext, passphrase) self.encrypt_gcm(plaintext, passphrase) end |
.encrypt_cbc(data, passphrase) ⇒ String
Encrypts the given plaintext using AES-CBC-256 with a randomly generated IV, and HMAC-SHA-256 for integrity verification.
95 96 97 |
# File 'lib/aes_bridge/cbc.rb', line 95 def self.encrypt_cbc(data, passphrase) Base64.strict_encode64(encrypt_cbc_bin(data, passphrase)) end |
.encrypt_cbc_bin(plaintext, passphrase) ⇒ String
Encrypts the given plaintext using AES-CBC-256 with a randomly generated IV, and HMAC-SHA-256 for integrity verification.
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/aes_bridge/cbc.rb', line 32 def self.encrypt_cbc_bin(plaintext, passphrase) plaintext = self.to_bytes(plaintext) passphrase = self.to_bytes(passphrase) salt = self.generate_random(16) iv = self.generate_random(16) aes_key, hmac_key = self.derive_keys_cbc(passphrase, salt) cipher = OpenSSL::Cipher.new('aes-256-cbc') cipher.encrypt cipher.key = aes_key cipher.iv = iv # fix empty plaintext encryption if plaintext.bytesize > 0 ciphertext = cipher.update(plaintext) + cipher.final else ciphertext = '' + cipher.final end tag = OpenSSL::HMAC.digest('sha256', hmac_key, iv + ciphertext) salt + iv + ciphertext + tag end |
.encrypt_gcm(data, passphrase) ⇒ String
Encrypts a string using AES-GCM.
79 80 81 |
# File 'lib/aes_bridge/gcm.rb', line 79 def self.encrypt_gcm(data, passphrase) Base64.strict_encode64(encrypt_gcm_bin(data, passphrase)) end |
.encrypt_gcm_bin(plaintext, passphrase) ⇒ String
Encrypts the given plaintext using AES-GCM.
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/aes_bridge/gcm.rb', line 24 def self.encrypt_gcm_bin(plaintext, passphrase) passphrase = to_bytes(passphrase) plaintext = to_bytes(plaintext) salt = generate_random(16) nonce = generate_random(12) key = derive_key_gcm(passphrase, salt) cipher = OpenSSL::Cipher.new('aes-256-gcm') cipher.encrypt cipher.key = key cipher.iv = nonce # fix empty plaintext encryption if plaintext.bytesize > 0 ciphertext = cipher.update(plaintext) + cipher.final else ciphertext = '' + cipher.final end tag = cipher.auth_tag salt + nonce + ciphertext + tag end |
.encrypt_legacy(raw, passphrase) ⇒ String
Encrypts the given plaintext using the legacy AES Everywhere format with AES-256-CBC. A random salt is generated and used along with the passphrase to derive the encryption key and IV.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# File 'lib/aes_bridge/legacy.rb', line 17 def self.encrypt_legacy(raw, passphrase) salt = OpenSSL::Random.random_bytes(8) key, iv = derive_key_iv_legacy(passphrase, salt) cipher = OpenSSL::Cipher.new('AES-256-CBC') cipher.encrypt cipher.key = key cipher.iv = iv # fix empty plaintext encryption if raw.bytesize > 0 encrypted = cipher.update(raw) + cipher.final else encrypted = '' end result = "Salted__" + salt + encrypted Base64.strict_encode64(result) end |
.generate_random(size) ⇒ String
Generates a cryptographically secure random string of a given size.
61 62 63 64 |
# File 'lib/aes_bridge/common.rb', line 61 def self.generate_random(size) generator = RandomGenerator.new generator.generate_random_bytes(size) end |
.to_bytes(data) ⇒ String
Converts data to bytes. If data is a String, it’s converted to binary encoding. Otherwise, it’s returned as is.
14 15 16 |
# File 'lib/aes_bridge/common.rb', line 14 def self.to_bytes(data) data.is_a?(String) ? data.b : data end |