module Clacky::AesGcm
def self.aes_ecb(key)
def self.aes_ecb(key) er.new("aes-256-ecb") c.final
def self.build_j0(iv, h)
For 12-byte IVs (standard): J0 = IV || 0x00000001
Build J0 counter block.
def self.build_j0(iv, h) 2 x00\x01" )
def self.bytes_to_int(str)
def self.bytes_to_int(str) { |acc, b| (acc << 8) | b }
def self.compute_tag(aes, h, j0, ciphertext, aad)
Compute GCM auth tag.
def self.compute_tag(aes, h, j0, ciphertext, aad) ciphertext)
def self.ctr_crypt(aes, ctr0, data)
CTR-mode encryption/decryption (symmetric — same operation).
def self.ctr_crypt(aes, ctr0, data) .empty? tesize all(ctr) byteslice(pos, BLOCK_SIZE) (keystream, chunk)
def self.decrypt(key, iv, ciphertext, tag, aad = "")
-
(OpenSSL::Cipher::CipherError)- on authentication failure
Returns:
-
(String)- plaintext (UTF-8)
Parameters:
-
aad(String) -- additional authenticated data (may be empty) -
tag(String) -- 16-byte binary auth tag -
ciphertext(String) -- binary ciphertext -
iv(String) -- 12-byte binary IV -
key(String) -- 32-byte binary key
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
def self.each_block(data, &block)
def self.each_block(data, &block) y? size slice(i, BLOCK_SIZE) st(BLOCK_SIZE, "\x00") if chunk.bytesize < BLOCK_SIZE
def self.encrypt(key, iv, plaintext, aad = "")
-
(Array- [ciphertext, auth_tag] both binary strings)
Parameters:
-
aad(String) -- additional authenticated data (may be empty) -
plaintext(String) -- binary or UTF-8 plaintext -
iv(String) -- 12-byte binary IV (recommended for GCM) -
key(String) -- 32-byte binary key
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
def self.gf128_mul(x, y)
def self.gf128_mul(x, y) << 127) != 0 1 1
def self.ghash(h, aad, ciphertext)
GHASH: polynomial hashing over GF(2^128)
def self.ghash(h, aad, ciphertext) t(h) s blk| x = gf128_mul(bytes_to_int(blk) ^ x, h_int) } t blocks xt) { |blk| x = gf128_mul(bytes_to_int(blk) ^ x, h_int) } aad) || len(ciphertext) in bits, each as 64-bit big-endian tesize * 8].pack("Q>") + [ciphertext.bytesize * 8].pack("Q>") _to_int(len_block) ^ x, h_int)
def self.inc32(block)
def self.inc32(block) eslice(0, 12) eslice(12, 4).unpack1("N") + 1) & 0xFFFFFFFF].pack("N")
def self.int_to_bytes(n)
def self.int_to_bytes(n) shift(n & 0xFF); n >>= 8 }
def self.secure_compare(a, b)
def self.secure_compare(a, b) ytesize != b.bytesize ) { |x, y| result |= x ^ y }
def self.xor_blocks(a, b)
def self.xor_blocks(a, b) b.bytesize].min (a.getbyte(i) ^ b.getbyte(i)).chr }.join.b