6 # A class that assists in encrypting and decrypting data using a
7 # combination of RSA and AES
9 # Data will be AES encrypted for speed, the Key used in # the AES
10 # stage will be encrypted using RSA
12 # ssl = SSL.new(public_key, private_key, passphrase)
14 # data = File.read("largefile.dat")
16 # crypted_data = ssl.encrypt_with_private(data)
20 # This will result in a hash of data like:
22 # crypted = {:key => "crd4NHvG....=",
23 # :data => "XWXlqN+i...=="}
25 # The key and data will all be base 64 encoded already by default
26 # you can pass a 2nd parameter as false to encrypt_with_private and
27 # counterparts that will prevent the base 64 encoding
29 # You can pass the data hash into ssl.decrypt_with_public which
30 # should return your original data
32 # There are matching methods for using a public key to encrypt
33 # data to be decrypted using a private key
35 attr_reader :public_key_file, :private_key_file, :ssl_cipher
37 def initialize(pubkey=nil, privkey=nil, passphrase=nil, cipher=nil)
38 @public_key_file = pubkey
39 @private_key_file = privkey
41 @public_key = read_key(:public, pubkey)
42 @private_key = read_key(:private, privkey, passphrase)
44 @ssl_cipher = "aes-256-cbc"
45 @ssl_cipher = Config.instance.ssl_cipher if Config.instance.ssl_cipher
46 @ssl_cipher = cipher if cipher
48 raise "The supplied cipher '#{@ssl_cipher}' is not supported" unless OpenSSL::Cipher.ciphers.include?(@ssl_cipher)
51 # Encrypts supplied data using AES and then encrypts using RSA
54 # Return a hash with everything optionally base 64 encoded
55 def encrypt_with_public(plain_text, base64=true)
56 crypted = aes_encrypt(plain_text)
59 key = base64_encode(rsa_encrypt_with_public(crypted[:key]))
60 data = base64_encode(crypted[:data])
62 key = rsa_encrypt_with_public(crypted[:key])
66 {:key => key, :data => data}
69 # Encrypts supplied data using AES and then encrypts using RSA
72 # Return a hash with everything optionally base 64 encoded
73 def encrypt_with_private(plain_text, base64=true)
74 crypted = aes_encrypt(plain_text)
77 key = base64_encode(rsa_encrypt_with_private(crypted[:key]))
78 data = base64_encode(crypted[:data])
80 key = rsa_encrypt_with_private(crypted[:key])
84 {:key => key, :data => data}
87 # Decrypts data, expects a hash as create with crypt_with_public
88 def decrypt_with_private(crypted, base64=true)
89 raise "Crypted data should include a key" unless crypted.include?(:key)
90 raise "Crypted data should include data" unless crypted.include?(:data)
93 key = rsa_decrypt_with_private(base64_decode(crypted[:key]))
94 aes_decrypt(key, base64_decode(crypted[:data]))
96 key = rsa_decrypt_with_private(crypted[:key])
97 aes_decrypt(key, crypted[:data])
101 # Decrypts data, expects a hash as create with crypt_with_private
102 def decrypt_with_public(crypted, base64=true)
103 raise "Crypted data should include a key" unless crypted.include?(:key)
104 raise "Crypted data should include data" unless crypted.include?(:data)
107 key = rsa_decrypt_with_public(base64_decode(crypted[:key]))
108 aes_decrypt(key, base64_decode(crypted[:data]))
110 key = rsa_decrypt_with_public(crypted[:key])
111 aes_decrypt(key, crypted[:data])
115 # Use the public key to RSA encrypt data
116 def rsa_encrypt_with_public(plain_string)
117 raise "No public key set" unless @public_key
119 @public_key.public_encrypt(plain_string)
122 # Use the private key to RSA decrypt data
123 def rsa_decrypt_with_private(crypt_string)
124 raise "No private key set" unless @private_key
126 @private_key.private_decrypt(crypt_string)
129 # Use the private key to RSA encrypt data
130 def rsa_encrypt_with_private(plain_string)
131 raise "No private key set" unless @private_key
133 @private_key.private_encrypt(plain_string)
136 # Use the public key to RSA decrypt data
137 def rsa_decrypt_with_public(crypt_string)
138 raise "No public key set" unless @public_key
140 @public_key.public_decrypt(crypt_string)
143 # encrypts a string, returns a hash of key, iv and data
144 def aes_encrypt(plain_string)
145 cipher = OpenSSL::Cipher::Cipher.new(ssl_cipher)
148 key = cipher.random_key
151 cipher.pkcs5_keyivgen(key)
152 encrypted_data = cipher.update(plain_string) + cipher.final
154 {:key => key, :data => encrypted_data}
157 # decrypts a string given key, iv and data
158 def aes_decrypt(key, crypt_string)
159 cipher = OpenSSL::Cipher::Cipher.new(ssl_cipher)
163 cipher.pkcs5_keyivgen(key)
164 decrypted_data = cipher.update(crypt_string) + cipher.final
167 # Signs a string using the private key
168 def sign(string, base64=false)
169 sig = @private_key.sign(OpenSSL::Digest::SHA1.new, string)
171 base64 ? base64_encode(sig) : sig
174 # Using the public key verifies that a string was signed using the private key
175 def verify_signature(signature, string, base64=false)
176 signature = base64_decode(signature) if base64
178 @public_key.verify(OpenSSL::Digest::SHA1.new, signature, string)
181 # base 64 encode a string
182 def base64_encode(string)
183 SSL.base64_encode(string)
186 def self.base64_encode(string)
187 Base64.encode64(string)
190 # base 64 decode a string
191 def base64_decode(string)
192 SSL.base64_decode(string)
195 def self.base64_decode(string)
196 Base64.decode64(string)
204 Digest::MD5.hexdigest(string)
207 # Creates a RFC 4122 version 5 UUID. If string is supplied it will produce repeatable
208 # UUIDs for that string else a random 128bit string will be used from OpenSSL::BN
210 # Code used with permission from:
211 # https://github.com/kwilczynski/puppet-functions/blob/master/lib/puppet/parser/functions/uuid.rb
213 def self.uuid(string=nil)
214 string ||= OpenSSL::Random.random_bytes(16).unpack('H*').shift
216 uuid_name_space_dns = "\x6b\xa7\xb8\x10\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8"
218 sha1 = Digest::SHA1.new
219 sha1.update(uuid_name_space_dns)
223 bytes = sha1.digest[0, 16].bytes.to_a
225 # version 5 adjustments
233 bytes = [4, 2, 2, 2, 6].collect do |i|
234 bytes.slice!(0, i).pack('C*').unpack('H*')
240 # Reads either a :public or :private key from disk, uses an
241 # optional passphrase to read the private key
242 def read_key(type, key=nil, passphrase=nil)
243 return key if key.nil?
245 raise "Could not find key #{key}" unless File.exist?(key)
246 raise "#{type} key file '#{key}' is empty" if File.zero?(key)
250 key = OpenSSL::PKey::RSA.new(File.read(key))
251 rescue OpenSSL::PKey::RSAError
252 key = OpenSSL::X509::Certificate.new(File.read(key)).public_key
255 # Ruby < 1.9.3 had a bug where it does not correctly clear the
256 # queue of errors while reading a key. It tries various ways
257 # to read the key and each failing attempt pushes an error onto
258 # the queue. With pubkeys only the 3rd attempt pass leaving 2
259 # stale errors on the error queue.
261 # In 1.9.3 they fixed this by simply discarding the errors after
262 # every attempt. So we simulate this fix here for older rubies
263 # as without it we get SSL_read errors from the Stomp+TLS sessions
265 # We do this only on 1.8 relying on 1.9.3 to do the right thing
266 # and we do not support 1.9 less than 1.9.3
268 # See http://bugs.ruby-lang.org/issues/4550
269 OpenSSL.errors if Util.ruby_version =~ /^1.8/
272 elsif type == :private
273 return OpenSSL::PKey::RSA.new(File.read(key), passphrase)
275 raise "Can only load :public or :private keys"