Class: String

Inherits:
Object show all
Defined in:
lib/crypto_toolchain/extensions/string_extensions.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.random_byteObject

Obviously not cryptographically secure



11
12
13
# File 'lib/crypto_toolchain/extensions/string_extensions.rb', line 11

def self.random_byte
  (0..255).to_a.sample.chr
end

.random_bytes(n) ⇒ Object

Not cryptographically secure



4
5
6
7
8
# 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

Instance Method Details

#^(other) ⇒ Object



55
56
57
58
59
60
61
62
# 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

#bitflip(bit_index, byte_index: 0) ⇒ Object Also known as: bit_flip, flipbit, flip_bit, flip

Bitstring is indexed as a normal string, ie:

‘d’ = 0x64 = 01100100

01234567

‘d’.bitflip(7) => ‘e’



212
213
214
215
216
217
218
# 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

#contains_duplicate_blocks?(blocksize = CryptoToolchain::AES_BLOCK_SIZE) ⇒ Boolean Also known as: is_ecb_encrypted?

Returns:

  • (Boolean)


238
239
240
241
# 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

#decrypt_cbc(key:, iv:, blocksize: CryptoToolchain::AES_BLOCK_SIZE, cipher: 'AES-128', strip_padding: true) ⇒ Object



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# 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_ecb(key:, blocksize: CryptoToolchain::AES_BLOCK_SIZE, cipher: 'AES-128') ⇒ Object



151
152
153
154
155
156
157
158
159
160
# 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') ⇒ Object



193
194
195
196
197
198
199
200
201
202
203
204
205
# 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) ⇒ Object Also known as: decrypt_ctr



224
225
226
227
228
229
230
231
232
233
234
235
# 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

#encrypt_ecb(key:, blocksize: CryptoToolchain::AES_BLOCK_SIZE, cipher: 'AES-128') ⇒ Object



162
163
164
165
166
167
168
169
170
171
172
173
# 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

#from_base64(strict: true) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
# 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_hexObject

Raises:

  • (StandardError)


15
16
17
18
# 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) ⇒ Object



80
81
82
# File 'lib/crypto_toolchain/extensions/string_extensions.rb', line 80

def hamming_distance(other)
  (self ^ other).to_bits.count("1")
end

#hex?Boolean

Returns:

  • (Boolean)


24
25
26
# 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) ⇒ Object



68
69
70
# 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_pkcs1_5_padded?(bits) ⇒ Boolean

Returns:

  • (Boolean)


135
136
137
# 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) ⇒ Boolean

Returns:

  • (Boolean)


139
140
141
# 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) ⇒ Object



126
127
128
129
130
131
132
133
# 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) ⇒ Object



116
117
118
119
120
121
122
123
124
# 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) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
# 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) ⇒ Object



90
91
92
93
94
# 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) ⇒ Object



72
73
74
# File 'lib/crypto_toolchain/extensions/string_extensions.rb', line 72

def repeat_to(len)
  ljust(len, self)
end

#scoreObject



64
65
66
# File 'lib/crypto_toolchain/extensions/string_extensions.rb', line 64

def score
  scan(/[etaoin shrdlu]/i).size
end

#snakecaseObject Also known as: snake_case, underscore

Thanks Ruby Facets!



245
246
247
248
249
250
251
252
# 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

#swap_endianObject Also known as: swap_endianness

Raises:

  • (ArgumentError)


28
29
30
31
# 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

#to_base64(strict: true) ⇒ Object



35
36
37
38
39
40
41
# 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_bitsObject Also known as: bitstring, bits



84
85
86
# File 'lib/crypto_toolchain/extensions/string_extensions.rb', line 84

def to_bits
  self.unpack("B*").first
end

#to_hexObject



20
21
22
# File 'lib/crypto_toolchain/extensions/string_extensions.rb', line 20

def to_hex
  unpack("H*").first
end

#to_numberObject



76
77
78
# File 'lib/crypto_toolchain/extensions/string_extensions.rb', line 76

def to_number
  to_hex.to_i(16)
end

#unique_blocks(blocksize = CryptoToolchain::AES_BLOCK_SIZE) ⇒ Object

unique blocks. block size is in bytes



110
111
112
113
114
# 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) ⇒ Object



143
144
145
146
147
148
149
# 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