class ECE
TODO: variable padding
Constants
- KEY_LENGTH
- NONCE_LENGTH
- SHA256_LENGTH
- TAG_LENGTH
- VERSION
Public Class Methods
decrypt(data, params)
click to toggle source
# File lib/ece/ece.rb, line 79 def self.decrypt(data, params) key = extract_key(params) rs = params[:rs] ? params [:rs] : 4096 raise "The rs parameter must be greater than 1." if rs <= 1 rs += TAG_LENGTH raise "Message is truncated" if data.length % rs == 0 result = "" counter = 0 (0..data.length).step(rs) do |i| block = decrypt_record(key, counter, data[i..i+rs-1]) result += block counter +=1 end result end
decrypt_record(params, counter, buffer, pad=0)
click to toggle source
# File lib/ece/ece.rb, line 95 def self.decrypt_record(params, counter, buffer, pad=0) gcm = OpenSSL::Cipher.new('aes-128-gcm') gcm.decrypt gcm.key = params[:key] gcm.iv = generate_nonce(params[:nonce], counter) pad_bytes = 1 if params[:auth] # old spec used 1 byte for padding, latest one always uses 2 bytes pad_bytes = 2 end raise "Block is too small" if buffer.length <= TAG_LENGTH+pad_bytes gcm.auth_tag = buffer[-TAG_LENGTH..-1] decrypted = gcm.update(buffer[0..-TAG_LENGTH-1]) + gcm.final if params[:auth] padding_length = decrypted[0..1].unpack("n")[0] raise "Padding is too big" if padding_length+2 > decrypted.length padding = decrypted[2..padding_length] raise "Wrong padding" unless padding = "\x00"*padding_length return decrypted[2+padding_length..-1] else padding_length = decrypted[0].unpack("C")[0] raise "Padding is too big" if padding_length+1 > decrypted.length padding = decrypted[1..padding_length] raise "Wrong padding" unless padding = "\x00"*padding_length return decrypted[1..-1] end end
encrypt(data, params)
click to toggle source
# File lib/ece/ece.rb, line 58 def self.encrypt(data, params) key = extract_key(params) rs = params[:rs] ? params [:rs] : 4096 padsize = params[:padsize] ? params [:padsize] : 0 raise "The rs parameter must be greater than 1." if rs <= 1 rs -=1 #this ensures encrypted data cannot be truncated result = "" pad_bytes = 1 if params[:auth] # old spec used 1 byte for padding, latest one always uses 2 bytes pad_bytes = 2 end counter = 0 (0..data.length).step(rs-pad_bytes+1) do |i| block = encrypt_record(key, counter, data[i..i+rs-pad_bytes], padsize) result += block counter +=1 end result end
encrypt_record(params, counter, buffer, pad=0)
click to toggle source
# File lib/ece/ece.rb, line 123 def self.encrypt_record(params, counter, buffer, pad=0) gcm = OpenSSL::Cipher.new('aes-128-gcm') gcm.encrypt gcm.key = params[:key] gcm.iv = generate_nonce(params[:nonce], counter) gcm.auth_data = "" padding = "" if params[:auth] padding = [pad].pack('n') + "\x00"*pad # 2 bytes, big endian, then n zero bytes of padding buf = padding+buffer record = gcm.update(buf) else record = gcm.update("\x00"+buffer) # 1 padding byte, not fully implemented end enc = record + gcm.final + gcm.auth_tag enc end
extract_key(params)
click to toggle source
# File lib/ece/ece.rb, line 29 def self.extract_key(params) raise "Salt must be 16-bytes long" unless params[:salt].length==16 input_key = params[:key] auth = false if params.has_key?(:auth) # Encrypted Content Encoding, March 11 2016, http://httpwg.org/http-extensions/draft-ietf-httpbis-encryption-encoding.html auth = true input = HKDF.new(input_key, {salt: params[:auth] , algorithm: 'sha256', info: "Content-Encoding: auth\x00"}) input_key = input.next_bytes(SHA256_LENGTH) secret = HKDF.new(input_key, {salt: params[:salt], algorithm: 'sha256', info: get_info("aesgcm", params[:user_public_key], params[:server_public_key])}) nonce = HKDF.new(input_key, salt: params[:salt], algorithm: 'sha256', info: get_info("nonce", params[:user_public_key], params[:server_public_key])) else secret = HKDF.new(input_key, {salt: params[:salt], algorithm: 'sha256', info: "Content-Encoding: aesgcm128"}) nonce = HKDF.new(input_key, salt: params[:salt], algorithm: 'sha256', info: "Content-Encoding: nonce") end {key: secret.next_bytes(KEY_LENGTH), nonce: nonce.next_bytes(NONCE_LENGTH), auth: auth} end
generate_nonce(nonce, counter)
click to toggle source
# File lib/ece/ece.rb, line 48 def self.generate_nonce(nonce, counter) raise "Nonce must be #{NONCE_LENGTH} bytes long." unless nonce.length == NONCE_LENGTH output = nonce.dup integer = nonce[-6..-1].unpack('B*')[0].to_i(2) #taking last 6 bytes, treating as integer x = ((integer ^ counter) & 0xffffff) + ((((integer / 0x1000000) ^ (counter / 0x1000000)) & 0xffffff) * 0x1000000) bytestring = x.to_s(16).length < 12 ? "0"*(12-x.to_s(16).length)+x.to_s(16) : x.to_s(16) #it's for correct handling of cases when generated integer is less than 6 bytes output[-6..-1] = [bytestring].pack('H*') #without it packing would produce less than 6 bytes output #I didn't find pack directive for such usage, so there is a such solution end
get_info(type, client_public, server_public)
click to toggle source
# File lib/ece/ece.rb, line 23 def self.get_info(type, client_public, server_public) cl_len_no = [client_public.size].pack('n') sv_len_no = [server_public.size].pack('n') "Content-Encoding: #{type}\x00P-256\x00#{cl_len_no}#{client_public}#{sv_len_no}#{server_public}" end
hkdf_extract(salt, ikm)
click to toggle source
# File lib/ece/ece.rb, line 19 def self.hkdf_extract(salt, ikm) #ikm stays for input keying material hmac_hash(salt,ikm) end
hmac_hash(key, input)
click to toggle source
# File lib/ece/ece.rb, line 14 def self.hmac_hash(key, input) digest = OpenSSL::Digest.new('sha256') OpenSSL::HMAC.digest(digest, key, input) end