class String

Public Class Methods

random_byte() click to toggle source

Obviously not cryptographically secure

# File lib/crypto_toolchain/extensions/string_extensions.rb, line 11
def self.random_byte
  (0..255).to_a.sample.chr
end
random_bytes(n) click to toggle source

Not cryptographically secure

# File lib/crypto_toolchain/extensions/string_extensions.rb, line 4
def self.random_bytes(n)
  n.times.with_object("") do |_, memo|
    memo << random_byte
  end
end

Public Instance Methods

^(other) click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 55
def ^(other)
  if length != other.length
    raise ArgumentError.new("Must be same lengths, self: #{self.bytesize}, other: #{other.bytesize}")
  end
  each_byte.with_index.with_object("") do |(byte, i), ret|
    ret << (byte.ord ^ other[i].ord)
  end
end
bit_flip(bit_index, byte_index: 0)
Alias for: bitflip
bitflip(bit_index, byte_index: 0) click to toggle source

Bitstring is indexed as a normal string, ie:

'd' = 0x64 = 01100100

01234567

'd'.bitflip(7) => 'e'

# File lib/crypto_toolchain/extensions/string_extensions.rb, line 212
def bitflip(bit_index, byte_index: 0)
  byte_offset, bit_offset = bit_index.divmod(8)
  byte_offset += byte_index
  target = self.dup
  target[byte_offset] = (target[byte_offset].ord ^  (1 << (7-bit_offset))).chr
  target
end
Also aliased as: bit_flip, flipbit, flip_bit, flip
bits()
Alias for: to_bits
bitstring()
Alias for: to_bits
contains_duplicate_blocks?(blocksize = CryptoToolchain::AES_BLOCK_SIZE) click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 238
def contains_duplicate_blocks?(blocksize = CryptoToolchain::AES_BLOCK_SIZE)
  _blocks = in_blocks(blocksize)
  _blocks.length > _blocks.uniq.length
end
Also aliased as: is_ecb_encrypted?
decrypt_cbc(key: , iv: , blocksize: CryptoToolchain::AES_BLOCK_SIZE, cipher: 'AES-128', strip_padding: true) click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 175
def decrypt_cbc(key: , iv: , blocksize: CryptoToolchain::AES_BLOCK_SIZE, cipher: 'AES-128', strip_padding: true)
  _blocks = in_blocks(blocksize)
  decrypted = _blocks.each_with_object("").with_index do |(block, memo), i|
    dec = OpenSSL::Cipher.new("#{cipher}-ECB")
    dec.decrypt
    dec.key = key
    dec.padding = 0
    unciphered = dec.update(block) + dec.final
    chain_block = i == 0 ? iv : _blocks[i - 1]
    memo << (unciphered ^ chain_block)
  end
  if strip_padding
    decrypted.without_pkcs7_padding(blocksize)
  else
    decrypted
  end
end
decrypt_ctr(key: , nonce: , cipher: 'AES-128', start_counter: 0)
Alias for: encrypt_ctr
decrypt_ecb(key: , blocksize: CryptoToolchain::AES_BLOCK_SIZE, cipher: 'AES-128') click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 151
def decrypt_ecb(key: , blocksize: CryptoToolchain::AES_BLOCK_SIZE, cipher: 'AES-128')
  in_blocks(blocksize).each_with_object("") do |block, memo|
    dec = OpenSSL::Cipher.new("#{cipher}-ECB")
    dec.decrypt
    dec.key = key
    dec.padding = 0
    plain = dec.update(block) + dec.final
    memo << plain
  end.without_pkcs7_padding(blocksize)
end
encrypt_cbc(key: , iv: , blocksize: CryptoToolchain::AES_BLOCK_SIZE, cipher: 'AES-128') click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 193
def encrypt_cbc(key: , iv: , blocksize: CryptoToolchain::AES_BLOCK_SIZE, cipher: 'AES-128')
  _blocks = pad_pkcs7(blocksize).in_blocks(blocksize)
  _blocks.each_with_object("").with_index do |(block, memo), i|
    chain_block = i == 0 ? iv : memo[(blocksize * -1)..-1]
    intermediate = block ^ chain_block
    enc = OpenSSL::Cipher.new("#{cipher}-ECB")
    enc.encrypt
    enc.key = key
    enc.padding = 0
    crypted = enc.update(intermediate) + enc.final
    memo << crypted
  end
