module Crypto

Cryptographic utilities module.

Cryptographic utilities module.

Constants

ASYM_ALG
BUFFER_SIZE
DIGEST_ALG
PADDING
SHA1_FINGERPRINT_REGEX
SYM_ALG
VERSION1
VERSION2

Public Class Methods

authenticate(data, sig, pubkey) click to toggle source

Verify the authenticity of the data from the IO stream or string ((|data|)) using the signature ((|sig|)) and the public key ((|pubkey|)).

Return true iff the signature is valid.

# File lib/ec2/amitools/crypto.rb, line 138
def Crypto.authenticate(data, sig, pubkey)
  raise ArgumentError.new("Invalid parameter data") if data.nil?
  raise ArgumentError.new("Invalid parameter sig") if sig.nil? or sig.length==0
  raise ArgumentError.new("Invalid parameter pubkey") if pubkey.nil?

  # Create IO stream if necessary.
  io = (data.instance_of?(StringIO) ? data : StringIO.new(data))

  sha = OpenSSL::Digest::SHA1.new
  res = false
  while not (io.eof?)
    res = pubkey.verify(sha, sig, io.read(BUFFER_SIZE))
  end
  res
end
cert2pubkey(data) click to toggle source
# File lib/ec2/amitools/crypto.rb, line 301
def Crypto.cert2pubkey(data)
  begin
    return OpenSSL::X509::Certificate.new(data).public_key
  rescue Exception => e
    raise "error reading certificate: #{e.message}"
  end
end
cert_sha1_fingerprint(cert_filename) click to toggle source

Generate the SHA1 fingerprint for a PEM-encoded certificate (NOT private key) Returns the fingerprint in aa:bb:… form Raises ArgumentError if the fingerprint cannot be obtained

# File lib/ec2/amitools/crypto.rb, line 336
def Crypto.cert_sha1_fingerprint(cert_filename)
  raise ArgumentError.new('cert_filename is nil')  if cert_filename.nil?
  raise ArgumentError.new("invalid cert file name: #{cert_filename}") unless FileTest.exists?(cert_filename)
  fingerprint = nil

  IO.popen("openssl x509 -in #{cert_filename} -noout -sha1 -fingerprint") do |io|
    out = io.read
    md = SHA1_FINGERPRINT_REGEX.match(out)
    if md
      fingerprint = md[1]
    end
  end

  raise ArgumentError.new("could not generate fingerprint for #{cert_filename}")  if fingerprint.nil?

  return fingerprint
end
certfile2pubkey(filename) click to toggle source

Return the public key from the X509 certificate file ((|filename|)).

# File lib/ec2/amitools/crypto.rb, line 289
def Crypto.certfile2pubkey(filename)
  begin
    File.open(filename) do |f|
      return cert2pubkey(f)
    end
  rescue Exception => e
    raise "error reading certificate file #{filename}: #{e.message}"
  end
end
decryptasym(cipher_text, keyfilename) click to toggle source

Decrypt the specified cipher text according to the AMI Manifest Encryption Scheme Version 1 or 2.

((|cipher_text|)) The cipher text to decrypt. ((|keyio_or_keyfilename|)) The key data IO stream or the name of the private key file.

# File lib/ec2/amitools/crypto.rb, line 39
def Crypto.decryptasym(cipher_text, keyfilename)
  raise ArgumentError.new('cipher_text') unless cipher_text
  raise ArgumentError.new('keyfilename') unless keyfilename and FileTest.exists? keyfilename

  # Load key.
  privkey = File.open(keyfilename, 'r') { |f| OpenSSL::PKey::RSA.new(f) }

  # Get version.
  version = cipher_text[0]
  if version == VERSION2
    return Crypto.decryptasym_v2( cipher_text, keyfilename )
  end
  raise ArgumentError.new("invalid encryption scheme versionb: #{version}") unless version == 1

  # Decrypt and extract encrypted symmetric key and initialization vector.
  symkey_cryptogram_len = cipher_text.slice(1, 2).unpack('C')[0]
  symkey_cryptogram = privkey.private_decrypt(
    cipher_text.slice(2, symkey_cryptogram_len),
    PADDING)
  symkey = symkey_cryptogram.slice(0, 16)
  iv = symkey_cryptogram.slice(16, 16)

  # Decrypt data with the symmetric key.
  cryptogram = cipher_text.slice(2 + symkey_cryptogram_len..cipher_text.size)
  decryptsym(cryptogram, symkey, iv)
end
decryptasym_v2(cipher_text, keyfilename) click to toggle source

Decrypt the specified cipher text according to the AMI Manifest Encryption Scheme Version 2.

