Module: Ronin::Support::Crypto

Defined in:
lib/ronin/support/crypto.rb,
lib/ronin/support/crypto/key.rb,
lib/ronin/support/crypto/cert.rb,
lib/ronin/support/crypto/hmac.rb,
lib/ronin/support/crypto/mixin.rb,
lib/ronin/support/crypto/cipher.rb,
lib/ronin/support/crypto/key/dh.rb,
lib/ronin/support/crypto/key/ec.rb,
lib/ronin/support/crypto/key/dsa.rb,
lib/ronin/support/crypto/key/rsa.rb,
lib/ronin/support/crypto/cert_chain.rb,
lib/ronin/support/crypto/cipher/aes.rb,
lib/ronin/support/crypto/key/methods.rb,
lib/ronin/support/crypto/cipher/aes128.rb,
lib/ronin/support/crypto/cipher/aes256.rb

Overview

Since:

  • 1.0.0

Defined Under Namespace

Modules: Key, Mixin Classes: Cert, CertChain, Cipher, HMAC

Class Method Summary collapse

Class Method Details

.aes128_cipher(**kwargs) ⇒ Cipher::AES

Creates a new AES-128 cipher.

Examples:

Crypto.aes128_cipher(direction: :encrypt, password: 's3cr3t')
# => #<Ronin::Support::Crypto::Cipher::AES128:0x00007f8bde789648 @key_size=128, @mode=:cbc>

Parameters:

Options Hash (**kwargs):

  • :mode (:cbc, :cfb, :ofb, :ctr, Symbol) — default: :cbc

    The desired AES cipher mode.

  • :hash (Symbol) — default: :md5

    The algorithm to hash the :password.

  • :key (String)

    The secret key to use.

  • :password (String)

    The password for the cipher.

  • :iv (String)

    The optional Initial Vector (IV).

  • :padding (Integer)

    Sets the padding for the cipher.

Returns:

See Also:

Since:

  • 1.0.0



445
446
447
# File 'lib/ronin/support/crypto.rb', line 445

def self.aes128_cipher(**kwargs)
  Cipher::AES128.new(**kwargs)
end

.aes128_decrypt(data, **kwargs) ⇒ String

Decrypts data using AES-128.

