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



443
444
445
# File 'lib/ronin/support/crypto.rb', line 443

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 key: or password: keyword argument must be given.

Since:

  • 1.0.0



517
518
519
# File 'lib/ronin/support/crypto.rb', line 517

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 key: or password: keyword argument must be given.

Since:

  • 1.0.0



480
481
482
# File 'lib/ronin/support/crypto.rb', line 480

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



554
555
556
# File 'lib/ronin/support/crypto.rb', line 554

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 key: or password: keyword argument must be given.

Since:

  • 1.0.0



628
629
630
# File 'lib/ronin/support/crypto.rb', line 628

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 key: or password: keyword argument must be given.

Since:

  • 1.0.0



591
592
593
# File 'lib/ronin/support/crypto.rb', line 591

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



326
327
328
# File 'lib/ronin/support/crypto.rb', line 326

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 key: or password: keyword argument must be given.

Since:

  • 1.0.0



406
407
408
# File 'lib/ronin/support/crypto.rb', line 406

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 key: or password: keyword argument must be given.

Since:

  • 1.0.0



366
367
368
# File 'lib/ronin/support/crypto.rb', line 366

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 key: or password: keyword argument must be given.

See Also:

Since:

  • 1.0.0



208
209
210
# File 'lib/ronin/support/crypto.rb', line 208

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

.ciphersArray<String>

The list of supported ciphers.

Examples:

Crypto.ciphers
# => ["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



165
166
167
# File 'lib/ronin/support/crypto.rb', line 165

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 key: or password: keyword argument must be given.

See Also:

Since:

  • 1.0.0



286
287
288
# File 'lib/ronin/support/crypto.rb', line 286

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 key: or password: keyword argument must be given.

See Also:

Since:

  • 1.0.0



247
248
249
# File 'lib/ronin/support/crypto.rb', line 247

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



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

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



725
726
727
728
729
# File 'lib/ronin/support/crypto.rb', line 725

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



691
692
693
694
695
# File 'lib/ronin/support/crypto.rb', line 691

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



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

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



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

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