((|cipher_text|)) The cipher text to decrypt. ((|keyio_or_keyfilename|)) The key data IO stream or the name of the private key file.

# File lib/ec2/amitools/crypto.rb, line 76
def Crypto.decryptasym_v2(cipher_text, keyfilename)
  raise ArgumentError.new('cipher_text') unless cipher_text
  raise ArgumentError.new('keyfilename') unless keyfilename and FileTest.exists? keyfilename

  # Load key.
  privkey = File.open(keyfilename, 'r') { |f| OpenSSL::PKey::RSA.new(f) }

  # Get version.
  version = cipher_text[0]
  raise ArgumentError.new("invalid encryption scheme versionb: #{version}") unless version == VERSION2

  # Decrypt and extract encrypted symmetric key and initialization vector.
  hi_byte, lo_byte = cipher_text.slice(1, 3).unpack('CC')
  symkey_cryptogram_len = ( hi_byte << 8 ) | lo_byte
  symkey_cryptogram = privkey.private_decrypt(
    cipher_text.slice(3, symkey_cryptogram_len),
    PADDING)
  
  symkey = symkey_cryptogram.slice(0, 16)
  iv = symkey_cryptogram.slice(16, 16)

  # Decrypt data with the symmetric key.
  cryptogram = cipher_text.slice( ( 3 + symkey_cryptogram_len )..cipher_text.size)
  decryptsym(cryptogram, symkey, iv)
end
decryptfile(src, dst, key, iv) click to toggle source

Decrypt the specified cipher text file to create the specified plain text file.

The symmetric cipher is AES in CBC mode. 128 bit keys are used. If the plain text file already exists it will be overwritten.

((|src|)) The name of the cipher text file to decrypt. ((|dst|)) The name of the plain text file to create. ((|key|)) The 128 bit (16 byte) symmetric key. ((|iv|)) The 128 bit (16 byte) initialization vector.

# File lib/ec2/amitools/crypto.rb, line 168
def Crypto.decryptfile(src, dst, key, iv)
  raise ArgumentError.new("invalid file name: #{src}") unless FileTest.exists?(src)
  raise ArgumentError.new("invalid key") unless key and key.size == 16
  raise ArgumentError.new("invalid iv") unless iv and iv.size == 16
  pio = IO.popen( "openssl enc -d -aes-128-cbc -in #{src} -out #{dst} -K #{Format::bin2hex(key)} -iv #{Format::bin2hex(iv)} 2>&1" )
  result = pio.read
  pio.close
  raise "error decrypting file #{src}: #{result}" if result.strip != ''
end
decryptsym(plaintext, key, iv) click to toggle source

Decrypt ciphertext using key and iv using AES-128-CBC.

# File lib/ec2/amitools/crypto.rb, line 183
def Crypto.decryptsym(plaintext, key, iv)
  raise ArgumentError.new("plaintext must be a String") unless plaintext.is_a? String
  raise ArgumentError.new("invalid key") unless key.is_a? String and key.size == 16
  raise ArgumentError.new("invalid iv") unless iv.is_a? String and iv.size == 16

  cipher = OpenSSL::Cipher::Cipher.new( 'AES-128-CBC' )
  cipher.decrypt( key, iv )
  # NOTE: If the key and iv aren't set this doesn't work correctly.
  cipher.key = key
  cipher.iv = iv
  plaintext = cipher.update( plaintext )
  plaintext + cipher.final
end
digest(io, alg = OpenSSL::Digest::SHA1.new) click to toggle source

Generate and return a message digest for the data from the IO stream ((|io|)), using the algorithm alg

# File lib/ec2/amitools/crypto.rb, line 203
def Crypto.digest(io, alg = OpenSSL::Digest::SHA1.new)
  raise ArgumentError.new('io') unless io.kind_of?(IO) or io.kind_of?(StringIO)
  while not io.eof?
    alg.update(io.read(BUFFER_SIZE))
  end
  alg.digest
end
encryptasym(data, pubkey) click to toggle source

Asymmetrically encrypt the specified data using the AMI Manifest Encryption Scheme Version 2.

The data is encrypted with an ephemeral symmetric key and initialization vector. The symmetric key and initialization vector are encrypted with the specified public key and preprended to the data.

((|data|)) The data to encrypt. ((|pubkey|)) The public key.

# File lib/ec2/amitools/crypto.rb, line 115
def Crypto.encryptasym(data, pubkey)
  raise ArgumentError.new('data') unless data
  raise ArgumentError.new('pubkey') unless pubkey

  symkey = gensymkey
  iv = geniv
  symkey_cryptogram = pubkey.public_encrypt( symkey + iv, PADDING )

  data_cryptogram = encryptsym(data, symkey, iv)

  hi_byte, lo_byte = Format.int2int16(symkey_cryptogram.size)

  Format::int2byte(VERSION2) + hi_byte + lo_byte + symkey_cryptogram + data_cryptogram
