Module: Crypto
- Defined in:
- lib/ec2/amitools/crypto.rb
Overview
Cryptographic utilities module.
Constant Summary collapse
- BUFFER_SIZE =
1024 * 1024
- ASYM_ALG =
'RSA'
- SYM_ALG =
'AES-128-CBC'
- DIGEST_ALG =
'SHA1'
- PADDING =
OpenSSL::PKey::RSA::PKCS1_PADDING
- VERSION1 =
1
- VERSION2 =
2
- SHA1_FINGERPRINT_REGEX =
/([a-f0-9]{2}(:[a-f0-9]{2}){15})/i
Class Method Summary collapse
-
.authenticate(data, sig, pubkey) ⇒ Object
Verify the authenticity of the data from the IO stream or string ((|data|)) using the signature ((|sig|)) and the public key ((|pubkey|)).
-
.cert2pubkey(data) ⇒ Object
—————————————————————————-#.
-
.cert_sha1_fingerprint(cert_filename) ⇒ Object
Generate the SHA1 fingerprint for a PEM-encoded certificate (NOT private key) Returns the fingerprint in aa:bb:…
-
.certfile2pubkey(filename) ⇒ Object
Return the public key from the X509 certificate file ((|filename|)).
-
.decryptasym(cipher_text, keyfilename) ⇒ Object
Decrypt the specified cipher text according to the AMI Manifest Encryption Scheme Version 1 or 2.
-
.decryptasym_v2(cipher_text, keyfilename) ⇒ Object
Decrypt the specified cipher text according to the AMI Manifest Encryption Scheme Version 2.
-
.decryptfile(src, dst, key, iv) ⇒ Object
Decrypt the specified cipher text file to create the specified plain text file.
-
.decryptsym(plaintext, key, iv) ⇒ Object
Decrypt ciphertext using key and iv using AES-128-CBC.
-
.digest(io, alg = OpenSSL::Digest::SHA1.new) ⇒ Object
Generate and return a message digest for the data from the IO stream ((|io|)), using the algorithm alg.
-
.encryptasym(data, pubkey) ⇒ Object
Asymmetrically encrypt the specified data using the AMI Manifest Encryption Scheme Version 2.
-
.encryptfile(src, dst, key, iv) ⇒ Object
Decrypt the specified cipher text file to create the specified plain text file.
-
.encryptsym(plaintext, key, iv) ⇒ Object
Encrypt plaintext with key and iv using AES-128-CBC.
-
.geniv ⇒ Object
Generate an initialization vector suitable use with symmetric cipher.
-
.gensymkey ⇒ Object
Generate a key suitable for use with a symmetric cipher.
-
.hmac_sha1(key, data) ⇒ Object
Return the HMAC SHA1 of data using key.
-
.loadprivkey(filename) ⇒ Object
——————————————————————————#.
-
.sign(data, keyfilename) ⇒ Object
Sign the data from IO stream or string ((|data|)) using the key in ((|keyfilename|)).
-
.xor(a, b) ⇒ Object
XOR the byte string ((|a|)) with the byte string ((|b|)).
Class Method Details
.authenticate(data, sig, pubkey) ⇒ Object
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.
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# 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) ⇒ Object
—————————————————————————-#
301 302 303 304 305 306 307 |
# 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.}" end end |
.cert_sha1_fingerprint(cert_filename) ⇒ Object
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
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 |
# 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) ⇒ Object
Return the public key from the X509 certificate file ((|filename|)).
289 290 291 292 293 294 295 296 297 |
# 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.}" end end |
.decryptasym(cipher_text, keyfilename) ⇒ Object
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.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# 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) ⇒ Object
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.
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
# 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) ⇒ Object
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.
168 169 170 171 172 173 174 175 176 |
# 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) ⇒ Object
Decrypt ciphertext using key and iv using AES-128-CBC.
183 184 185 186 187 188 189 190 191 192 193 194 195 |
# 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) ⇒ Object
Generate and return a message digest for the data from the IO stream ((|io|)), using the algorithm alg
203 204 205 206 207 208 209 |
# 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) ⇒ Object
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.
115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# 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) ⇒ Object
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.
238 239 240 241 242 243 244 245 |
# 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) ⇒ Object
Encrypt plaintext with key and iv using AES-128-CBC.
252 253 254 255 256 257 258 259 260 261 262 263 264 |
# 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 ⇒ Object
Generate an initialization vector suitable use with symmetric cipher.
271 272 273 |
# File 'lib/ec2/amitools/crypto.rb', line 271 def Crypto.geniv OpenSSL::Cipher::Cipher.new(SYM_ALG).random_iv end |
.gensymkey ⇒ Object
Generate a key suitable for use with a symmetric cipher.
280 281 282 |
# File 'lib/ec2/amitools/crypto.rb', line 280 def Crypto.gensymkey OpenSSL::Cipher::Cipher.new(SYM_ALG).random_key end |
.hmac_sha1(key, data) ⇒ Object
Return the HMAC SHA1 of data using key.
214 215 216 217 218 219 220 221 222 |
# 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) ⇒ Object
——————————————————————————#
356 357 358 359 360 361 362 |
# 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.}" end end |
.sign(data, keyfilename) ⇒ Object
Sign the data from IO stream or string ((|data|)) using the key in ((|keyfilename|)).
Return the signature.
317 318 319 320 321 322 323 324 325 326 327 |
# 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) ⇒ Object
XOR the byte string ((|a|)) with the byte string ((|b|)). The operans must be of the same length.
370 371 372 373 374 375 376 377 378 |
# 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 |