module Net::SSH::Transport::GCMCipher
def self.block_size
def self.block_size 16 end
def self.extended(orig)
def self.extended(orig) # rubocop:disable Metrics/BlockLength orig.class_eval do include Net::SSH::Loggable attr_reader :cipher attr_reader :key attr_accessor :nonce # # Semantically gcm cipher supplies the OpenSSL iv interface with a nonce # as it is not randomly generated due to being supplied from a counter. # The RFC's use IV and nonce interchangeably. # def initialize(encrypt:, key:) @cipher = OpenSSL::Cipher.new(algo_name) @key = key key_len = @cipher.key_len if key.size != key_len error_message = "#{cipher_name}: keylength does not match" error { error_message } raise error_message end encrypt ? @cipher.encrypt : @cipher.decrypt @cipher.key = key @nonce = { fixed: nil, invocation_counter: 0 } end def update_cipher_mac(payload, _sequence_number) # # --- RFC 5647 7.3 --- # When using AES-GCM with secure shell, the packet_length field is to # be treated as additional authenticated data, not as plaintext. # length_data = [payload.bytesize].pack('N') cipher.auth_data = length_data encrypted_data = cipher.update(payload) << cipher.final mac = cipher.auth_tag incr_nonce length_data + encrypted_data + mac end # # --- RFC 5647 --- # uint32 packet_length; // 0 <= packet_length < 2^32 # def read_length(data, _sequence_number) data.unpack1('N') end # # --- RFC 5647 --- # In AES-GCM secure shell, the inputs to the authenticated encryption # are: # PT (Plain Text) # byte padding_length; // 4 <= padding_length < 256 # byte[n1] payload; // n1 = packet_length-padding_length-1 # byte[n2] random_padding; // n2 = padding_length # AAD (Additional Authenticated Data) # uint32 packet_length; // 0 <= packet_length < 2^32 # IV (Initialization Vector) # As described in section 7.1. # BK (Block Cipher Key) # The appropriate Encryption Key formed during the Key Exchange. # def read_and_mac(data, mac, _sequence_number) # The authentication tag will be placed in the MAC field at the end of the packet # OpenSSL does not verify auth tag length # GCM mode allows arbitrary sizes for the auth_tag up to 128 bytes and a single # byte allows authentication to pass. If single byte auth tags are possible # an attacker would require no more than 256 attempts to forge a valid tag. # raise 'incorrect auth_tag length' unless mac.to_s.length == mac_length packet_length = data.unpack1('N') cipher.auth_tag = mac.to_s cipher.auth_data = [packet_length].pack('N') result = cipher.update(data[4...]) << cipher.final incr_nonce result end def mac_length 16 end def block_size 16 end def self.block_size 16 end # # --- RFC 5647 --- # N_MIN minimum nonce (IV) length 12 octets # N_MAX maximum nonce (IV) length 12 octets # def iv_len 12 end # # --- RFC 5288 --- # Each value of the nonce_explicit MUST be distinct for each distinct # invocation of the GCM encrypt function for any fixed key. Failure to # meet this uniqueness requirement can significantly degrade security. # The nonce_explicit MAY be the 64-bit sequence number. # # --- RFC 5116 --- # (2.1) Applications that can generate distinct nonces SHOULD use the nonce # formation method defined in Section 3.2, and MAY use any # other method that meets the uniqueness requirement. # # (3.2) The following method to construct nonces is RECOMMENDED. # # <- variable -> <- variable -> # - - - - - - - - - - - - - - # | fixed | counter | # # Initial octets consist of a fixed field and final octets consist of a # Counter field. Implementations SHOULD support 12-octet nonces in which # the Counter field is four octets long. # The Counter fields of successive nonces form a monotonically increasing # sequence, when those fields are regarded as unsignd integers in network # byte order. # The Counter part SHOULD be equal to zero for the first nonce and increment # by one for each successive nonce that is generated. # The Fixed field MUST remain constant for all nonces that are generated for # a given encryption device. # # --- RFC 5647 --- # The invocation field is treated as a 64-bit integer and is increment after # each invocation of AES-GCM to process a binary packet. # AES-GCM produces a keystream in blocks of 16-octets that is used to # encrypt the plaintext. This keystream is produced by encrypting the # following 16-octet data structure: # # uint32 fixed; // 4 octets # uint64 invocation_counter; // 8 octets # unit32 block_counter; // 4 octets # # The block_counter is initially set to one (1) and increment as each block # of key is produced. # # The reader is reminded that SSH requires that the data to be encrypted # MUST be padded out to a multiple of the block size (16-octets for AES-GCM). # def incr_nonce return if nonce[:fixed].nil? nonce[:invocation_counter] = [nonce[:invocation_counter].to_s.unpack1('B*').to_i(2) + 1].pack('Q>*') apply_nonce end def nonce=(iv_s) return if nonce[:fixed] nonce[:fixed] = iv_s[0...4] nonce[:invocation_counter] = iv_s[4...12] apply_nonce end def apply_nonce cipher.iv = "#{nonce[:fixed]}#{nonce[:invocation_counter]}" end # # --- RFC 5647 --- # If AES-GCM is selected as the encryption algorithm for a given # tunnel, AES-GCM MUST also be selected as the Message Authentication # Code (MAC) algorithm. Conversely, if AES-GCM is selected as the MAC # algorithm, it MUST also be selected as the encryption algorithm. # def implicit_mac? true end end end
def apply_nonce
def apply_nonce cipher.iv = "#{nonce[:fixed]}#{nonce[:invocation_counter]}" end
def block_size
def block_size 16 end
def implicit_mac?
algorithm, it MUST also be selected as the encryption algorithm.
Code (MAC) algorithm. Conversely, if AES-GCM is selected as the MAC
tunnel, AES-GCM MUST also be selected as the Message Authentication
If AES-GCM is selected as the encryption algorithm for a given
--- RFC 5647 ---
def implicit_mac? true end
def incr_nonce
MUST be padded out to a multiple of the block size (16-octets for AES-GCM).
The reader is reminded that SSH requires that the data to be encrypted
of key is produced.
The block_counter is initially set to one (1) and increment as each block
unit32 block_counter; // 4 octets
uint64 invocation_counter; // 8 octets
uint32 fixed; // 4 octets
following 16-octet data structure:
encrypt the plaintext. This keystream is produced by encrypting the
AES-GCM produces a keystream in blocks of 16-octets that is used to
each invocation of AES-GCM to process a binary packet.
The invocation field is treated as a 64-bit integer and is increment after
--- RFC 5647 ---
a given encryption device.
The Fixed field MUST remain constant for all nonces that are generated for
by one for each successive nonce that is generated.
The Counter part SHOULD be equal to zero for the first nonce and increment
byte order.
sequence, when those fields are regarded as unsignd integers in network
The Counter fields of successive nonces form a monotonically increasing
the Counter field is four octets long.
Counter field. Implementations SHOULD support 12-octet nonces in which
Initial octets consist of a fixed field and final octets consist of a
| fixed | counter |
- - - - - - - - - - - - - -
<- variable -> <- variable ->
(3.2) The following method to construct nonces is RECOMMENDED.
other method that meets the uniqueness requirement.
formation method defined in Section 3.2, and MAY use any
(2.1) Applications that can generate distinct nonces SHOULD use the nonce
--- RFC 5116 ---
The nonce_explicit MAY be the 64-bit sequence number.
meet this uniqueness requirement can significantly degrade security.
invocation of the GCM encrypt function for any fixed key. Failure to
Each value of the nonce_explicit MUST be distinct for each distinct
--- RFC 5288 ---
def incr_nonce return if nonce[:fixed].nil? nonce[:invocation_counter] = [nonce[:invocation_counter].to_s.unpack1('B*').to_i(2) + 1].pack('Q>*') apply_nonce end
def initialize(encrypt:, key:)
The RFC's use IV and nonce interchangeably.
as it is not randomly generated due to being supplied from a counter.
Semantically gcm cipher supplies the OpenSSL iv interface with a nonce
def initialize(encrypt:, key:) @cipher = OpenSSL::Cipher.new(algo_name) @key = key key_len = @cipher.key_len if key.size != key_len error_message = "#{cipher_name}: keylength does not match" error { error_message } raise error_message end encrypt ? @cipher.encrypt : @cipher.decrypt @cipher.key = key @nonce = { fixed: nil, invocation_counter: 0 } end
def iv_len
N_MAX maximum nonce (IV) length 12 octets
N_MIN minimum nonce (IV) length 12 octets
--- RFC 5647 ---
def iv_len 12 end
def mac_length
def mac_length 16 end
def nonce=(iv_s)
def nonce=(iv_s) return if nonce[:fixed] nonce[:fixed] = iv_s[0...4] nonce[:invocation_counter] = iv_s[4...12] apply_nonce end
def read_and_mac(data, mac, _sequence_number)
The appropriate Encryption Key formed during the Key Exchange.
BK (Block Cipher Key)
As described in section 7.1.
IV (Initialization Vector)
uint32 packet_length; // 0 <= packet_length < 2^32
AAD (Additional Authenticated Data)
byte[n2] random_padding; // n2 = padding_length
byte[n1] payload; // n1 = packet_length-padding_length-1
byte padding_length; // 4 <= padding_length < 256
PT (Plain Text)
are:
In AES-GCM secure shell, the inputs to the authenticated encryption
--- RFC 5647 ---
def read_and_mac(data, mac, _sequence_number) # The authentication tag will be placed in the MAC field at the end of the packet # OpenSSL does not verify auth tag length # GCM mode allows arbitrary sizes for the auth_tag up to 128 bytes and a single # byte allows authentication to pass. If single byte auth tags are possible # an attacker would require no more than 256 attempts to forge a valid tag. # raise 'incorrect auth_tag length' unless mac.to_s.length == mac_length packet_length = data.unpack1('N') cipher.auth_tag = mac.to_s cipher.auth_data = [packet_length].pack('N') result = cipher.update(data[4...]) << cipher.final incr_nonce result end
def read_length(data, _sequence_number)
uint32 packet_length; // 0 <= packet_length < 2^32
--- RFC 5647 ---
def read_length(data, _sequence_number) data.unpack1('N') end
def update_cipher_mac(payload, _sequence_number)
def update_cipher_mac(payload, _sequence_number) # # --- RFC 5647 7.3 --- # When using AES-GCM with secure shell, the packet_length field is to # be treated as additional authenticated data, not as plaintext. # length_data = [payload.bytesize].pack('N') cipher.auth_data = length_data encrypted_data = cipher.update(payload) << cipher.final mac = cipher.auth_tag incr_nonce length_data + encrypted_data + mac end