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
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
# 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
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
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
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
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
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
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
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
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
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
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
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
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
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
# 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 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 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