end
encrypt_ctr(key: , nonce: , cipher: 'AES-128', start_counter: 0) click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 224
def encrypt_ctr(key: , nonce: , cipher: 'AES-128', start_counter: 0)
  each_byte.with_index(start_counter).with_object("") do |(byte, i), memo|
    ctr = i / 16
    ctr_params = [nonce, ctr].pack("Q<Q<")
    enc = OpenSSL::Cipher.new("#{cipher}-ECB")
    enc.encrypt
    enc.key = key
    enc.padding = 0
    keystream = enc.update(ctr_params) + enc.final
    memo << (byte.chr ^ keystream[i % 16])
  end
end
Also aliased as: decrypt_ctr
encrypt_ecb(key: , blocksize: CryptoToolchain::AES_BLOCK_SIZE, cipher: 'AES-128') click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 162
def encrypt_ecb(key: , blocksize: CryptoToolchain::AES_BLOCK_SIZE, cipher: 'AES-128')
  self.pad_pkcs7(blocksize).in_blocks(blocksize).each_with_object("").with_index do |(block, memo), i|

    enc = OpenSSL::Cipher.new("#{cipher}-ECB")
    enc.encrypt
    enc.key = key
    enc.padding = 0
    plain = enc.update(block) + enc.final

    memo << plain
  end
end
flip(bit_index, byte_index: 0)
Alias for: bitflip
flip_bit(bit_index, byte_index: 0)
Alias for: bitflip
flipbit(bit_index, byte_index: 0)
Alias for: bitflip
from_base64(strict: true) click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 43
def from_base64(strict: true)
  if strict
    begin
      Base64.strict_decode64(self)
    rescue ArgumentError
      Base64.decode64(self)
    end
  else
    Base64.decode64(self)
  end
end
from_hex() click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 15
def from_hex
  raise StandardError.new("Not hex") unless hex?
  [self].pack("H*")
end
hamming_distance(other) click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 80
def hamming_distance(other)
  (self ^ other).to_bits.count("1")
end
hex?() click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 24
def hex?
  self !~ /[^0-9a-f]/i
end
in_blocks(blocksize = CryptoToolchain::AES_BLOCK_SIZE) click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 68
def in_blocks(blocksize = CryptoToolchain::AES_BLOCK_SIZE)
  bytes.map(&:chr).each_slice(blocksize).map(&:join) || [""]
end
is_ecb_encrypted?(blocksize = CryptoToolchain::AES_BLOCK_SIZE)
is_pkcs1_5_padded?(bits) click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 135
def is_pkcs1_5_padded?(bits)
  self[0..1] == "\x00\x02"
end
is_pkcs7_padded?(blocksize = CryptoToolchain::AES_BLOCK_SIZE) click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 139
def is_pkcs7_padded?(blocksize = CryptoToolchain::AES_BLOCK_SIZE)
  return in_blocks(blocksize).last.is_block_pkcs7_padded?(blocksize)
end
pad_pkcs1_5(bits) click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 126
def pad_pkcs1_5(bits)
  len = bits / 8
  if self.bytesize > len - 11
    raise ArgumentError.new("String #{self.inspect} is too long to pad with PKCS#1v1.5, length: #{self.bytesize}")
  end
  padstring = (len - 3 - self.bytesize).times.with_object("") { |_, memo| memo << rand(1..255).chr }
  "\x00\x02#{padstring}\x00#{self}"
end
pad_pkcs7(blocksize = CryptoToolchain::AES_BLOCK_SIZE) click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 116
def pad_pkcs7(blocksize = CryptoToolchain::AES_BLOCK_SIZE)
  _blocks = in_blocks(blocksize)
  pad_num = blocksize - _blocks.last.bytesize
  if pad_num == 0
    "#{self}#{blocksize.chr * blocksize}"
  else
    "#{self}#{pad_num.chr * pad_num}"
  end