end
encryptfile(src, dst, key, iv) click to toggle source

Decrypt the specified cipher text file to create the specified plain text file.

The symmetric cipher is AES in CBC mode. 128 bit keys are used. If the plain text file already exists it will be overwritten.

((|key|)) The 128 bit (16 byte) symmetric key. ((|src|)) The name of the cipher text file to encrypt. ((|dst|)) The name of the plain text file to create. ((|iv|)) The 128 bit (16 byte) initialization vector.

# File lib/ec2/amitools/crypto.rb, line 238
def Crypto.encryptfile(src, dst, key, iv)
  raise ArgumentError.new("invalid file name: #{src}") unless FileTest.exists?(src)
  raise ArgumentError.new("invalid key") unless key and key.size == 16
  raise ArgumentError.new("invalid iv") unless iv and iv.size == 16
  cmd = "openssl enc -e -aes-128-cbc -in #{src} -out #{dst} -K #{Format::bin2hex(key)} -iv #{Format::bin2hex(iv)}"
  result = Kernel::system(cmd)
  raise "error encrypting file #{src}" unless result
end
encryptsym(plaintext, key, iv) click to toggle source

Encrypt plaintext with key and iv using AES-128-CBC.

# File lib/ec2/amitools/crypto.rb, line 252
def Crypto.encryptsym(plaintext, key, iv)
  raise ArgumentError.new("plaintext must be a String") unless plaintext.kind_of? String
  raise ArgumentError.new("invalid key") unless ( key.is_a? String and key.size == 16 )
  raise ArgumentError.new("invalid iv") unless ( iv.is_a? String and iv.size == 16 )

  cipher = OpenSSL::Cipher::Cipher.new( 'AES-128-CBC' )
  cipher.encrypt( key, iv )
  # NOTE: If the key and iv aren't set this doesn't work correctly.
  cipher.key = key
  cipher.iv = iv
  ciphertext = cipher.update( plaintext )
  ciphertext + cipher.final
end
geniv() click to toggle source

Generate an initialization vector suitable use with symmetric cipher.

# File lib/ec2/amitools/crypto.rb, line 271
def Crypto.geniv
  OpenSSL::Cipher::Cipher.new(SYM_ALG).random_iv
end
gensymkey() click to toggle source

Generate a key suitable for use with a symmetric cipher.

# File lib/ec2/amitools/crypto.rb, line 280
def Crypto.gensymkey
  OpenSSL::Cipher::Cipher.new(SYM_ALG).random_key
end
hmac_sha1( key, data ) click to toggle source

Return the HMAC SHA1 of data using key.

# File lib/ec2/amitools/crypto.rb, line 214
def Crypto.hmac_sha1( key, data )
  raise ParameterError.new( "key must be a String" ) unless key.is_a? String
  raise ParameterError.new( "data must be a String" ) unless data.is_a? String

  md = OpenSSL::Digest::SHA1.new
  hmac = OpenSSL::HMAC.new( key, md)
  hmac.update( data )
  return hmac.digest
end
loadprivkey(filename) click to toggle source
# File lib/ec2/amitools/crypto.rb, line 356
def Crypto.loadprivkey filename
  begin
    OpenSSL::PKey::RSA.new( File.open( filename,'r' ) )
  rescue Exception => e
    raise "error reading private key from file #{filename}: #{e.message}"
  end
end
sign(data, keyfilename) click to toggle source

Sign the data from IO stream or string ((|data|)) using the key in ((|keyfilename|)).

Return the signature.

# File lib/ec2/amitools/crypto.rb, line 317
def Crypto.sign(data, keyfilename)
  raise ArgumentError.new('data') unless data
  raise ArgumentError.new("invalid file name: #{keyfilename}") unless FileTest.exists?(keyfilename)

  # Create an IO stream from the data if necessary.
  io = (data.instance_of?(StringIO) ? data : StringIO.new(data))

  sha = OpenSSL::Digest::SHA1.new
  pk  = loadprivkey( keyfilename )
  return pk.sign(sha, io.read )
end
xor(a, b) click to toggle source

XOR the byte string ((|a|)) with the byte string ((|b|)). The operans must be of the same length.

# File lib/ec2/amitools/crypto.rb, line 370
def Crypto.xor(a, b)
  raise ArgumentError.new('data lengths differ') unless a.size == b.size
  xored = String.new
  a.size.times do |i|
    xored << (a[i] ^ b[i])
  end

  xored
end