Module: Clacky::AesGcm
- Defined in:
- lib/clacky/aes_gcm.rb
Overview
Pure-Ruby AES-256-GCM implementation.
Why this exists:
macOS ships Ruby 2.6 linked against LibreSSL 3.3.x which has a known
bug: AES-GCM encrypt/decrypt raises CipherError even for valid inputs.
This implementation uses AES-256-ECB (which LibreSSL supports correctly)
as the single block-cipher primitive and builds GCM on top:
- CTR mode → keystream for encryption / decryption
- GHASH → authentication tag
The output is 100% compatible with OpenSSL / standard AES-256-GCM:
ciphertext, iv, and auth_tag produced here can be decrypted by OpenSSL
and vice-versa.
Reference: NIST SP 800-38D
Usage:
ct, tag = AesGcm.encrypt(key, iv, plaintext, aad)
pt = AesGcm.decrypt(key, iv, ciphertext, tag, aad)
Constant Summary collapse
- BLOCK_SIZE =
16- TAG_LENGTH =
16- R =
Galois Field GF(2^128) multiplication. Reduction polynomial: x^128 + x^7 + x^2 + x + 1 Uses the reflected bit order per GCM spec.
0xe1000000000000000000000000000000
Class Method Summary collapse
-
.decrypt(key, iv, ciphertext, tag, aad = "") ⇒ String
Decrypt ciphertext with AES-256-GCM and verify auth tag.
-
.encrypt(key, iv, plaintext, aad = "") ⇒ Array<String, String>
Encrypt plaintext with AES-256-GCM.
Class Method Details
.decrypt(key, iv, ciphertext, tag, aad = "") ⇒ String
Decrypt ciphertext with AES-256-GCM and verify auth tag.
56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/clacky/aes_gcm.rb', line 56 def self.decrypt(key, iv, ciphertext, tag, aad = "") aes = aes_ecb(key) h = aes.call("\x00" * BLOCK_SIZE) j0 = build_j0(iv, h) exp_tag = compute_tag(aes, h, j0, ciphertext, aad.b) unless secure_compare(exp_tag, tag) raise OpenSSL::Cipher::CipherError, "bad decrypt (authentication tag mismatch)" end ctr_crypt(aes, inc32(j0), ciphertext).force_encoding("UTF-8") end |
.encrypt(key, iv, plaintext, aad = "") ⇒ Array<String, String>
Encrypt plaintext with AES-256-GCM.
38 39 40 41 42 43 44 45 |
# File 'lib/clacky/aes_gcm.rb', line 38 def self.encrypt(key, iv, plaintext, aad = "") aes = aes_ecb(key) h = aes.call("\x00" * BLOCK_SIZE) # H = E(K, 0^128) j0 = build_j0(iv, h) ct = ctr_crypt(aes, inc32(j0), plaintext.b) tag = compute_tag(aes, h, j0, ct, aad.b) [ct, tag] end |