Parameters:

  • data (#to_s)

    The data to encrypt.

  • kwargs (Hash{Symbol => Object})

    Additional keyword arguments for aes128_cipher.

Options Hash (**kwargs):

  • mode (:cbc, :cfb, :ofb, :ctr, Symbol) — default: :cbc

    The desired AES cipher mode.

  • :hash (Symbol) — default: :md5

    The algorithm to hash the :password.

  • :key (String)

    The secret key to use.

  • :password (String)

    The password for the cipher.

  • :iv (String)

    The optional Initial Vector (IV).

  • :padding (Integer)

    Sets the padding for the cipher.

Returns:

  • (String)

    The encrypted data.

Raises:

  • (ArgumentError)

    Either the the key: or password: keyword argument must be given.

Since:

  • 1.0.0



519
520
521
# File 'lib/ronin/support/crypto.rb', line 519

def self.aes128_decrypt(data,**kwargs)
  self.aes128_cipher(direction: :decrypt, **kwargs).decrypt(data)
end

.aes128_encrypt(data, **kwargs) ⇒ String

Encrypts data using AES-128.

Parameters:

  • data (#to_s)

    The data to encrypt.

  • kwargs (Hash{Symbol => Object})

    Additional keyword arguments for aes128_cipher.

Options Hash (**kwargs):

  • mode (:cbc, :cfb, :ofb, :ctr, Symbol) — default: :cbc

    The desired AES cipher mode.

  • :hash (Symbol) — default: :md5

    The algorithm to hash the :password.

  • :key (String)

    The secret key to use.

  • :password (String)

    The password for the cipher.

  • :iv (String)

    The optional Initial Vector (IV).

  • :padding (Integer)

    Sets the padding for the cipher.

Returns:

  • (String)

    The encrypted data.

Raises:

  • (ArgumentError)

    Either the the key: or password: keyword argument must be given.

Since:

  • 1.0.0



482
483
484
# File 'lib/ronin/support/crypto.rb', line 482

def self.aes128_encrypt(data,**kwargs)
  self.aes128_cipher(direction: :encrypt, **kwargs).encrypt(data)
end

.aes256_cipher(**kwargs) ⇒ Cipher::AES

Creates a new AES-256 cipher.

Examples:

Crypto.aes256_cipher(direction: :encrypt, password: 's3cr3t')
# => #<Ronin::Support::Crypto::Cipher::AES256:0x00007f8bde789648 @key_size=256, @mode=:cbc>

Parameters:

Options Hash (**kwargs):

  • :mode (:cbc, :cfb, :ofb, :ctr, Symbol) — default: :cbc

    The desired AES cipher mode.

  • :hash (Symbol) — default: :sha256

    The algorithm to hash the :password.

  • :key (String)

    The secret key to use.

  • :password (String)

    The password for the cipher.

  • :iv (String)

    The optional Initial Vector (IV).

  • :padding (Integer)

    Sets the padding for the cipher.

Returns:

See Also:

Since:

  • 1.0.0



556
557
558
# File 'lib/ronin/support/crypto.rb', line 556

def self.aes256_cipher(**kwargs)
  Cipher::AES256.new(**kwargs)
end

.aes256_decrypt(data, **kwargs) ⇒ String

Decrypts data using AES-256.

Parameters:

  • data (#to_s)

    The data to encrypt.

  • kwargs (Hash{Symbol => Object})

    Additional keyword arguments for aes256_cipher.

Options Hash (**kwargs):

  • mode (:cbc, :cfb, :ofb, :ctr, Symbol) — default: :cbc

    The desired AES cipher mode.

  • :hash (Symbol) — default: :sh256

    The algorithm to hash the :password.

  • :key (String)

    The secret key to use.

  • :password (String)

    The password for the cipher.

  • :iv (String)

    The optional Initial Vector (IV).

  • :padding (Integer)

    Sets the padding for the cipher.

Returns:

  • (String)

    The encrypted data.

Raises:

  • (ArgumentError)

    Either the the key: or password: keyword argument must be given.

Since:

  • 1.0.0



630
631
632
# File 'lib/ronin/support/crypto.rb', line 630

def self.aes256_decrypt(data,**kwargs)
  self.aes256_cipher(direction: :decrypt, **kwargs).decrypt(data)
end

.aes256_encrypt(data, **kwargs) ⇒ String

Encrypts data using AES-256.

Parameters:

  • data (#to_s)

    The data to encrypt.

  • kwargs (Hash{Symbol => Object})

    Additional keyword arguments for aes256_cipher.

Options Hash (**kwargs):

  • mode (:cbc, :cfb, :ofb, :ctr, Symbol) — default: :cbc

    The desired AES cipher mode.

  • :hash (Symbol) — default: :sha256

    The algorithm to hash the :password.

  • :key (String)

    The secret key to use.

  • :password (String)

    The password for the cipher.

  • :iv (String)

    The optional Initial Vector (IV).

  • :padding (Integer)

    Sets the padding for the cipher.

Returns:

  • (String)

    The encrypted data.

Raises:

  • (ArgumentError)

    Either the the key: or password: keyword argument must be given.

Since:

  • 1.0.0



593
594
595
# File 'lib/ronin/support/crypto.rb', line 593

def self.aes256_encrypt(data,**kwargs)
  self.aes256_cipher(direction: :encrypt, **kwargs).encrypt(data)
end

.aes_cipher(**kwargs) ⇒ Cipher::AES

Creates a new AES cipher.

Examples:

Crypto.aes_cipher(key_size: 256, direction: :encrypt, password: 's3cr3t', hash: :sha256)
# => #<Ronin::Support::Crypto::Cipher::AES:0x00007f2b84dfa6b8 @key_size=256, @mode=:cbc>

Parameters:

Options Hash (**kwargs):

  • :key_size (Integer)

    The desired key size in bits.

  • :mode (:cbc, :cfb, :ofb, :ctr, Symbol) — default: :cbc

    The desired AES cipher mode.

  • :hash (Symbol) — default: :sha256

    The algorithm to hash the :password.

  • :key (String)

    The secret key to use.

  • :password (String)

    The password for the cipher.

  • :iv (String)

    The optional Initial Vector (IV).

  • :padding (Integer)

    Sets the padding for the cipher.

Returns:

See Also:

Since:

  • 1.0.0



328
329
330
# File 'lib/ronin/support/crypto.rb', line 328

def self.aes_cipher(**kwargs)
  Cipher::AES.new(**kwargs)
end

.aes_decrypt(data, **kwargs) ⇒ String

Decrypts data using AES.

Parameters:

  • data (#to_s)

    The data to encrypt.

  • kwargs (Hash{Symbol => Object})

    Additional keyword arguments for aes_cipher.

Options Hash (**kwargs):

  • :key_size (Integer)

    The desired key size in bits.

  • mode (:cbc, :cfb, :ofb, :ctr, Symbol) — default: :cbc

    The desired AES cipher mode.

  • :hash (Symbol) — default: :sha256

    The algorithm to hash the :password.

  • :key (String)

    The secret key to use.

  • :password (String)

    The password for the cipher.

  • :iv (String)

    The optional Initial Vector (IV).

  • :padding (Integer)

    Sets the padding for the cipher.

Returns:

  • (String)

    The encrypted data.

Raises:

  • (ArgumentError)

    Either the the key: or password: keyword argument must be given.

Since:

  • 1.0.0



408
409
410
# File 'lib/ronin/support/crypto.rb', line 408

def self.aes_decrypt(data,**kwargs)
  self.aes_cipher(direction: :decrypt, **kwargs).decrypt(data)
end

.aes_encrypt(data, **kwargs) ⇒ String

Encrypts data using AES.

Parameters:

  • data (#to_s)

    The data to encrypt.

  • kwargs (Hash{Symbol => Object})

    Additional keyword arguments for aes_cipher.

Options Hash (**kwargs):

  • :key_size (Integer)

    The desired key size in bits.

  • mode (:cbc, :cfb, :ofb, :ctr, Symbol) — default: :cbc

    The desired AES cipher mode.

  • :hash (Symbol) — default: :sha256

    The algorithm to hash the :password.

  • :key (String)

    The secret key to use.

  • :password (String)

    The password for the cipher.

  • :iv (String)

    The optional Initial Vector (IV).

  • :padding (Integer)

    Sets the padding for the cipher.

Returns:

  • (String)

    The encrypted data.

Raises:

  • (ArgumentError)

    Either the the key: or password: keyword argument must be given.

Since:

  • 1.0.0



368
369
370
# File 'lib/ronin/support/crypto.rb', line 368

def self.aes_encrypt(data,**kwargs)
  self.aes_cipher(direction: :encrypt, **kwargs).encrypt(data)
end

.Cert(cert) ⇒ Cert

Coerces a value into a Cert object.

Parameters:

  • cert (String, OpenSSL::X509::Certificate, Cert)

    The certificate String or OpenSSL::X509::Certificate value.

Returns:

  • (Cert)

    The coerced certificate.

Raises:

  • (ArgumentError)

    The certificate value was not a String or a OpenSSL::X509::Certificate object.

Since:

  • 1.0.0



554
555
556
557
558
559
560
561
562
563
564
565
# File 'lib/ronin/support/crypto/cert.rb', line 554

def self.Cert(cert)
  case cert
  when String then Cert.parse(cert)
  when Cert   then cert
  when OpenSSL::X509::Certificate
    new_cert = Cert.allocate
    new_cert.send(:initialize_copy,cert)
    new_cert
  else
    raise(ArgumentError,"value must be either a String or a OpenSSL::X509::Certificate object: #{cert.inspect}")
  end
end

.cipher(name, **kwargs) ⇒ OpenSSL::Cipher

Creates a cipher.

Examples:

Crypto.cipher('aes-128-cbc', direction: :encrypt, key 'secret'.md5)
# => #<OpenSSL::Cipher:0x0000000170d108>

Parameters:

Options Hash (**kwargs):

  • :direction (:encrypt, :decrypt)

    Specifies whether to encrypt or decrypt data.

  • :hash (Symbol) — default: :sha256

    The algorithm to hash the :password.

  • :key (String)

    The secret key to use.

  • :password (String)

    The password for the cipher.

  • :iv (String)

    The optional Initial Vector (IV).

  • :padding (Integer)

    Sets the padding for the cipher.

Returns:

  • (OpenSSL::Cipher)

    The newly created cipher.

Raises:

  • (ArgumentError)

    Either the the key: or password: keyword argument must be given.

See Also:

Since:

  • 1.0.0



210
211
212
# File 'lib/ronin/support/crypto.rb', line 210

def self.cipher(name,**kwargs)
  Cipher.new(name,**kwargs)
end

.ciphersArray<String>

The list of supported ciphers.

Examples:

Cipher.supported
# => ["RC5",
#     "aes-128-cbc",
#     "aes-128-cbc-hmac-sha1",
#     "aes-128-cbc-hmac-sha256",
#     ...]

Returns:

  • (Array<String>)

    The list of supported cipher names.

See Also:

Since:

  • 1.0.0



167
168
169
# File 'lib/ronin/support/crypto.rb', line 167

def self.ciphers
  Cipher.supported
end

.decrypt(data, cipher:, **kwargs) ⇒ String

Decrypts data using the cipher.

Parameters:

  • data (#to_s)

    The data to decrypt.

  • cipher (String)

    The cipher name (ex: "aes-256-cbc").

  • kwargs (Hash{Symbol => Object})

    Additional keyword arguments for cipher.

Options Hash (**kwargs):

  • :hash (Symbol) — default: :sha256

    The algorithm to hash the :password.

  • :key (String)

    The secret key to use.

  • :password (String)

    The password for the cipher.

  • :iv (String)

    The optional Initial Vector (IV).

  • :padding (Integer)

    Sets the padding for the cipher.

Returns:

  • (String)

    The decrypted data.

Raises:

  • (ArgumentError)

    Either the the key: or password: keyword argument must be given.

See Also:

Since:

  • 1.0.0



288
289
290
# File 'lib/ronin/support/crypto.rb', line 288

def self.decrypt(data, cipher: ,**kwargs)
  self.cipher(cipher, direction: :decrypt, **kwargs).decrypt(data)
end

.digest(name) ⇒ OpenSSL::Digest

Looks up a digest.

Examples:

Crypto.digest(:sha256)
# => OpenSSL::Digest::SHA256

Parameters:

  • name (String, Symbol)

    The name of the digest.

Returns:

  • (OpenSSL::Digest)

    The OpenSSL Digest class.

See Also:

Since:

  • 1.0.0



96
97
98
# File 'lib/ronin/support/crypto.rb', line 96

def self.digest(name)
  OpenSSL::Digest.const_get(name.upcase)
end

.encrypt(data, cipher:, **kwargs) ⇒ String

Encrypts data using the cipher.

Parameters:

  • data (#to_s)

    The data to encrypt.

  • cipher (String)

    The cipher name (ex: "aes-256-cbc").

  • kwargs (Hash{Symbol => Object})

    Additional keyword arguments for cipher.

Options Hash (**kwargs):

  • :hash (Symbol) — default: :sha256

    The algorithm to hash the :password.

  • :key (String)

    The secret key to use.

  • :password (String)

    The password for the cipher.

  • :iv (String)

    The optional Initial Vector (IV).

  • :padding (Integer)

    Sets the padding for the cipher.

Returns:

  • (String)

    The encrypted data.

Raises:

  • (ArgumentError)

    Either the the key: or password: keyword argument must be given.

See Also:

Since:

  • 1.0.0



249
250
251
# File 'lib/ronin/support/crypto.rb', line 249

def self.encrypt(data, cipher: ,**kwargs)
  self.cipher(cipher, direction: :encrypt, **kwargs).encrypt(data)
end

.hmac(data = nil, key:, digest: :sha1) {|hmac| ... } ⇒ OpenSSL::HMAC

Creates a new HMAC.

Examples:

hmac = Crypto.hmac("hello world", key: 'secret')
# => #<Ronin::Support::Crypto::HMAC: 03376ee7ad7bbfceee98660439a4d8b125122a5a>
hmac.hexdigest
# => "03376ee7ad7bbfceee98660439a4d8b125122a5a"
hmac.digest
# => "\x037n\xE7\xAD{\xBF\xCE\xEE\x98f\x049\xA4\xD8\xB1%\x12*Z"

with a block:

hmac = Crypto.hmac("hello world", key: 'secret') do |hmac|
  hmac << "hello"
  hmac << " world"
end
# => #<Ronin::Support::Crypto::HMAC: 03376ee7ad7bbfceee98660439a4d8b125122a5a>

Parameters:

  • data (String, nil) (defaults to: nil)

    The optional data to sign.

  • key (String)

    The secret key for the HMAC.

  • digest (Symbol) (defaults to: :sha1)

    The digest algorithm for the HMAC.

Yields:

  • (hmac)

    If a block is given, it will be passed the new HMAC object, which can then be populated.

Yield Parameters:

  • hmac (OpenSSL::HMAC)

    The new HMAC object.

Returns:

  • (OpenSSL::HMAC)

    The HMAC object.

See Also:

Since:

  • 1.0.0



139
140
141
142
143
144
145
146
147
# File 'lib/ronin/support/crypto.rb', line 139

def self.hmac(data=nil, key: , digest: :sha1)
  hmac = HMAC.new(key,digest(digest).new)

  if    block_given? then yield hmac
  elsif data         then hmac.update(data)
  end

  return hmac
end

.Key(key) ⇒ RSA, ...

Coerces a value into a Key object.

Parameters:

  • key (String, OpenSSL::PKey::RSA, OpenSSL::PKey::DSA, OpenSSL::PKey::DH, OpenSSL::PKey::EC)

    The key String or OpenSSL::PKey::PKey value.

Returns:

  • (RSA, DSA, DH, EC)

    The coerced certificate.

Raises:

  • (ArgumentError)

    The key value was not a String or a OpenSSL::PKey::PKey object.

  • (NotImplementedError)

    A new OpenSSL::PKey::PKey value that was not OpenSSL::PKey::RSA, OpenSSL::PKey::DSA, OpenSSL::PKey::DH, or OpenSSL::PKey::EC was given.

Since:

  • 1.1.0



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/ronin/support/crypto/key.rb', line 130

def self.Key(key)
  case key
  when String then Key.parse(key)
  when OpenSSL::PKey::PKey
    key_class = case key
                when OpenSSL::PKey::RSA then Key::RSA
                when OpenSSL::PKey::DSA then Key::DSA
                when OpenSSL::PKey::DH  then Key::DH
                when OpenSSL::PKey::EC  then Key::EC
                else
                  raise(NotImplementedError,"#{key.inspect} is not supported")
                end

    new_key = key_class.allocate
    new_key.send(:initialize_copy,key)
    new_key
  else
    raise(ArgumentError,"value must be either a String or a OpenSSL::PKey::PKey object: #{key.inspect}")
  end
end

.rot(string, n = 13, alphabets: [('A'..'Z').to_a, ('a'..'z').to_a, ('0'..'9').to_a]) ⇒ String

Note:

This method was added as a joke and should not be used for secure cryptographic communications.

Rotates the characters in the given string using the given alphabet.

Examples:

ROT13 "encryption":

Crypto.rot("The quick brown fox jumps over 13 lazy dogs.")
# => "Gur dhvpx oebja sbk whzcf bire 46 ynml qbtf."

ROT13 "decryption":

Crypto.rot("Gur dhvpx oebja sbk whzcf bire 46 ynml qbtf.", -13)
# => "The quick brown fox jumps over 13 lazy dogs."

Parameters:

  • string (String)

    The String to rotate.

  • n (Integer) (defaults to: 13)

    The number of characters to shift each character by.

  • alphabets (Array<Array<String>>) (defaults to: [('A'..'Z').to_a, ('a'..'z').to_a, ('0'..'9').to_a])

    The alphabet(s) to use.

Returns:

  • (String)

    The rotated string.

Since:

  • 1.0.0



760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
# File 'lib/ronin/support/crypto.rb', line 760

def self.rot(string,n=13, alphabets: [('A'..'Z').to_a, ('a'..'z').to_a, ('0'..'9').to_a])
  translation_table = {}

  alphabets.each do |alphabet|
    modulo = alphabet.count

    alphabet.each_with_index do |char,index|
      translation_table[char] = alphabet[(index + n) % modulo]
    end
  end

  new_string = String.new(encoding: string.encoding)

  string.each_char do |char|
    new_string << translation_table.fetch(char,char)
  end

  return new_string
end

.rsa_decrypt(data, key: nil, key_file: nil, key_password: nil, **kwargs) ⇒ String

Decrypts data using a RSA key.

Optional padding mode. nil and false will disable padding.

Parameters:

  • data (String)

    The data to decrypt.

  • key (String, nil) (defaults to: nil)

    The PEM or DER encoded RSA key string.

  • key_file (String, nil) (defaults to: nil)

    The path to the PEM or DER encoded RSA key file.

  • key_password (String, nil) (defaults to: nil)

    The optional password to decrypt the encrypted RSA key.

  • kwargs (Hash{Symbol => Object})

    Additional keyword arguments for Ronin::Support::Crypto::Key::RSA#private_decrypt.

Options Hash (**kwargs):

  • :padding (:pkcs1_oaep, :pkcs1, :sslv23, nil, false) — default: :pkcs1

Returns:

  • (String)

    The decrypted data.

Raises:

  • (ArgumentError)

    Either the key: or key_file: keyword argument must be given.

Since:

  • 1.0.0



727
728
729
730
731
# File 'lib/ronin/support/crypto.rb', line 727

def self.rsa_decrypt(data, key: nil, key_file: nil, key_password: nil, **kwargs)
  rsa = rsa_key(key, path: key_file, password: key_password)

  return rsa.private_decrypt(data,**kwargs)
end

.rsa_encrypt(data, key: nil, key_file: nil, key_password: nil, **kwargs) ⇒ String

Encrypts data using a RSA key.

Optional padding mode. nil and false will disable padding.

Parameters:

  • data (String)

    The data to encrypt.

  • key (String, nil) (defaults to: nil)

    The PEM or DER encoded RSA key string.

  • key_file (String, nil) (defaults to: nil)

    The path to the PEM or DER encoded RSA key file.

  • key_password (String, nil) (defaults to: nil)

    The optional password to decrypt the encrypted RSA key.

  • kwargs (Hash{Symbol => Object})

    Additional keyword arguments for Ronin::Support::Crypto::Key::RSA#public_encrypt.

Options Hash (**kwargs):

  • :padding (:pkcs1_oaep, :pkcs1, :sslv23, nil, false) — default: :pkcs1

Returns:

  • (String)

    The encrypted data.

Raises:

  • (ArgumentError)

    Either the key: or key_file: keyword argument must be given.

Since:

  • 1.0.0



693
694
695
696
697
# File 'lib/ronin/support/crypto.rb', line 693

def self.rsa_encrypt(data, key: nil, key_file: nil, key_password: nil, **kwargs)
  rsa = rsa_key(key, path: key_file, password: key_password)

  return rsa.public_encrypt(data,**kwargs)
end

.rsa_key(key = nil, path: nil, password: nil) ⇒ Key::RSA

Loads an RSA key.

Parameters:

  • key (String, nil) (defaults to: nil)

    The PEM or DER encoded RSA key string.

  • path (String, nil) (defaults to: nil)

    The path to the PEM or DER encoded RSA key file.

  • password (String, nil) (defaults to: nil)

    The optional password to decrypt the encrypted RSA key.

Returns:

Raises:

  • (ArgumentError)

    Either the key: or key_file: keyword argument must be given.

Since:

  • 1.0.0



651
652
653
654
655
656
657
658
659
660
661
662
663
# File 'lib/ronin/support/crypto.rb', line 651

def self.rsa_key(key=nil, path: nil, password: nil)
  if path
    Key::RSA.load_file(path, password: password)
  elsif key
    case key
    when Key::RSA           then key
    when OpenSSL::PKey::RSA then Key::RSA.new(key)
    when String             then Key::RSA.load(key, password: password)
    end
  else
    raise(ArgumentError,"either key: or key_file: keyword arguments must be given")
  end
end

.xor(string, key) ⇒ String

XOR encodes the String.

Examples:

Crypto.xor("hello", 0x41)
# => ")$--."
Crypto.xor("hello again", [0x55, 0x41, 0xe1])
# => "=$\x8d9.\xc14&\x80</"

Parameters:

  • string (String)

    The String to XOR.

  • key (Enumerable, Integer)

    The byte to XOR against each byte in the String.

Returns:

  • (String)

    The XOR encoded String.

Since:

  • 1.0.0



800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
# File 'lib/ronin/support/crypto.rb', line 800

def self.xor(string,key)
  key = case key
        when Integer then [key]
        when String  then key.bytes
        else              key
        end

  key    = key.cycle
  result = String.new(encoding: string.encoding)

  string.bytes.each do |b|
    result << (b ^ key.next).chr
  end

  return result
end