end
potential_repeating_xor_keys(potential_keysizes: self.potential_repeating_xor_keysizes) click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 96
def potential_repeating_xor_keys(potential_keysizes: self.potential_repeating_xor_keysizes)
  potential_keysizes.map do |keysize|
    arr = self.in_blocks(keysize)
    transposed = (0...keysize).each_with_object([]) do |i, memo|
      memo << arr.map { |row| row[i] }.join
    end
    key = transposed.each_with_object("") do |str, memo|
      memo << CryptoToolchain::Tools.detect_single_character_xor(str)
    end
    key
  end
end
potential_repeating_xor_keysizes(take: 3, min: 2, max: 40) click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 90
def potential_repeating_xor_keysizes(take: 3, min: 2, max: 40)
  (min..max).sort_by do |size|
    normalized_hamming_distance(self.in_blocks(size)) / size.to_f
  end.take(take)
end
repeat_to(len) click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 72
def repeat_to(len)
  ljust(len, self)
end
score() click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 64
def score
  scan(/[etaoin shrdlu]/i).size
end
snake_case()
Alias for: snakecase
snakecase() click to toggle source

Thanks Ruby Facets!

# File lib/crypto_toolchain/extensions/string_extensions.rb, line 245
def snakecase
  gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
    gsub(/([a-z\d])([A-Z])/,'\1_\2').
    tr('-', '_').
    gsub(/\s/, '_').
    gsub(/__+/, '_').
    downcase
end
Also aliased as: snake_case, underscore
swap_endian() click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 28
def swap_endian
  raise ArgumentError.new("Bytesize must be multiple of 4") unless bytesize % 4 == 0
  unpack("L<*").pack("L>*")
end
Also aliased as: swap_endianness
swap_endianness()
Alias for: swap_endian
to_base64(strict: true) click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 35
def to_base64(strict: true)
  if strict
    Base64.strict_encode64(self)
  else
    Base64.encode64(self)
  end
end
to_bits() click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 84
def to_bits
  self.unpack("B*").first
end
Also aliased as: bitstring, bits
to_hex() click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 20
def to_hex
  unpack("H*").first
end
to_number() click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 76
def to_number
  to_hex.to_i(16)
end
underscore()
Alias for: snakecase
unique_blocks(blocksize = CryptoToolchain::AES_BLOCK_SIZE) click to toggle source

unique blocks. block size is in bytes

# File lib/crypto_toolchain/extensions/string_extensions.rb, line 110
def unique_blocks(blocksize = CryptoToolchain::AES_BLOCK_SIZE)
  in_blocks(blocksize).each_with_object({}) do |block, found|
    found[block] ||= true
  end.keys
end
without_pkcs7_padding(blocksize = CryptoToolchain::AES_BLOCK_SIZE, raise_error: false) click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 143
def without_pkcs7_padding(blocksize = CryptoToolchain::AES_BLOCK_SIZE, raise_error: false)
  if !is_pkcs7_padded?(blocksize)
    raise ArgumentError.new("Not PKCS7 padded") unless is_pkcs7_padded?(blocksize) if raise_error
    return self
  end
  self[0..(bytesize - (1 + bytes.last))]
end

Protected Instance Methods

is_block_pkcs7_padded?(blocksize = CryptoToolchain::AES_BLOCK_SIZE) click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 258
def is_block_pkcs7_padded?(blocksize = CryptoToolchain::AES_BLOCK_SIZE)
  return true if self == blocksize.chr * blocksize
  (1...blocksize).each do |padding|
    return true if self[(blocksize - padding)..-1] == padding.chr * padding
  end
  false
end
normalized_hamming_distance(blocks) click to toggle source
# File lib/crypto_toolchain/extensions/string_extensions.rb, line 266
def normalized_hamming_distance(blocks)
  raise ArgumentError.new("arg should be an array") unless blocks.is_a?(Array)
  (
    blocks[0].hamming_distance(blocks[1]) +
    blocks[0].hamming_distance(blocks[2]) +
    blocks[0].hamming_distance(blocks[3])
  ) / 3.0
end